swactor/docs/development_history/DEAD_NODE_REPROBE.md
Developer 439a52bec3 feat(swim): dead-node reprobe, SWIM invariants, and flaky test fixes
Dead-node reprobe mechanism allows partition-healed nodes to rejoin the
cluster automatically. When a reprobe ping reaches a dead-declared node,
the piggyback exchange triggers incarnation-bump refutation, transitioning
the node back to Alive. Death declarations are re-enqueued fresh before
each reprobe to ensure piggyback carries useful membership info.

Added SWIM property invariant checks (completeness, accuracy, convergence)
as reusable post-condition validators for simulation tests.

Investigated 3 flaky MT gossip tests: rewrote convergence_curve_is_monotonic_mt
(strict monotonicity invalid under non-atomic MT snapshots), tuned
partition_heals_and_converges_mt (reduced nodes, relaxed threshold),
documented all_nodes_receive_all_keys_in_ring_1000_mt (stable in isolation).

Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
2026-02-13 07:45:44 +00:00

3.2 KiB
Raw Blame History

Dead-Node Reprobe — Design & Rationale

Problem

When a network partition heals, SWIM nodes on both sides may have declared each other Dead. The alive_members() filter excludes Dead nodes from probing targets, so neither side initiates communication — creating a permanent split even after connectivity is restored.

The existing refutation mechanism (incarnation bump on learning of own death declaration) handles resurrection correctly, but depends on someone telling the dead-declared node about its status. With no probes to dead nodes, nobody does.

Solution: Independent Dead-Node Reprobe Cycle

Added a lightweight reprobe mechanism to SwimProbe that periodically pings dead nodes. The existing piggyback + refutation mechanism handles the rest:

  1. Reprober pings dead node with piggybacked "you are Dead(inc=N)"
  2. Target receives piggyback, sees it's declared Dead → refutes → bumps incarnation
  3. Target replies with Ack carrying piggybacked "I'm Alive(new_inc)"
  4. Reprober applies piggyback → target transitions Dead→Alive

Key insight: we re-enqueue the death declaration in the dissemination queue before packing the reprobe ping's piggyback. Without this, the original death declaration's transmit budget would be long exhausted, and the piggyback would carry no useful membership info.

Design Decisions

Why inside SwimProbe (not SwimNode)?

  • SwimProbe owns the tick counter, sequence counter, and member list access
  • All probe-related logic stays in one place
  • The reprobe is a simple independent cycle — doesn't interfere with ProbePhase

Why no new wire messages?

  • SwimAction::SendPing works identically for normal probes and reprobes
  • The ack from a reprobe targets a different sequence than the current probe cycle, so the probe state machine ignores it — but the piggyback is applied at the SwimNode layer before the probe state machine sees the ack

Configuration

  • dead_reprobe_interval: u64 (default: 50 ticks, ~5× probe_interval)
  • Set to 0 to disable completely
  • Existing test configs use 0 to avoid interference with timing

Files Changed

File Change
crates/distribution/src/swim/probe.rs dead_reprobe_interval in config, maybe_reprobe_dead() method
crates/distribution/src/swim/member_list.rs Added dead_members()
crates/distribution/src/swim/node.rs Re-enqueue death declaration in translate_probe_actions
All SwimConfig struct literals Added dead_reprobe_interval field

Edge Cases

  • Truly dead nodes: Reprobe ping is lost (no ack), no harm done
  • All members dead: Reprobe cycles through them round-robin
  • Concurrent reprobe + normal probe: Independent, different sequences
  • Dissemination budget: Death re-enqueued fresh each reprobe, not stale

Alternatives Considered

  1. Dead node grace period + resurrection timer: More complex, adds new state tracking alongside suspicion timers. Rejected for simplicity.
  2. Periodic re-join via seed nodes: Requires seed node availability, doesn't work when seed is itself dead-declared. Rejected.
  3. Direct liveness inference from Ping reception: Would require changing handle_ping to special-case pings from dead nodes. More invasive.