1pub mod error;
2pub mod http;
3pub mod iroh;
4pub mod metrics;
5#[cfg(all(feature = "tor", not(target_family = "wasm")))]
6pub mod tor;
7pub mod ws;
8
9use std::collections::{BTreeMap, BTreeSet, HashMap};
10use std::fmt::{self, Debug};
11use std::net::SocketAddr;
12use std::pin::Pin;
13use std::str::FromStr as _;
14use std::sync::Arc;
15use std::time::Duration;
16
17use anyhow::{Context as _, anyhow, bail};
18use async_trait::async_trait;
19use fedimint_core::envs::{
20 FM_WS_API_CONNECT_OVERRIDES_ENV, is_running_in_test_env, parse_kv_list_from_env,
21};
22use fedimint_core::module::{ApiMethod, ApiRequestErased};
23use fedimint_core::util::backoff_util::{FibonacciBackoff, custom_backoff};
24use fedimint_core::util::{FmtCompact, FmtCompactAnyhow, SafeUrl};
25use fedimint_core::{apply, async_trait_maybe_send};
26use fedimint_logging::{LOG_CLIENT_NET_API, LOG_NET};
27use fedimint_metrics::HistogramExt as _;
28use reqwest::Method;
29use serde_json::Value;
30use tokio::sync::{OnceCell, SetOnce, broadcast, watch};
31use tracing::trace;
32
33use crate::error::ServerError;
34use crate::metrics::{CONNECTION_ATTEMPTS_TOTAL, CONNECTION_DURATION_SECONDS};
35use crate::ws::WebsocketConnector;
36
37const IROH_NEXT_PATH: &str = "/v1";
38
39pub fn iroh_next_endpoint_url(endpoint: &str) -> anyhow::Result<SafeUrl> {
45 let endpoint_id =
46 iroh_next::EndpointId::from_str(endpoint).context("Invalid Iroh 1.0 endpoint ID")?;
47 SafeUrl::parse(&format!("iroh://{endpoint_id}{IROH_NEXT_PATH}"))
48 .context("Invalid Iroh 1.0 endpoint URL")
49}
50
51fn is_iroh_next_endpoint_url(url: &SafeUrl) -> anyhow::Result<bool> {
52 match url.path() {
53 "" | "/" => Ok(false),
54 IROH_NEXT_PATH => Ok(true),
55 path => bail!("Unsupported Iroh API URL path: {path}"),
56 }
57}
58
59fn preserve_iroh_next_marker(original: &SafeUrl, replacement: &SafeUrl) -> SafeUrl {
60 if original.scheme() == "iroh"
63 && original.path() == IROH_NEXT_PATH
64 && replacement.scheme() == "iroh"
65 {
66 let mut replacement = replacement.clone().to_unsafe();
67 replacement.set_path(IROH_NEXT_PATH);
68 replacement.into()
69 } else {
70 replacement.clone()
71 }
72}
73
74pub type ServerResult<T> = Result<T, ServerError>;
75
76type ConnectorInitFn = Arc<
78 dyn Fn() -> Pin<Box<dyn Future<Output = anyhow::Result<DynConnector>> + Send>> + Send + Sync,
79>;
80
81#[derive(Debug, Clone)]
86#[allow(clippy::struct_excessive_bools)] pub struct ConnectorRegistryBuilder {
88 connection_overrides: BTreeMap<SafeUrl, SafeUrl>,
93
94 iroh_enable: bool,
96 iroh_dns: Option<SafeUrl>,
98 iroh_pkarr_dht: bool,
100 iroh_next: bool,
102
103 ws_enable: bool,
105 ws_force_tor: bool,
106
107 http_enable: bool,
109}
110
111impl ConnectorRegistryBuilder {
112 #[allow(clippy::unused_async)] pub async fn bind(self) -> anyhow::Result<ConnectorRegistry> {
114 let iroh_next = self.iroh_next && self.iroh_enable;
115
116 let mut connectors_lazy: BTreeMap<String, (ConnectorInitFn, OnceCell<DynConnector>)> =
118 BTreeMap::new();
119
120 let path_change = Arc::new(watch::channel(0u64).0);
124
125 let builder_ws = self.clone();
127 let ws_connector_init = Arc::new(move || {
128 let builder = builder_ws.clone();
129 Box::pin(async move { builder.build_ws_connector().await })
130 as Pin<Box<dyn Future<Output = anyhow::Result<DynConnector>> + Send>>
131 });
132 connectors_lazy.insert("ws".into(), (ws_connector_init.clone(), OnceCell::new()));
133 connectors_lazy.insert("wss".into(), (ws_connector_init.clone(), OnceCell::new()));
134
135 let builder_iroh = self.clone();
137 let path_change_iroh = path_change.clone();
138 connectors_lazy.insert(
139 "iroh".into(),
140 (
141 Arc::new(move || {
142 let builder = builder_iroh.clone();
143 let path_change = path_change_iroh.clone();
144 Box::pin(async move { builder.build_iroh_connector(path_change).await })
145 as Pin<Box<dyn Future<Output = anyhow::Result<DynConnector>> + Send>>
146 }),
147 OnceCell::new(),
148 ),
149 );
150
151 let builder_http = self.clone();
152 let http_connector_init = Arc::new(move || {
153 let builder = builder_http.clone();
154 Box::pin(async move { builder.build_http_connector() })
155 as Pin<Box<dyn Future<Output = anyhow::Result<DynConnector>> + Send>>
156 });
157
158 connectors_lazy.insert(
159 "http".into(),
160 (http_connector_init.clone(), OnceCell::new()),
161 );
162 connectors_lazy.insert(
163 "https".into(),
164 (http_connector_init.clone(), OnceCell::new()),
165 );
166
167 Ok(ConnectorRegistry {
168 inner: ConnectorRegistryInner {
169 connectors_lazy,
170 connection_overrides: self.connection_overrides,
171 initialized: SetOnce::new(),
172 path_change,
173 iroh_next,
174 }
175 .into(),
176 })
177 }
178
179 pub async fn build_iroh_connector(
180 &self,
181 path_change: Arc<watch::Sender<u64>>,
182 ) -> anyhow::Result<DynConnector> {
183 if !self.iroh_enable {
184 bail!("Iroh connector not enabled");
185 }
186 Ok(Arc::new(
187 iroh::IrohConnector::new(self.iroh_dns.clone(), self.iroh_pkarr_dht, path_change)
188 .await?,
189 ) as DynConnector)
190 }
191
192 pub async fn build_ws_connector(&self) -> anyhow::Result<DynConnector> {
193 if !self.ws_enable {
194 bail!("Websocket connector not enabled");
195 }
196
197 match self.ws_force_tor {
198 #[cfg(all(feature = "tor", not(target_family = "wasm")))]
199 true => {
200 use crate::tor::TorConnector;
201
202 Ok(Arc::new(TorConnector::bootstrap().await?) as DynConnector)
203 }
204
205 false => Ok(Arc::new(WebsocketConnector::new()) as DynConnector),
206 #[allow(unreachable_patterns)]
207 _ => bail!("Tor requested, but not support not compiled in"),
208 }
209 }
210
211 pub fn build_http_connector(&self) -> anyhow::Result<DynConnector> {
212 if !self.http_enable {
213 bail!("Http connector not enabled");
214 }
215
216 Ok(Arc::new(crate::http::HttpConnector::default()) as DynConnector)
217 }
218
219 pub fn iroh_pkarr_dht(self, enable: bool) -> Self {
220 Self {
221 iroh_pkarr_dht: enable,
222 ..self
223 }
224 }
225
226 pub fn iroh_next(self, enable: bool) -> Self {
229 Self {
230 iroh_next: enable,
231 ..self
232 }
233 }
234
235 pub fn ws_force_tor(self, enable: bool) -> Self {
236 Self {
237 ws_force_tor: enable,
238 ..self
239 }
240 }
241
242 pub fn http(self, enable: bool) -> Self {
243 Self {
244 http_enable: enable,
245 ..self
246 }
247 }
248
249 pub fn set_iroh_dns(self, url: SafeUrl) -> Self {
250 Self {
251 iroh_dns: Some(url),
252 ..self
253 }
254 }
255
256 pub fn with_env_var_overrides(mut self) -> anyhow::Result<Self> {
258 for (k, v) in parse_kv_list_from_env::<_, SafeUrl>(FM_WS_API_CONNECT_OVERRIDES_ENV)? {
260 self = self.with_connection_override(k, v);
261 }
262
263 if is_running_in_test_env() {
266 self.iroh_next = false;
267 }
268
269 Ok(Self { ..self })
270 }
271
272 pub fn with_connection_override(
273 mut self,
274 original_url: SafeUrl,
275 replacement_url: SafeUrl,
276 ) -> Self {
277 self.connection_overrides
278 .insert(original_url, replacement_url);
279 self
280 }
281}
282
283struct ConnectorRegistryInner {
285 connectors_lazy: BTreeMap<String, (ConnectorInitFn, OnceCell<DynConnector>)>,
287 connection_overrides: BTreeMap<SafeUrl, SafeUrl>,
289 initialized: tokio::sync::SetOnce<()>,
294 path_change: Arc<watch::Sender<u64>>,
297 iroh_next: bool,
300}
301
302#[derive(Clone)]
315pub struct ConnectorRegistry {
316 inner: Arc<ConnectorRegistryInner>,
317}
318
319impl fmt::Debug for ConnectorRegistry {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 f.debug_struct("ConnectorRegistry")
322 .field("connectors_lazy", &self.inner.connectors_lazy.len())
323 .field("connection_overrides", &self.inner.connection_overrides)
324 .field("iroh_next", &self.inner.iroh_next)
325 .finish()
326 }
327}
328
329impl ConnectorRegistry {
330 pub fn iroh_next_enabled(&self) -> bool {
333 self.inner.iroh_next
334 }
335
336 pub fn build_from_client_defaults() -> ConnectorRegistryBuilder {
341 ConnectorRegistryBuilder {
342 iroh_enable: true,
343 iroh_dns: None,
344 iroh_pkarr_dht: false,
345 iroh_next: true,
346 ws_enable: true,
347 ws_force_tor: false,
348 http_enable: true,
349
350 connection_overrides: BTreeMap::default(),
351 }
352 }
353
354 pub fn build_from_server_defaults() -> ConnectorRegistryBuilder {
357 ConnectorRegistryBuilder {
358 iroh_enable: true,
359 iroh_dns: None,
360 iroh_pkarr_dht: true,
361 iroh_next: true,
362 ws_enable: true,
363 ws_force_tor: false,
364 http_enable: false,
365
366 connection_overrides: BTreeMap::default(),
367 }
368 }
369
370 pub fn build_from_testing_defaults() -> ConnectorRegistryBuilder {
373 ConnectorRegistryBuilder {
374 iroh_enable: true,
375 iroh_dns: None,
376 iroh_pkarr_dht: false,
377 iroh_next: false,
378 ws_enable: true,
379 ws_force_tor: false,
380 http_enable: true,
381
382 connection_overrides: BTreeMap::default(),
383 }
384 }
385
386 pub fn build_from_client_env() -> anyhow::Result<ConnectorRegistryBuilder> {
389 let builder = Self::build_from_client_defaults().with_env_var_overrides()?;
390 Ok(builder)
391 }
392
393 pub fn build_from_server_env() -> anyhow::Result<ConnectorRegistryBuilder> {
396 let builder = Self::build_from_server_defaults().with_env_var_overrides()?;
397 Ok(builder)
398 }
399
400 pub fn build_from_testing_env() -> anyhow::Result<ConnectorRegistryBuilder> {
403 let builder = Self::build_from_testing_defaults().with_env_var_overrides()?;
404 Ok(builder)
405 }
406
407 pub async fn wait_for_initialized_connections(&self) {
409 self.inner.initialized.wait().await;
410 }
411
412 pub async fn connect_guardian(
417 &self,
418 url: &SafeUrl,
419 api_secret: Option<&str>,
420 ) -> ServerResult<DynGuaridianConnection> {
421 trace!(
422 target: LOG_NET,
423 %url,
424 "Connection requested to guardian"
425 );
426 let _ = self.inner.initialized.set(());
427
428 let replacement = self
429 .inner
430 .connection_overrides
431 .get(url)
432 .map(|replacement| preserve_iroh_next_marker(url, replacement));
433 let url = match replacement.as_ref() {
434 Some(replacement) => {
435 trace!(
436 target: LOG_NET,
437 original_url = %url,
438 replacement_url = %replacement,
439 "Using a connectivity override for connection"
440 );
441
442 replacement
443 }
444 None => url,
445 };
446
447 let scheme = url.scheme().to_string();
448
449 let Some(connector_lazy) = self.inner.connectors_lazy.get(&scheme) else {
450 return Err(ServerError::InvalidEndpoint(anyhow!(
451 "Unsupported scheme: {}; missing endpoint handler",
452 url.scheme()
453 )));
454 };
455
456 let init_fn = connector_lazy.0.clone();
458
459 let timer = CONNECTION_DURATION_SECONDS
460 .with_label_values(&[&scheme])
461 .start_timer_ext();
462
463 let result = connector_lazy
464 .1
465 .get_or_try_init(|| async move { init_fn().await })
466 .await
467 .map_err(|e| {
468 ServerError::Transport(anyhow!(
469 "Connector failed to initialize: {}",
470 e.fmt_compact_anyhow()
471 ))
472 })?
473 .connect_guardian(url, api_secret)
474 .await;
475
476 timer.observe_duration();
477
478 let result_label = if result.is_ok() { "success" } else { "error" }.to_string();
479 CONNECTION_ATTEMPTS_TOTAL
480 .with_label_values(&[&scheme, &result_label])
481 .inc();
482
483 let conn = result.inspect_err(|err| {
484 trace!(
485 target: LOG_NET,
486 %url,
487 err = %err.fmt_compact(),
488 "Connection failed"
489 );
490 })?;
491
492 trace!(
493 target: LOG_NET,
494 %url,
495 "Connection returned"
496 );
497 Ok(conn)
498 }
499
500 pub async fn connect_gateway(&self, url: &SafeUrl) -> anyhow::Result<DynGatewayConnection> {
505 trace!(
506 target: LOG_NET,
507 %url,
508 "Connection requested to gateway"
509 );
510 let _ = self.inner.initialized.set(());
511
512 let url = match self.inner.connection_overrides.get(url) {
513 Some(replacement) => {
514 trace!(
515 target: LOG_NET,
516 original_url = %url,
517 replacement_url = %replacement,
518 "Using a connectivity override for connection"
519 );
520
521 replacement
522 }
523 None => url,
524 };
525
526 let scheme = url.scheme().to_string();
527
528 let Some(connector_lazy) = self.inner.connectors_lazy.get(&scheme) else {
529 return Err(anyhow!(
530 "Unsupported scheme: {}; missing endpoint handler",
531 url.scheme()
532 ));
533 };
534
535 let init_fn = connector_lazy.0.clone();
537
538 let timer = CONNECTION_DURATION_SECONDS
539 .with_label_values(&[&scheme])
540 .start_timer_ext();
541
542 let result = connector_lazy
543 .1
544 .get_or_try_init(|| async move { init_fn().await })
545 .await
546 .map_err(|e| {
547 ServerError::Transport(anyhow!(
548 "Connector failed to initialize: {}",
549 e.fmt_compact_anyhow()
550 ))
551 })?
552 .connect_gateway(url)
553 .await;
554
555 timer.observe_duration();
556
557 let result_label = if result.is_ok() { "success" } else { "error" }.to_string();
558 CONNECTION_ATTEMPTS_TOTAL
559 .with_label_values(&[&scheme, &result_label])
560 .inc();
561
562 result
563 }
564
565 pub fn connectivity(&self, url: &SafeUrl) -> Connectivity {
571 let url = match self.inner.connection_overrides.get(url) {
572 Some(replacement) => replacement,
573 None => url,
574 };
575
576 let Some((_, connector_cell)) = self.inner.connectors_lazy.get(url.scheme()) else {
577 return Connectivity::Unknown;
578 };
579
580 match connector_cell.get() {
581 Some(connector) => connector.connectivity(url),
582 None => Connectivity::Unknown,
583 }
584 }
585
586 pub async fn iroh_peer_info(
589 &self,
590 url: &SafeUrl,
591 path_timeout: Duration,
592 ) -> ServerResult<Option<IrohPeerInfo>> {
593 let url = match self.inner.connection_overrides.get(url) {
594 Some(replacement) => replacement,
595 None => url,
596 };
597
598 let Some((init_fn, connector_cell)) = self.inner.connectors_lazy.get(url.scheme()) else {
599 return Ok(None);
600 };
601
602 let init_fn = init_fn.clone();
603 connector_cell
604 .get_or_try_init(|| async move { init_fn().await })
605 .await
606 .map_err(|e| {
607 ServerError::Transport(anyhow!(
608 "Connector failed to initialize: {}",
609 e.fmt_compact_anyhow()
610 ))
611 })?
612 .iroh_peer_info(url, path_timeout)
613 .await
614 }
615
616 pub fn connectivity_change_notifier(&self) -> watch::Receiver<u64> {
625 self.inner.path_change.subscribe()
626 }
627}
628pub type DynConnector = Arc<dyn Connector>;
629
630#[async_trait]
631pub trait Connector: Send + Sync + 'static + Debug {
632 async fn connect_guardian(
633 &self,
634 url: &SafeUrl,
635 api_secret: Option<&str>,
636 ) -> ServerResult<DynGuaridianConnection>;
637
638 async fn connect_gateway(&self, url: &SafeUrl) -> anyhow::Result<DynGatewayConnection>;
639
640 fn connectivity(&self, url: &SafeUrl) -> Connectivity;
642
643 async fn iroh_peer_info(
645 &self,
646 _url: &SafeUrl,
647 _path_timeout: Duration,
648 ) -> ServerResult<Option<IrohPeerInfo>> {
649 Ok(None)
650 }
651}
652
653#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
662pub enum Connectivity {
663 Direct,
664 Relay,
665 Mixed,
666 Tor,
667 Unknown,
668}
669
670#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
677pub enum PeerStatus {
678 Disconnected,
679 Connected(Connectivity),
680}
681
682#[derive(Debug, Clone, PartialEq, Eq)]
684pub struct IrohPeerInfo {
685 pub node_id: String,
686 pub connectivity: Connectivity,
687 pub direct_addr: Option<SocketAddr>,
688 pub known_direct_addrs: Vec<SocketAddr>,
689 pub relay_url: Option<String>,
690}
691
692#[apply(async_trait_maybe_send!)]
695pub trait IConnection: Debug + Send + Sync + 'static {
696 fn is_connected(&self) -> bool;
697
698 async fn await_disconnection(&self);
699}
700
701pub type DynGuaridianConnection = Arc<dyn IGuardianConnection>;
703
704#[async_trait]
706pub trait IGuardianConnection: IConnection + Debug + Send + Sync + 'static {
707 async fn request(&self, method: ApiMethod, request: ApiRequestErased) -> ServerResult<Value>;
708
709 fn into_dyn(self) -> DynGuaridianConnection
710 where
711 Self: Sized,
712 {
713 Arc::new(self)
714 }
715}
716
717pub type DynGatewayConnection = Arc<dyn IGatewayConnection>;
719
720#[apply(async_trait_maybe_send!)]
722pub trait IGatewayConnection: IConnection + Debug + Send + Sync + 'static {
723 async fn request(
724 &self,
725 password: Option<String>,
726 method: Method,
727 route: &str,
728 payload: Option<Value>,
729 ) -> ServerResult<Value>;
730
731 fn into_dyn(self) -> DynGatewayConnection
732 where
733 Self: Sized,
734 {
735 Arc::new(self)
736 }
737}
738
739#[derive(Debug)]
740pub struct ConnectionPool<T: IConnection + ?Sized> {
741 connectors: ConnectorRegistry,
743
744 active_connections: watch::Sender<BTreeSet<SafeUrl>>,
745
746 #[allow(clippy::type_complexity)]
752 connections: Arc<tokio::sync::Mutex<HashMap<SafeUrl, Arc<ConnectionState<T>>>>>,
753}
754
755impl<T: IConnection + ?Sized> Clone for ConnectionPool<T> {
756 fn clone(&self) -> Self {
757 Self {
758 connectors: self.connectors.clone(),
759 connections: self.connections.clone(),
760 active_connections: self.active_connections.clone(),
761 }
762 }
763}
764
765impl<T: IConnection + ?Sized> ConnectionPool<T> {
766 pub fn new(connectors: ConnectorRegistry) -> Self {
767 Self {
768 connectors,
769 connections: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
770 active_connections: watch::channel(BTreeSet::new()).0,
771 }
772 }
773
774 async fn get_or_init_pool_entry(&self, url: &SafeUrl) -> Arc<ConnectionState<T>> {
775 let mut pool_locked = self.connections.lock().await;
776 pool_locked
777 .entry(url.to_owned())
778 .and_modify(|entry_arc| {
779 if let Some(existing_conn) = entry_arc.connection.get()
785 && !existing_conn.is_connected()
786 {
787 trace!(
788 target: LOG_CLIENT_NET_API,
789 %url,
790 "Existing connection is disconnected, removing from pool"
791 );
792 self.active_connections.send_modify(|v| {
793 v.remove(url);
794 });
795 *entry_arc = Arc::new(ConnectionState::new_reconnecting());
796 }
797 })
798 .or_insert_with(|| Arc::new(ConnectionState::new_initial()))
799 .clone()
800 }
801
802 pub async fn get_or_create_connection<F, Fut>(
803 &self,
804 url: &SafeUrl,
805 api_secret: Option<&str>,
806 create_connection: F,
807 ) -> ServerResult<Arc<T>>
808 where
809 F: Fn(SafeUrl, Option<String>, ConnectorRegistry) -> Fut + Clone + Send + Sync + 'static,
810 Fut: Future<Output = ServerResult<Arc<T>>> + Send + 'static,
811 {
812 let pool_entry_arc = self.get_or_init_pool_entry(url).await;
813
814 let leader_tx = loop {
815 let mut leader_rx = {
816 let mut chan_locked = pool_entry_arc
817 .merge_connection_attempts_chan
818 .lock()
819 .expect("locking error");
820
821 if chan_locked.is_closed() {
822 let (leader_tx, leader_rx) = broadcast::channel(1);
823 *chan_locked = leader_rx;
824 break leader_tx;
827 }
828
829 chan_locked.resubscribe()
831 };
832
833 if let Ok(res) = leader_rx.recv().await {
834 match res {
835 Ok(o) => return Ok(o),
836 Err(err) => {
837 return Err(ServerError::Connection(anyhow::format_err!("{}", err)));
838 }
839 }
840 }
841 };
842
843 let conn = pool_entry_arc
844 .connection
845 .get_or_try_init(|| async {
846 let retry_delay = pool_entry_arc.pre_reconnect_delay();
847 fedimint_core::runtime::sleep(retry_delay).await;
848
849 trace!(target: LOG_CLIENT_NET_API, %url, "Attempting to create a new connection");
850 let res = create_connection(
851 url.clone(),
852 api_secret.map(std::string::ToString::to_string),
853 self.connectors.clone(),
854 )
855 .await;
856
857 let _ = leader_tx.send(
862 res.as_ref()
863 .map(|o| o.clone())
864 .map_err(|err| err.to_string()),
865 );
866
867 let conn = res?;
868
869 self.active_connections.send_modify(|v| {
870 v.insert(url.clone());
871 });
872
873 fedimint_core::runtime::spawn("connection disconnect watch", {
874 let conn = conn.clone();
875 let s = self.clone();
876 let url = url.clone();
877 async move {
878 conn.await_disconnection().await;
880 s.get_or_init_pool_entry(&url).await;
886 }
887 });
888
889 Ok(conn)
890 })
891 .await?;
892
893 trace!(target: LOG_CLIENT_NET_API, %url, "Connection ready");
894 Ok(conn.clone())
895 }
896 pub fn get_active_connection_receiver(&self) -> watch::Receiver<BTreeSet<SafeUrl>> {
898 self.active_connections.subscribe()
899 }
900
901 pub async fn wait_for_initialized_connections(&self) {
902 self.connectors.wait_for_initialized_connections().await
903 }
904
905 pub fn connectivity(&self, url: &SafeUrl) -> Connectivity {
907 self.connectors.connectivity(url)
908 }
909
910 pub fn connectivity_change_notifier(&self) -> watch::Receiver<u64> {
913 self.connectors.connectivity_change_notifier()
914 }
915}
916
917#[derive(Debug)]
920struct ConnectionStateInner {
921 fresh: bool,
922 backoff: FibonacciBackoff,
923}
924
925#[derive(Debug)]
926pub struct ConnectionState<T: ?Sized> {
927 pub connection: tokio::sync::OnceCell<Arc<T>>,
929
930 merge_connection_attempts_chan:
934 std::sync::Mutex<broadcast::Receiver<std::result::Result<Arc<T>, String>>>,
935
936 inner: std::sync::Mutex<ConnectionStateInner>,
940}
941
942impl<T: ?Sized> ConnectionState<T> {
943 pub fn new_initial() -> Self {
945 Self {
946 connection: OnceCell::new(),
947 inner: std::sync::Mutex::new(ConnectionStateInner {
948 fresh: true,
949 backoff: custom_backoff(
950 Duration::from_millis(5),
952 Duration::from_secs(30),
953 None,
954 ),
955 }),
956 merge_connection_attempts_chan: std::sync::Mutex::new(broadcast::channel(1).1),
957 }
958 }
959
960 pub fn new_reconnecting() -> Self {
963 Self {
964 connection: OnceCell::new(),
965 inner: std::sync::Mutex::new(ConnectionStateInner {
966 fresh: false,
968 backoff: custom_backoff(
969 Duration::from_millis(500),
971 Duration::from_secs(30),
972 None,
973 ),
974 }),
975 merge_connection_attempts_chan: std::sync::Mutex::new(broadcast::channel(1).1),
976 }
977 }
978
979 pub fn pre_reconnect_delay(&self) -> Duration {
982 let mut backoff_locked = self.inner.lock().expect("Locking failed");
983 let fresh = backoff_locked.fresh;
984
985 backoff_locked.fresh = false;
986
987 if fresh {
988 Duration::default()
989 } else {
990 backoff_locked.backoff.next().expect("Keeps retrying")
991 }
992 }
993}