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