mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 16:24:45 +00:00
node: add a debug-build peer-map coherence check
Assert, once per tick in debug builds, that the peer control-machine map and its carriers stay coherent: every control machine has a live handshake leg, an active peer, or a pending connect (a machine with none is a leak the stale reaper can never see), and every handshake leg has a machine. The second direction sits behind a file-local const so a branch where handshake-window legs legitimately run machine-less can gate it off without weakening the leak tripwire. Teach the add_connection test seam to seed a control machine for the leg it inserts (derived from the connection's direction and identity), and remove_connection to dispose it, so the seam-built topologies satisfy the check. Three unit tests: seam coherence, coherence through a real promotion, and the orphaned-machine panic.
This commit is contained in:
+65
-1
@@ -2273,6 +2273,53 @@ impl Node {
|
||||
self.peer_timers.remove(&link);
|
||||
}
|
||||
|
||||
/// Gates the leg→machine direction of `debug_assert_peer_maps_coherent`.
|
||||
/// Asserting it requires the convention that every `connections` insert is
|
||||
/// paired with a `peer_machines` insert before the next await point —
|
||||
/// which holds here: inbound msg1 births a machine alongside the window
|
||||
/// leg, and dials birth one before the leg exists. Where handshake-window
|
||||
/// legs legitimately run machine-less, flip this to `false` rather than
|
||||
/// weakening the machine→carrier direction.
|
||||
#[cfg(debug_assertions)]
|
||||
const CHECK_LEGS_HAVE_MACHINES: bool = true;
|
||||
|
||||
/// Debug-build coherence sweep over the peer-lifecycle maps, run once per
|
||||
/// rx-loop tick and invoked directly by unit tests.
|
||||
///
|
||||
/// Machine→carrier: every `peer_machines` entry must have a live carrier —
|
||||
/// a pending connection on the same link, an active peer on it, or a
|
||||
/// pending connect still resolving toward it. A machine with none of these
|
||||
/// is unreachable by every teardown path and has leaked.
|
||||
///
|
||||
/// Leg→machine: every pending connection carries a control machine (gated
|
||||
/// by `CHECK_LEGS_HAVE_MACHINES` above).
|
||||
#[cfg(debug_assertions)]
|
||||
pub(in crate::node) fn debug_assert_peer_maps_coherent(&self) {
|
||||
for link in self.peer_machines.keys() {
|
||||
let has_carrier = self.connections.contains_key(link)
|
||||
|| self.peers.values().any(|peer| peer.link_id() == *link)
|
||||
|| self
|
||||
.peering
|
||||
.pending_connects
|
||||
.iter()
|
||||
.any(|pending| pending.link_id == *link);
|
||||
assert!(
|
||||
has_carrier,
|
||||
"control machine for link {link} has no live carrier \
|
||||
(no pending connection, active peer, or pending connect)"
|
||||
);
|
||||
}
|
||||
|
||||
if Self::CHECK_LEGS_HAVE_MACHINES {
|
||||
for link in self.connections.keys() {
|
||||
assert!(
|
||||
self.peer_machines.contains_key(link),
|
||||
"pending connection on link {link} has no control machine"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Operator-visible msg1 resend count for a pending handshake `link`, read
|
||||
/// from the per-peer machine (the counter's home once the resend drive moved
|
||||
/// off the shell connection). Machine-less connections (inbound legs that
|
||||
@@ -2335,6 +2382,11 @@ impl Node {
|
||||
// === Connection Management (Handshake Phase) ===
|
||||
|
||||
/// Add a pending connection.
|
||||
///
|
||||
/// Also seeds a control machine for the leg when none exists yet, keeping
|
||||
/// the leg→machine invariant (`debug_assert_peer_maps_coherent`) intact
|
||||
/// for callers that insert a connection directly rather than through the
|
||||
/// dial or inbound-msg1 paths, which pair the two inserts themselves.
|
||||
pub fn add_connection(&mut self, connection: PeerConnection) -> Result<(), NodeError> {
|
||||
let link_id = connection.link_id();
|
||||
|
||||
@@ -2348,6 +2400,16 @@ impl Node {
|
||||
});
|
||||
}
|
||||
|
||||
self.peer_machines.entry(link_id).or_insert_with(|| {
|
||||
let now = connection.started_at();
|
||||
match connection.expected_identity() {
|
||||
Some(identity) if connection.is_outbound() => {
|
||||
PeerMachine::new_outbound(link_id, *identity, now)
|
||||
}
|
||||
_ => PeerMachine::new_inbound(link_id, now),
|
||||
}
|
||||
});
|
||||
|
||||
self.connections.insert(link_id, connection);
|
||||
Ok(())
|
||||
}
|
||||
@@ -2362,8 +2424,10 @@ impl Node {
|
||||
self.connections.get_mut(link_id)
|
||||
}
|
||||
|
||||
/// Remove a connection.
|
||||
/// Remove a connection, disposing its control machine alongside
|
||||
/// (the disposal complement of `add_connection`'s machine seeding).
|
||||
pub fn remove_connection(&mut self, link_id: &LinkId) -> Option<PeerConnection> {
|
||||
self.remove_peer_machine(*link_id);
|
||||
self.connections.remove(link_id)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user