use super::*;
impl_veilid_log_facility!("rtab");
pub(crate) const RELIABLE_PING_INTERVAL_START: TimestampDuration = TimestampDuration::new_secs(10);
pub(crate) const RELIABLE_PING_INTERVAL_MAX: TimestampDuration = TimestampDuration::new_secs(120);
pub(crate) const RELIABLE_PING_INTERVAL_MULTIPLIER: f64 = 2.0;
pub(crate) const UNRELIABLE_ANSWER_SPAN: TimestampDuration = TimestampDuration::new_secs(60);
pub(crate) const UNRELIABLE_PING_INTERVAL: TimestampDuration = TimestampDuration::new_secs(5);
pub(crate) const MISSING_LOST_QUESTIONS_UNORDERED: u32 = 3;
pub(crate) const MISSING_LOST_QUESTIONS_ORDERED: u32 = 1;
pub(crate) fn missing_lost_questions_count(sequence_ordering: SequenceOrdering) -> u32 {
match sequence_ordering {
SequenceOrdering::Unordered => MISSING_LOST_QUESTIONS_UNORDERED,
SequenceOrdering::Ordered => MISSING_LOST_QUESTIONS_ORDERED,
}
}
pub(crate) const DEAD_LOST_QUESTION_SPAN: TimestampDuration = TimestampDuration::new_secs(60);
pub(crate) const DEAD_UNREACHABLE_COUNT: u32 = 1;
pub(crate) const DEAD_FAILED_TO_SEND_UNORDERED: u32 = 9;
pub(crate) const DEAD_FAILED_TO_SEND_ORDERED: u32 = 3;
pub(crate) fn dead_failed_to_send_count(sequence_ordering: SequenceOrdering) -> u32 {
match sequence_ordering {
SequenceOrdering::Unordered => DEAD_FAILED_TO_SEND_UNORDERED,
SequenceOrdering::Ordered => DEAD_FAILED_TO_SEND_ORDERED,
}
}
pub(crate) const DEAD_NEVER_SEEN_LOST_QUESTIONS_UNORDERED: u32 = 3;
pub(crate) const DEAD_NEVER_SEEN_LOST_QUESTIONS_ORDERED: u32 = 1;
pub(crate) fn dead_never_seen_lost_questions_count(sequence_ordering: SequenceOrdering) -> u32 {
match sequence_ordering {
SequenceOrdering::Unordered => DEAD_NEVER_SEEN_LOST_QUESTIONS_UNORDERED,
SequenceOrdering::Ordered => DEAD_NEVER_SEEN_LOST_QUESTIONS_ORDERED,
}
}
impl BucketEntryInner {
pub(super) fn compute_state_reason(&self, cur_ts: Timestamp) -> BucketEntryStateReason {
let opt_nm = self
.opt_registry
.as_ref()
.and_then(|r| r.lookup::<NetworkManager>());
let offline_overlap_since = |from: Timestamp| {
opt_nm
.as_ref()
.map(|nm| {
nm.online_detector().offline_overlap(
RoutingDomain::PublicInternet,
from,
cur_ts,
)
})
.unwrap_or_else(|| TimestampDuration::new(0))
};
Self::compute_state_reason_from_stats(
cur_ts,
self.punishment,
&self.rpc_stats,
&self.per_sequence_ordering_stats,
&self.per_transport_stats,
offline_overlap_since,
)
}
pub(crate) fn compute_state_reason_from_stats(
cur_ts: Timestamp,
punishment: Option<PunishmentReason>,
rpc_stats: &RPCStats,
per_sequence_ordering_stats: &BTreeMap<SequenceOrdering, RPCStats>,
per_transport_stats: &BTreeMap<TransportType, RPCStats>,
offline_overlap_since: impl Fn(Timestamp) -> TimestampDuration,
) -> BucketEntryStateReason {
if let Some(p) = punishment {
return BucketEntryStateReason::Punished(p);
}
if rpc_stats.unreachable >= DEAD_UNREACHABLE_COUNT {
return BucketEntryStateReason::Dead(BucketEntryStateDeadReason::ExcessiveUnreachable);
}
if per_sequence_ordering_stats
.iter()
.any(|(so, stats)| stats.failed_to_send >= dead_failed_to_send_count(*so))
{
return BucketEntryStateReason::Dead(BucketEntryStateDeadReason::ExcessiveSendFailures);
}
if per_transport_stats.is_empty()
&& per_sequence_ordering_stats.iter().any(|(so, stats)| {
stats.recent_lost_questions >= dead_never_seen_lost_questions_count(*so)
})
{
return BucketEntryStateReason::Dead(
BucketEntryStateDeadReason::NeverSeenLostQuestions,
);
}
if !per_transport_stats.is_empty()
&& per_sequence_ordering_stats.values().any(|stats| {
stats
.first_steady_lost_question_ts
.map(|ts| {
cur_ts
.duration_since(ts)
.saturating_sub(offline_overlap_since(ts))
>= DEAD_LOST_QUESTION_SPAN
})
.unwrap_or(false)
})
{
return BucketEntryStateReason::Dead(BucketEntryStateDeadReason::SteadyLostQuestions);
}
if rpc_stats.unreachable > 0 {
return BucketEntryStateReason::Missing(BucketEntryStateMissingReason::Unreachable);
}
if per_sequence_ordering_stats
.values()
.any(|stats| stats.failed_to_send > 0)
{
return BucketEntryStateReason::Missing(BucketEntryStateMissingReason::FailedToSend);
}
if per_sequence_ordering_stats
.iter()
.any(|(so, stats)| stats.recent_lost_questions >= missing_lost_questions_count(*so))
{
return BucketEntryStateReason::Missing(BucketEntryStateMissingReason::LostQuestions);
}
if per_sequence_ordering_stats
.values()
.all(|stats| stats.first_steady_answer_ts.is_none())
{
return BucketEntryStateReason::Initial;
}
if per_sequence_ordering_stats.values().any(|stats| {
if let Some(ts) = stats.first_steady_answer_ts {
let raw = cur_ts.duration_since(ts);
raw < UNRELIABLE_ANSWER_SPAN
|| raw.saturating_sub(offline_overlap_since(ts)) < UNRELIABLE_ANSWER_SPAN
} else {
false
}
}) {
return BucketEntryStateReason::Unreliable;
}
BucketEntryStateReason::Reliable
}
pub fn state_reason(&self, cur_ts: Timestamp) -> BucketEntryStateReason {
let mut state_stats_accounting = self.state_stats_accounting.lock();
self.compute_and_log_state_reason_inner(cur_ts, &mut state_stats_accounting)
}
pub fn state(&self, cur_ts: Timestamp) -> BucketEntryState {
self.state_reason(cur_ts).into()
}
fn compute_and_log_state_reason_inner(
&self,
cur_ts: Timestamp,
state_stats_accounting: &mut StateStatsAccounting,
) -> BucketEntryStateReason {
let reason = self.compute_state_reason(cur_ts);
if let Some(state_reason_change_span) =
state_stats_accounting.record_state_reason(cur_ts, reason)
{
veilid_log!(self debug target: "rtab::state::node", "{:#}: {:#}", self.best_node_id(), state_reason_change_span);
}
reason
}
pub(super) fn revive(&mut self, cur_ts: Timestamp) {
let mut state_stats_accounting = self.state_stats_accounting.lock();
let dead_or_missing_reason = self.compute_state_reason(cur_ts);
if matches!(
dead_or_missing_reason,
BucketEntryStateReason::Dead(_) | BucketEntryStateReason::Missing(_)
) {
self.rpc_stats.last_question_ts = None;
self.rpc_stats.last_seen_ts = None;
self.rpc_stats.first_steady_answer_ts = None;
self.rpc_stats.first_steady_lost_question_ts = None;
self.rpc_stats.failed_to_send = 0;
self.rpc_stats.unreachable = 0;
self.per_sequence_ordering_stats.clear();
self.per_transport_stats.clear();
self.contact_method_failures.clear();
let alive_reason =
self.compute_and_log_state_reason_inner(cur_ts, &mut state_stats_accounting);
if !matches!(alive_reason, BucketEntryStateReason::Initial) {
veilid_log!(self error "node was ({:?}) but is ({:?}) after reviving: {}", dead_or_missing_reason, alive_reason, self.best_node_id());
}
veilid_log!(self debug target: "rtab::state::node", "Node revived: {:#}", self.best_node_id());
}
}
}