fedimint_client/client/
handle.rs1use std::ops;
2use std::sync::Arc;
3use std::time::Duration;
4
5#[cfg(not(target_family = "wasm"))]
6use fedimint_core::runtime;
7use fedimint_core::util::FmtCompact as _;
8use fedimint_logging::LOG_CLIENT;
9#[cfg(not(target_family = "wasm"))]
10use tokio::runtime::{Handle as RuntimeHandle, RuntimeFlavor};
11use tracing::{Instrument as _, debug, error, trace, warn};
12
13use super::Client;
14use crate::ClientBuilder;
15use crate::error::ClientBuildError;
16
17#[derive(Debug)]
27pub struct ClientHandle {
28 inner: Option<Arc<Client>>,
29}
30
31pub type ClientHandleArc = Arc<ClientHandle>;
33
34impl ClientHandle {
35 pub(crate) fn new(inner: Arc<Client>) -> Self {
37 ClientHandle {
38 inner: inner.into(),
39 }
40 }
41
42 pub(crate) fn as_inner(&self) -> &Arc<Client> {
43 self.inner.as_ref().expect("Inner always set")
44 }
45
46 pub fn start_executor(&self) {
47 self.as_inner().start_executor();
48 }
49
50 pub async fn shutdown(mut self) {
52 self.shutdown_inner().await;
53 }
54
55 async fn shutdown_inner(&mut self) {
56 let Some(inner) = self.inner.take() else {
57 error!(
58 target: LOG_CLIENT,
59 "ClientHandleShared::shutdown called twice"
60 );
61 return;
62 };
63 let client_span = inner.client_span.clone();
64 inner.executor.stop_executor();
65 let db = inner.db.clone();
66 client_span.in_scope(|| {
67 debug!(target: LOG_CLIENT, "Waiting for client task group to shut down");
68 });
69 if let Err(err) = inner
70 .task_group
71 .clone()
72 .shutdown_join_all(Some(Duration::from_secs(30)))
73 .instrument(client_span.clone())
74 .await
75 {
76 client_span.in_scope(|| {
77 warn!(target: LOG_CLIENT, err = %err.fmt_compact(), "Error waiting for client task group to shut down");
78 });
79 }
80
81 let client_strong_count = Arc::strong_count(&inner);
82 client_span.in_scope(|| {
83 debug!(target: LOG_CLIENT, "Dropping last handle to Client");
84 });
85 drop(inner);
88
89 client_span.in_scope(|| {
90 if client_strong_count != 1 {
91 debug!(target: LOG_CLIENT, count = client_strong_count - 1, LOG_CLIENT, "External Client references remaining after last handle dropped");
92 }
93
94 let db_strong_count = db.strong_count();
95 if db_strong_count != 1 {
96 debug!(target: LOG_CLIENT, count = db_strong_count - 1, "External DB references remaining after last handle dropped");
97 }
98 trace!(target: LOG_CLIENT, "Dropped last handle to Client");
99 });
100 }
101
102 pub async fn restart(self) -> Result<ClientHandle, ClientBuildError> {
110 let (builder, config, api_secret, root_secret, db, endpoints) = {
111 let client = self
112 .inner
113 .as_ref()
114 .ok_or(ClientBuildError::AlreadyStopped)?;
115 let builder = ClientBuilder::from_existing(client);
116 let config = client.config().await;
117 let api_secret = client.api_secret.clone();
118 let root_secret = client.root_secret.clone();
119 let db = client.db().clone();
120 let endpoints = client.endpoints().clone();
121
122 (builder, config, api_secret, root_secret, db, endpoints)
123 };
124 self.shutdown().await;
125
126 builder
127 .build(
128 endpoints,
129 db,
130 root_secret,
131 config,
132 api_secret,
133 false,
134 None,
135 None,
136 None, )
138 .await
139 }
140}
141
142impl ops::Deref for ClientHandle {
143 type Target = Client;
144
145 fn deref(&self) -> &Self::Target {
146 self.inner.as_ref().expect("Must have inner client set")
147 }
148}
149
150impl Drop for ClientHandle {
156 fn drop(&mut self) {
157 if self.inner.is_none() {
158 return;
159 }
160
161 #[cfg(not(target_family = "wasm"))]
166 let runtime_handle = RuntimeHandle::try_current();
167 #[cfg(target_family = "wasm")]
168 let can_block = false;
169 #[cfg(not(target_family = "wasm"))]
170 let can_block = runtime_handle
171 .as_ref()
172 .is_ok_and(|handle| handle.runtime_flavor() != RuntimeFlavor::CurrentThread);
173 if !can_block {
174 let inner = self.inner.take().expect("Must have inner client set");
175 inner.executor.stop_executor();
176 #[cfg(target_family = "wasm")]
177 let reason = "on wasm";
178 #[cfg(not(target_family = "wasm"))]
179 let reason = if runtime_handle.is_ok() {
181 "on current thread runtime"
182 } else {
183 "without a runtime"
184 };
185 error!(target: LOG_CLIENT, "Automatic client shutdown is not possible {reason}, call ClientHandle::shutdown manually.");
186 return;
187 }
188
189 self.inner
190 .as_ref()
191 .expect("Must have inner client set")
192 .client_span
193 .in_scope(|| {
194 debug!(target: LOG_CLIENT, "Shutting down the Client on last handle drop");
195 });
196 #[cfg(not(target_family = "wasm"))]
197 runtime::block_in_place(|| {
198 runtime::block_on(self.shutdown_inner());
199 });
200 }
201}