proto/stp: hoist the parent flap/hold-down veto to the shell for a clock-free classify core

evaluate_parent no longer reads the injected clock: it returns a ParentEval of
Mandatory, Discretionary, or None, and the flap/hold-down veto is applied at the
edge via a new TreeState::is_switch_suppressed(now_ms), gating only the
discretionary arm. classify_announce/classify_periodic take the pre-computed
switch_suppressed bool instead of now_ms, so the whole classify ladder is
clock-free; the shell callers (tree announce/periodic re-eval, MMP first-RTT
re-eval, handle_parent_lost) compute the veto verdict. The no-coords parent
case stays discretionary (veto-gated) exactly as before. Behavior unchanged;
the veto tests now assert the moved responsibility.
This commit is contained in:
Johnathan Corgan
2026-07-08 19:00:24 +00:00
parent 309a91d293
commit dc9334e725
7 changed files with 260 additions and 102 deletions
+13 -5
View File
@@ -13,6 +13,7 @@ use crate::proto::mmp::{
LinkReportKind, LinkReportSnapshot, MmpAction, PeerLivenessSnapshot, ReceiverReport, RrLog,
SenderReport,
};
use crate::proto::stp::ParentEval;
use std::time::{Duration, Instant};
use tracing::{debug, info, trace, warn};
@@ -198,11 +199,18 @@ impl Node {
.map(|d| d.as_secs())
.unwrap_or(0);
let mono_now_ms = crate::time::mono_ms();
if let Some(new_parent) = self.tree_state.evaluate_parent(
&peer_costs,
&std::collections::BTreeSet::new(),
mono_now_ms,
) {
// Compute the flap-dampening / hold-down veto at the edge; a mandatory
// switch bypasses it, a discretionary one is taken only if not suppressed.
let switch_suppressed = self.tree_state.is_switch_suppressed(mono_now_ms);
let new_parent = match self
.tree_state
.evaluate_parent(&peer_costs, &std::collections::BTreeSet::new())
{
ParentEval::Mandatory(p) => Some(p),
ParentEval::Discretionary(p) if !switch_suppressed => Some(p),
ParentEval::Discretionary(_) | ParentEval::None => None,
};
if let Some(new_parent) = new_parent {
let new_seq = self.tree_state.my_declaration().sequence() + 1;
let flap_dampened =
self.tree_state
+14 -2
View File
@@ -329,8 +329,17 @@ impl Node {
// the wall-clock `now_ms` above used for the peer's tree position). Read
// once and threaded into classify + the state mutators.
let mono_now_ms = crate::time::mono_ms();
// Compute the flap-dampening / hold-down veto at the edge; the classify core
// is clock-free and consumes only this pre-computed verdict.
let switch_suppressed = self.tree_state.is_switch_suppressed(mono_now_ms);
match Stp::classify_announce(&self.tree_state, *from, &peer_costs, &skip, mono_now_ms) {
match Stp::classify_announce(
&self.tree_state,
*from,
&peer_costs,
&skip,
switch_suppressed,
) {
TreeDecision::Switch {
new_parent,
new_seq,
@@ -583,8 +592,11 @@ impl Node {
// Monotonic ms for the flap-dampening / hold-down timers, read once and
// threaded into classify + the state mutators.
let mono_now_ms = crate::time::mono_ms();
// Compute the flap-dampening / hold-down veto at the edge; the classify core
// is clock-free and consumes only this pre-computed verdict.
let switch_suppressed = self.tree_state.is_switch_suppressed(mono_now_ms);
match Stp::classify_periodic(&self.tree_state, &peer_costs, &skip, mono_now_ms) {
match Stp::classify_periodic(&self.tree_state, &peer_costs, &skip, switch_suppressed) {
TreeDecision::Switch {
new_parent,
new_seq,