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