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::FmtCompactAnyhow 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_anyhow(), "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(target_family = "wasm")]
169 let can_block = false;
170 #[cfg(not(target_family = "wasm"))]
171 let can_block = RuntimeHandle::current().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 if cfg!(target_family = "wasm") {
177 error!(target: LOG_CLIENT, "Automatic client shutdown is not possible on wasm, call ClientHandle::shutdown manually.");
178 } else {
179 error!(target: LOG_CLIENT, "Automatic client shutdown is not possible on current thread runtime, call ClientHandle::shutdown manually.");
180 }
181 return;
182 }
183
184 self.inner
185 .as_ref()
186 .expect("Must have inner client set")
187 .client_span
188 .in_scope(|| {
189 debug!(target: LOG_CLIENT, "Shutting down the Client on last handle drop");
190 });
191 #[cfg(not(target_family = "wasm"))]
192 runtime::block_in_place(|| {
193 runtime::block_on(self.shutdown_inner());
194 });
195 }
196}