Found by a full cross-model re-read of #800 (branch fix/replication-uniform-backoff), recorded in that PR's description and deliberately not fixed there. The armed-timer half is unmerged code; the entry leak underneath it is on main.
What is wrong
When a node keeps its name but moves to a new URL, the reconciliation path cancels the setup scheduler for the old URL and knowingly leaves the old connectionReplicationMap entry behind:
// A node that moved to a new URL leaves its old URL's entry behind (pre-existing); at least do
// not let a setup armed for the address it left fire against it.
const previousUrl = previousNode && getNodeURL(previousNode);
if (previousUrl && previousUrl !== getNodeURL(node)) subscribeSetupScheduler.cancelUrl(previousUrl);
That was a sound scoping decision when the only armed work for a URL was a setup. #800 adds an owned per-entry recovery timer (entry.reDriveTimer), and the stale entry survives the migration with its ownership guards intact — so an already-armed recovery timer can still fire and post a force-reconnect to the endpoint the node has left.
Old leak, new consequence: the entry surviving is pre-existing, the timer riding on it is not.
Why this epic
This is the same class as the interaction bug this workstream already records — the #466 false-positive force-reconnect in the wedge re-drive. Each recovery layer is individually correct; the defect is one layer holding a reference the other layer's teardown does not clear.
The shape of a fix
Either migrate or tear down the old URL's entry on a same-name URL change — its iterator, worker subscription and recovery timer — or, minimally, clear every old entry's reDriveTimer alongside the existing cancelUrl. Worth covering with a test that changes a node's URL while a recovery timer is armed.
Refs #800, #466
Found by a full cross-model re-read of #800 (branch
fix/replication-uniform-backoff), recorded in that PR's description and deliberately not fixed there. The armed-timer half is unmerged code; the entry leak underneath it is onmain.What is wrong
When a node keeps its name but moves to a new URL, the reconciliation path cancels the setup scheduler for the old URL and knowingly leaves the old
connectionReplicationMapentry behind:That was a sound scoping decision when the only armed work for a URL was a setup. #800 adds an owned per-entry recovery timer (
entry.reDriveTimer), and the stale entry survives the migration with its ownership guards intact — so an already-armed recovery timer can still fire and post a force-reconnect to the endpoint the node has left.Old leak, new consequence: the entry surviving is pre-existing, the timer riding on it is not.
Why this epic
This is the same class as the interaction bug this workstream already records — the #466 false-positive force-reconnect in the wedge re-drive. Each recovery layer is individually correct; the defect is one layer holding a reference the other layer's teardown does not clear.
The shape of a fix
Either migrate or tear down the old URL's entry on a same-name URL change — its iterator, worker subscription and recovery timer — or, minimally, clear every old entry's
reDriveTimeralongside the existingcancelUrl. Worth covering with a test that changes a node's URL while a recovery timer is armed.Refs #800, #466