1use std::collections::BTreeMap;
4use std::collections::BTreeSet;
5use std::future::Future;
6use std::sync::Arc;
7use std::time::Duration;
8
9use futures::future::FutureExt;
10use futures::pin_mut;
11use futures::select;
12use rings_transport::core::transport::WebrtcConnectionState;
13
14pub use self::storage_repair::StorageRepairOutcome;
15use crate::dht::successor::SuccessorReader;
16use crate::dht::types::CorrectChord;
17use crate::dht::Chord;
18use crate::dht::Did;
19use crate::dht::PeerRing;
20use crate::dht::PeerRingAction;
21use crate::dht::PeerRingRemoteAction;
22use crate::dht::TopoInfo;
23use crate::error::Error;
24use crate::error::Result;
25use crate::message::FindSuccessorReportHandler;
26use crate::message::FindSuccessorSend;
27use crate::message::FindSuccessorThen;
28use crate::message::Message;
29use crate::message::MessagePayload;
30use crate::message::NotifyPredecessorSend;
31use crate::message::PayloadSender;
32use crate::message::PeerLivenessProbe;
33use crate::message::QueryForTopoInfoSend;
34use crate::swarm::transport::PendingConnectionAttempt;
35use crate::swarm::transport::SwarmTransport;
36use crate::swarm::transport::TransportReadiness;
37use crate::swarm::transport::PEER_LIVENESS_IDLE_MS;
38use crate::swarm::transport::TRACKED_PAYLOAD_COMPLETION_BOUND;
39use crate::utils::get_epoch_ms_i64;
40use crate::utils::sleep;
41use crate::utils::Instant;
42
43const STABILIZATION_STEP_TIMEOUT: Duration =
44 TRACKED_PAYLOAD_COMPLETION_BOUND.saturating_add(Duration::from_secs(1));
45const STABILIZATION_STOP_POLL_INTERVAL: Duration = Duration::from_millis(50);
46const DISCONNECTED_CONNECTION_GRACE_MS: i64 = 30_000;
47pub(crate) const STORAGE_REPAIR_MAX_DELIVERIES_PER_STEP: usize = 1;
51pub(crate) const STORAGE_REPAIR_FRESH_CONNECTION_GRACE_MS: i64 = 30_000;
52
53#[derive(Clone, Copy, Debug)]
54enum TopologyPeerRemovalReason {
55 NoAdmittedTransport,
56 MissingTransportObject,
57 SendTerminal,
58 TerminalTransport(WebrtcConnectionState),
59 DataChannelNotOpen(WebrtcConnectionState),
60 DisconnectedGraceElapsed {
61 disconnected_for_ms: i64,
62 grace_ms: i64,
63 },
64 DisconnectedSuccessorFailover {
65 disconnected_for_ms: i64,
66 },
67 DisconnectedTopologyPrune {
68 disconnected_for_ms: i64,
69 },
70 UnansweredLivenessProbe {
71 unanswered_for_ms: i64,
72 timeout_ms: i64,
73 },
74}
75
76#[derive(Clone, Copy)]
77struct AdmittedPeerState {
78 attempt: PendingConnectionAttempt,
79 readiness: Option<TransportReadiness>,
80 send_terminal: bool,
81}
82
83#[derive(Clone, Copy)]
84struct TopologyPeerRemoval {
85 attempt: Option<PendingConnectionAttempt>,
86 reason: TopologyPeerRemovalReason,
87}
88
89impl TopologyPeerRemovalReason {
90 const fn as_str(self) -> &'static str {
91 match self {
92 Self::NoAdmittedTransport => "no_admitted_transport",
93 Self::MissingTransportObject => "missing_transport_object",
94 Self::SendTerminal => "send_terminal",
95 Self::TerminalTransport(_) => "terminal_transport",
96 Self::DataChannelNotOpen(_) => "data_channel_not_open",
97 Self::DisconnectedGraceElapsed { .. } => "disconnected_grace_elapsed",
98 Self::DisconnectedSuccessorFailover { .. } => "disconnected_successor_failover",
99 Self::DisconnectedTopologyPrune { .. } => "disconnected_topology_prune",
100 Self::UnansweredLivenessProbe { .. } => "unanswered_liveness_probe",
101 }
102 }
103
104 const fn transport_state(self) -> Option<WebrtcConnectionState> {
105 match self {
106 Self::TerminalTransport(state) | Self::DataChannelNotOpen(state) => Some(state),
107 _ => None,
108 }
109 }
110
111 const fn disconnected_for_ms(self) -> Option<i64> {
112 match self {
113 Self::DisconnectedGraceElapsed {
114 disconnected_for_ms,
115 ..
116 }
117 | Self::DisconnectedSuccessorFailover {
118 disconnected_for_ms,
119 }
120 | Self::DisconnectedTopologyPrune {
121 disconnected_for_ms,
122 } => Some(disconnected_for_ms),
123 _ => None,
124 }
125 }
126
127 const fn disconnected_grace_ms(self) -> Option<i64> {
128 match self {
129 Self::DisconnectedGraceElapsed { grace_ms, .. } => Some(grace_ms),
130 _ => None,
131 }
132 }
133
134 const fn liveness_unanswered_for_ms(self) -> Option<i64> {
135 match self {
136 Self::UnansweredLivenessProbe {
137 unanswered_for_ms, ..
138 } => Some(unanswered_for_ms),
139 _ => None,
140 }
141 }
142
143 const fn liveness_timeout_ms(self) -> Option<i64> {
144 match self {
145 Self::UnansweredLivenessProbe { timeout_ms, .. } => Some(timeout_ms),
146 _ => None,
147 }
148 }
149
150 const fn should_disconnect_transport(self) -> bool {
151 !matches!(
152 self,
153 Self::NoAdmittedTransport | Self::DisconnectedTopologyPrune { .. }
154 )
155 }
156}
157
158enum StepDeadline<T> {
159 Completed(Result<T>),
160 TimedOut,
161}
162
163async fn await_step_deadline<F, T>(future: F, timeout: Duration) -> StepDeadline<T>
164where F: Future<Output = Result<T>> {
165 let future = future.fuse();
166 let timer = sleep(timeout).fuse();
167 pin_mut!(future, timer);
168 select! {
169 result = future => StepDeadline::Completed(result),
170 _ = timer => StepDeadline::TimedOut,
171 }
172}
173
174#[derive(Clone)]
176pub struct Stabilizer {
177 transport: Arc<SwarmTransport>,
178 dht: Arc<PeerRing>,
179}
180
181impl Stabilizer {
182 pub fn new(transport: Arc<SwarmTransport>) -> Self {
184 let dht = transport.dht.clone();
185 Self { transport, dht }
186 }
187
188 pub async fn stabilize(&self) -> Result<()> {
190 self.stabilize_with_step_timeout(STABILIZATION_STEP_TIMEOUT)
191 .await
192 }
193
194 pub(crate) async fn stabilize_with_step_timeout(&self, timeout: Duration) -> Result<()> {
195 self.stabilize_topology_with_step_timeout(timeout).await;
196 self.transport.claim_storage_repair();
197 let repair_outcome = self
198 .run_step("repair_storage", timeout, self.repair_storage())
199 .await;
200 if !matches!(repair_outcome, Some(StorageRepairOutcome::Complete)) {
201 self.transport.request_storage_repair();
202 }
203 Ok(())
204 }
205
206 async fn stabilize_topology_with_step_timeout(&self, timeout: Duration) {
207 self.run_step(
208 "clean_unavailable_connections",
209 timeout,
210 self.clean_unavailable_connections(),
211 )
212 .await;
213 self.run_step("notify_predecessor", timeout, self.notify_predecessor())
214 .await;
215 self.run_step("fix_fingers", timeout, self.fix_fingers())
216 .await;
217 self.run_step("probe_peer_liveness", timeout, self.probe_peer_liveness())
218 .await;
219 self.run_step("correct_stabilize", timeout, self.correct_stabilize())
222 .await;
223 }
224
225 async fn run_step<F, T>(&self, step: &'static str, timeout: Duration, future: F) -> Option<T>
226 where F: Future<Output = Result<T>> {
227 let started_at = Instant::now();
228 tracing::debug!(
229 target: "rings_core::dht::stabilization",
230 local = %self.dht.did,
231 step,
232 timeout_ms = timeout.as_millis(),
233 "STABILIZATION step start"
234 );
235
236 let result = match await_step_deadline(future, timeout).await {
237 StepDeadline::Completed(result) => result,
238 StepDeadline::TimedOut => {
239 self.log_step_timeout(step, timeout, elapsed_since(started_at));
240 return None;
241 }
242 };
243
244 match result {
245 Ok(output) => {
246 let elapsed_ms = elapsed_since(started_at);
247 if u128::try_from(elapsed_ms).unwrap_or(0) > timeout.as_millis() {
248 self.log_step_timeout(step, timeout, elapsed_ms);
249 }
250 tracing::debug!(
251 target: "rings_core::dht::stabilization",
252 local = %self.dht.did,
253 step,
254 elapsed_ms,
255 "STABILIZATION step end"
256 );
257 Some(output)
258 }
259 Err(e) => {
260 tracing::error!(
261 target: "rings_core::dht::stabilization",
262 local = %self.dht.did,
263 step,
264 error = ?e,
265 "STABILIZATION step failed"
266 );
267 None
268 }
269 }
270 }
271
272 fn log_step_timeout(&self, step: &'static str, timeout: Duration, elapsed_ms: i64) {
273 let topology = TopoInfo::try_from(self.dht.as_ref()).ok();
274 let mut connections: Vec<(Did, WebrtcConnectionState)> = self
275 .transport
276 .admitted_connections()
277 .into_iter()
278 .map(|(attempt, conn)| (attempt.peer(), conn.webrtc_connection_state()))
279 .collect();
280 connections.sort_by_key(|(did, _)| *did);
281
282 tracing::warn!(
283 target: "rings_core::dht::stabilization",
284 local = %self.dht.did,
285 step,
286 timeout_ms = timeout.as_millis(),
287 elapsed_ms,
288 reason = "stabilization_step_overran_deadline",
289 topology = ?topology,
290 connections = ?connections,
291 "STABILIZATION step exceeded timeout"
292 );
293 }
294
295 pub async fn clean_unavailable_connections(&self) -> Result<()> {
321 self.transport.expire_pending_connections().await?;
322 let admitted_states = self.admitted_connection_states()?;
323 let topology_peers = self.dht_topology_peers()?;
324 let mut candidates = topology_peers;
325 candidates.extend(self.transport.admitted_connection_ids());
326 let now_ms = get_epoch_ms_i64();
327
328 for did in candidates {
329 if let Some(removal) = self
330 .topology_peer_removal_reason(did, admitted_states.get(&did).copied(), now_ms)
331 .await?
332 {
333 self.remove_unavailable_peer(did, removal).await?;
334 }
335 }
336
337 Ok(())
338 }
339
340 fn admitted_connection_states(&self) -> Result<BTreeMap<Did, AdmittedPeerState>> {
341 self.transport
342 .admitted_connection_snapshots()?
343 .into_iter()
344 .map(|(attempt, connection)| {
345 let readiness = connection.as_ref().map(|connection| connection.readiness());
346 let send_terminal = self.transport.is_send_terminal_attempt(attempt)?;
347 Ok((attempt.peer(), AdmittedPeerState {
348 attempt,
349 readiness,
350 send_terminal,
351 }))
352 })
353 .collect()
354 }
355
356 fn dht_topology_peers(&self) -> Result<BTreeSet<Did>> {
357 let topology = self.dht.topology_state()?;
358 let mut peers = BTreeSet::new();
359
360 for did in topology.successors {
361 if did != self.dht.did {
362 peers.insert(did);
363 }
364 }
365
366 if let Some(predecessor) = topology.predecessor {
367 if predecessor != self.dht.did {
368 peers.insert(predecessor);
369 }
370 }
371
372 for did in topology.fingers.into_iter().flatten() {
373 if did != self.dht.did {
374 peers.insert(did);
375 }
376 }
377
378 Ok(peers)
379 }
380
381 async fn topology_peer_removal_reason(
382 &self,
383 did: Did,
384 admitted: Option<AdmittedPeerState>,
385 now_ms: i64,
386 ) -> Result<Option<TopologyPeerRemoval>> {
387 let Some(admitted) = admitted else {
388 return Ok(Some(TopologyPeerRemoval {
389 attempt: None,
390 reason: TopologyPeerRemovalReason::NoAdmittedTransport,
391 }));
392 };
393 let removal = |reason| {
394 Some(TopologyPeerRemoval {
395 attempt: Some(admitted.attempt),
396 reason,
397 })
398 };
399 if admitted.send_terminal {
400 return Ok(removal(TopologyPeerRemovalReason::SendTerminal));
401 }
402 let Some(readiness) = admitted.readiness else {
403 return Ok(removal(TopologyPeerRemovalReason::MissingTransportObject));
404 };
405 let state = readiness.state();
406
407 if readiness.is_terminal() {
408 return Ok(removal(TopologyPeerRemovalReason::TerminalTransport(state)));
409 }
410
411 if matches!(state, WebrtcConnectionState::Connected) && !readiness.data_channel_open() {
412 return Ok(removal(TopologyPeerRemovalReason::DataChannelNotOpen(
413 state,
414 )));
415 }
416
417 if let Some(expiry) = self
418 .transport
419 .peer_liveness_expiry(admitted.attempt, now_ms)?
420 {
421 return Ok(removal(
422 TopologyPeerRemovalReason::UnansweredLivenessProbe {
423 unanswered_for_ms: expiry.unanswered_for_ms,
424 timeout_ms: expiry.timeout_ms,
425 },
426 ));
427 }
428
429 if matches!(state, WebrtcConnectionState::Disconnected) {
430 if let Some(reason) = self
431 .disconnected_peer_removal_reason(did, admitted, now_ms)
432 .await?
433 {
434 return Ok(removal(reason));
435 }
436 } else {
437 self.transport.clear_peer_disconnected(admitted.attempt);
438 }
439
440 Ok(None)
441 }
442
443 async fn disconnected_peer_removal_reason(
444 &self,
445 did: Did,
446 admitted: AdmittedPeerState,
447 now_ms: i64,
448 ) -> Result<Option<TopologyPeerRemovalReason>> {
449 let disconnected_for_ms = if let Some(disconnected_since_ms) = self
450 .transport
451 .peer_disconnected_since_attempt_ms(admitted.attempt)
452 {
453 now_ms.saturating_sub(disconnected_since_ms)
454 } else {
455 self.transport
456 .record_peer_disconnected(admitted.attempt)
457 .await;
458 tracing::warn!(
459 target: "rings_core::dht::stabilization",
460 local = %self.dht.did,
461 peer = %did,
462 "STABILIZATION clean_unavailable observed disconnected peer without prior callback"
463 );
464 0
465 };
466 if self.transport.live_successor_fallback(did)?.is_some() {
467 return Ok(Some(
468 TopologyPeerRemovalReason::DisconnectedSuccessorFailover {
469 disconnected_for_ms,
470 },
471 ));
472 }
473 if self.disconnected_topology_prune_candidate(did)? {
474 return Ok(Some(TopologyPeerRemovalReason::DisconnectedTopologyPrune {
475 disconnected_for_ms,
476 }));
477 }
478 Ok(
479 (disconnected_for_ms >= DISCONNECTED_CONNECTION_GRACE_MS).then_some(
480 TopologyPeerRemovalReason::DisconnectedGraceElapsed {
481 disconnected_for_ms,
482 grace_ms: DISCONNECTED_CONNECTION_GRACE_MS,
483 },
484 ),
485 )
486 }
487
488 fn disconnected_topology_prune_candidate(&self, peer: Did) -> Result<bool> {
489 let topology = self.dht.topology_state()?;
490 if topology.successors.first().copied() == Some(peer) {
491 return Ok(false);
492 }
493 if topology
494 .successors
495 .iter()
496 .skip(1)
497 .any(|successor| *successor == peer)
498 {
499 return Ok(true);
500 }
501
502 if topology.predecessor == Some(peer) {
503 return Ok(true);
504 }
505
506 Ok(topology.fingers.contains(&Some(peer)))
507 }
508
509 async fn remove_unavailable_peer(&self, did: Did, removal: TopologyPeerRemoval) -> Result<()> {
510 let reason = removal.reason;
511 let should_repair = self
512 .dht
513 .peer_may_share_storage_responsibility(did, self.transport.storage_redundancy())
514 .await?;
515 let fallback_snapshot = self.transport.live_successor_fallback(did)?;
516 tracing::info!(
517 target: "rings_core::dht::stabilization",
518 local = %self.dht.did,
519 peer = %did,
520 reason = reason.as_str(),
521 state = ?reason.transport_state(),
522 disconnected_for_ms = ?reason.disconnected_for_ms(),
523 disconnected_grace_ms = ?reason.disconnected_grace_ms(),
524 fallback = ?fallback_snapshot,
525 liveness_unanswered_for_ms = ?reason.liveness_unanswered_for_ms(),
526 liveness_timeout_ms = ?reason.liveness_timeout_ms(),
527 should_repair,
528 "STABILIZATION clean_unavailable selected peer"
529 );
530
531 if reason.should_disconnect_transport() {
532 tracing::debug!(
533 target: "rings_core::dht::stabilization",
534 local = %self.dht.did,
535 peer = %did,
536 reason = reason.as_str(),
537 "STABILIZATION clean_unavailable disconnect start"
538 );
539 let outcome = match removal.attempt {
540 Some(attempt) => self.transport.disconnect_unavailable(attempt).await?,
541 None => None,
542 };
543 let Some(outcome) = outcome else {
544 tracing::debug!(
545 target: "rings_core::dht::stabilization",
546 local = %self.dht.did,
547 peer = %did,
548 reason = reason.as_str(),
549 "STABILIZATION clean_unavailable skipped superseded evidence"
550 );
551 return Ok(());
552 };
553 let fallback = outcome.fallback();
554 tracing::debug!(
555 target: "rings_core::dht::stabilization",
556 local = %self.dht.did,
557 peer = %did,
558 reason = reason.as_str(),
559 fallback = ?fallback,
560 "STABILIZATION clean_unavailable disconnect complete"
561 );
562 } else {
563 tracing::debug!(
564 target: "rings_core::dht::stabilization",
565 local = %self.dht.did,
566 peer = %did,
567 reason = reason.as_str(),
568 "STABILIZATION clean_unavailable topology remove start"
569 );
570 let Some(outcome) = self
571 .transport
572 .remove_unavailable_topology(did, removal.attempt)?
573 else {
574 tracing::debug!(
575 target: "rings_core::dht::stabilization",
576 local = %self.dht.did,
577 peer = %did,
578 reason = reason.as_str(),
579 "STABILIZATION clean_unavailable skipped superseded topology evidence"
580 );
581 return Ok(());
582 };
583 let fallback = outcome.fallback();
584 tracing::debug!(
585 target: "rings_core::dht::stabilization",
586 local = %self.dht.did,
587 peer = %did,
588 reason = reason.as_str(),
589 fallback = ?fallback,
590 "STABILIZATION clean_unavailable topology remove complete"
591 );
592 }
593
594 if should_repair {
595 self.transport.request_storage_repair();
596 tracing::debug!(
597 target: "rings_core::dht::stabilization",
598 local = %self.dht.did,
599 peer = %did,
600 reason = reason.as_str(),
601 "STABILIZATION clean_unavailable deferred storage repair to its scheduled phase"
602 );
603 }
604
605 Ok(())
606 }
607
608 async fn probe_peer_liveness(&self) -> Result<()> {
609 let now_ms = get_epoch_ms_i64();
610 let candidates = self.transport.liveness_probe_candidates(now_ms)?;
611 for attempt in candidates {
612 let peer = attempt.peer();
613 let state = self
614 .transport
615 .get_connection(peer)
616 .map(|conn| conn.webrtc_connection_state());
617 let msg = Message::PeerLivenessProbe(PeerLivenessProbe { sent_at_ms: now_ms });
618 tracing::debug!(
619 target: "rings_core::dht::stabilization",
620 local = %self.dht.did,
621 peer = %peer,
622 state = ?state,
623 idle_ms = PEER_LIVENESS_IDLE_MS,
624 "STABILIZATION peer liveness probe send start"
625 );
626 match self.transport.send_direct_message(msg, peer).await {
627 Ok(tx_id) => {
628 self.transport
629 .record_peer_liveness_probe_sent(attempt, now_ms)?;
630 tracing::debug!(
631 target: "rings_core::dht::stabilization",
632 local = %self.dht.did,
633 peer = %peer,
634 tx_id = %tx_id,
635 "STABILIZATION peer liveness probe send complete"
636 );
637 }
638 Err(error) => {
639 tracing::warn!(
640 target: "rings_core::dht::stabilization",
641 local = %self.dht.did,
642 peer = %peer,
643 state = ?state,
644 error = ?error,
645 records_peer_failure = error.records_peer_send_failure(),
646 "STABILIZATION peer liveness probe send failed"
647 );
648 }
649 }
650 }
651 Ok(())
652 }
653
654 #[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
655 pub(crate) async fn probe_peer_liveness_for_simulation(&self) -> Result<()> {
656 self.probe_peer_liveness().await
657 }
658
659 pub async fn notify_predecessor(&self) -> Result<()> {
661 let (successor_min, successor_list) = {
662 let successor = self.dht.successors();
663 (successor.min()?, successor.list()?)
664 };
665
666 let msg = Message::NotifyPredecessorSend(NotifyPredecessorSend { did: self.dht.did });
667 if self.dht.did != successor_min {
668 for s in successor_list {
669 let payload =
670 MessagePayload::new_send(msg.clone(), self.transport.session_sk(), s, s)?;
671 let tx_id = payload.transaction.tx_id;
672 let target_state = self
673 .transport
674 .get_connection(s)
675 .map(|conn| conn.webrtc_connection_state());
676 tracing::debug!(
677 target: "rings_core::dht::stabilization",
678 local = %self.dht.did,
679 successor = %s,
680 tx_id = %tx_id,
681 target_state = ?target_state,
682 "STABILIZATION notify_predecessor send start"
683 );
684 if let Err(e) = self.transport.send_payload(payload).await {
685 tracing::error!(
686 target: "rings_core::dht::stabilization",
687 local = %self.dht.did,
688 successor = %s,
689 tx_id = %tx_id,
690 target_state = ?target_state,
691 error = ?e,
692 "STABILIZATION notify_predecessor send failed"
693 );
694 return Err(e);
695 }
696 tracing::debug!(
697 target: "rings_core::dht::stabilization",
698 local = %self.dht.did,
699 successor = %s,
700 tx_id = %tx_id,
701 "STABILIZATION notify_predecessor send complete"
702 );
703 }
704 Ok(())
705 } else {
706 tracing::debug!(
707 target: "rings_core::dht::stabilization",
708 local = %self.dht.did,
709 successor = %successor_min,
710 "STABILIZATION notify_predecessor skip local successor"
711 );
712 Ok(())
713 }
714 }
715
716 async fn fix_fingers(&self) -> Result<()> {
718 match self.dht.fix_fingers() {
719 Ok(action) => match action {
720 PeerRingAction::None => {
721 tracing::debug!(
722 target: "rings_core::dht::stabilization",
723 local = %self.dht.did,
724 "STABILIZATION fix_fingers no remote action"
725 );
726 Ok(())
727 }
728 PeerRingAction::RemoteAction(
729 closest_predecessor,
730 PeerRingRemoteAction::FindSuccessorForFix {
731 did: finger_did,
732 index,
733 },
734 ) => {
735 let msg = Message::FindSuccessorSend(FindSuccessorSend {
736 did: finger_did,
737 then: FindSuccessorThen::Report(
738 FindSuccessorReportHandler::FixFingerTable { index },
739 ),
740 strict: false,
741 });
742 let payload = MessagePayload::new_send(
743 msg.clone(),
744 self.transport.session_sk(),
745 closest_predecessor,
746 closest_predecessor,
747 )?;
748 let tx_id = payload.transaction.tx_id;
749 let next_hop_state = self
750 .transport
751 .get_connection(closest_predecessor)
752 .map(|conn| conn.webrtc_connection_state());
753 tracing::debug!(
754 target: "rings_core::dht::stabilization",
755 local = %self.dht.did,
756 next_hop = %closest_predecessor,
757 next_hop_state = ?next_hop_state,
758 finger_did = %finger_did,
759 index,
760 tx_id = %tx_id,
761 "STABILIZATION fix_fingers send start"
762 );
763 if let Err(e) = self.transport.send_payload(payload).await {
764 tracing::error!(
765 target: "rings_core::dht::stabilization",
766 local = %self.dht.did,
767 next_hop = %closest_predecessor,
768 next_hop_state = ?next_hop_state,
769 finger_did = %finger_did,
770 index,
771 tx_id = %tx_id,
772 error = ?e,
773 "STABILIZATION fix_fingers send failed"
774 );
775 return Err(e);
776 }
777 tracing::debug!(
778 target: "rings_core::dht::stabilization",
779 local = %self.dht.did,
780 next_hop = %closest_predecessor,
781 finger_did = %finger_did,
782 index,
783 tx_id = %tx_id,
784 "STABILIZATION fix_fingers send complete"
785 );
786 Ok(())
787 }
788 _ => {
789 tracing::error!("Invalid PeerRing Action");
790 Err(Error::PeerRingInvalidAction)
791 }
792 },
793 Err(e) => {
794 tracing::error!("{:?}", e);
795 Err(e)
796 }
797 }
798 }
799
800 pub async fn correct_stabilize(&self) -> Result<()> {
802 match self.dht.pre_stabilize()? {
803 PeerRingAction::RemoteAction(
804 next,
805 PeerRingRemoteAction::QueryForSuccessorListAndPred,
806 ) => {
807 let next_hop_state = self
808 .transport
809 .get_connection(next)
810 .map(|conn| conn.webrtc_connection_state());
811 tracing::debug!(
812 target: "rings_core::dht::stabilization",
813 local = %self.dht.did,
814 next = %next,
815 next_hop_state = ?next_hop_state,
816 "STABILIZATION correct_stabilize query start"
817 );
818 match self
819 .transport
820 .send_direct_message(
821 Message::QueryForTopoInfoSend(QueryForTopoInfoSend::new_for_stab(next)),
822 next,
823 )
824 .await
825 {
826 Ok(tx_id) => tracing::debug!(
827 target: "rings_core::dht::stabilization",
828 local = %self.dht.did,
829 next = %next,
830 tx_id = %tx_id,
831 "STABILIZATION correct_stabilize query complete"
832 ),
833 Err(e) => {
834 tracing::error!(
835 target: "rings_core::dht::stabilization",
836 local = %self.dht.did,
837 next = %next,
838 next_hop_state = ?next_hop_state,
839 error = ?e,
840 "STABILIZATION correct_stabilize query failed"
841 );
842 return Err(e);
843 }
844 }
845 }
846 action => {
847 tracing::debug!(
848 target: "rings_core::dht::stabilization",
849 local = %self.dht.did,
850 action = ?action,
851 "STABILIZATION correct_stabilize no remote query"
852 );
853 }
854 }
855 Ok(())
856 }
857}
858
859fn elapsed_since(started_at: Instant) -> i64 {
860 i64::try_from(started_at.elapsed().as_millis()).unwrap_or(i64::MAX)
861}
862
863mod maintenance;
864#[cfg(all(test, target_family = "wasm"))]
865pub(crate) use maintenance::maintenance_phase_trace_for_test;
866#[cfg(all(test, target_family = "wasm"))]
867pub(crate) use maintenance::reset_maintenance_phase_trace_for_test;
868#[cfg(all(test, target_family = "wasm"))]
869pub(crate) use maintenance::MaintenancePhaseEvent;
870#[cfg(all(test, target_family = "wasm"))]
871pub(crate) use maintenance::MaintenancePhaseKind;
872mod storage_repair;
873
874#[cfg(test)]
875mod tests {
876 use std::sync::atomic::AtomicBool;
877 use std::sync::atomic::Ordering;
878
879 use super::*;
880
881 struct DropWitness(Arc<AtomicBool>);
882
883 impl Drop for DropWitness {
884 fn drop(&mut self) {
885 self.0.store(true, Ordering::Release);
886 }
887 }
888
889 #[cfg_attr(target_family = "wasm", wasm_bindgen_test::wasm_bindgen_test)]
890 #[cfg_attr(not(target_family = "wasm"), tokio::test)]
891 async fn test_step_deadline_drops_work_that_does_not_complete() {
892 let dropped = Arc::new(AtomicBool::new(false));
893 let witness = dropped.clone();
894 let future = async move {
895 let _witness = DropWitness(witness);
896 futures::future::pending::<()>().await;
897 Ok(())
898 };
899
900 let result = await_step_deadline(future, Duration::from_millis(1)).await;
901
902 assert!(matches!(result, StepDeadline::TimedOut));
903 assert!(dropped.load(Ordering::Acquire));
904 }
905}