Fix flaky de-auth positive control: remove_node revokes locally before notifying the peer - #752
Draft
kriszyp wants to merge 3 commits into
Draft
Fix flaky de-auth positive control: remove_node revokes locally before notifying the peer#752kriszyp wants to merge 3 commits into
kriszyp wants to merge 3 commits into
Conversation
…e notifying the peer remove_node awaited the removed peer's remove_node_back response before deleting the peer's hdb_nodes row locally, so the peer's cooperative teardown (1008 "No longer subscribed") got a full round-trip head start on the dynamic send-authorization watch and could close every watch-bearing socket before the watch saw any delete event -- leaving no "Unauthorized database subscription" close for the positive control in hdbNodesReloadSendAuth.test.mjs to observe (2026-08-22 main CI failure, run 32558915650). Deleting locally first also stops local revocation from waiting on a possibly-offline peer's connect timeout. The test now polls for the close signature instead of sampling the logs once. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GbV4RZzUbNLa97qTLpkcCb
… logs post-remove_node, trim comments The positive control now searches a post-remove_node log slice (no vacuous pass from earlier markers in isolated runs) and accepts either signature the send-auth watch produces -- the debug-level Unauthorized close reason or the warn-level "hdb_nodes no longer authorizes" line -- removing a silent dependency on debug logging. Comment narration trimmed per review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GbV4RZzUbNLa97qTLpkcCb
…message Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GbV4RZzUbNLa97qTLpkcCb
There was a problem hiding this comment.
Code Review
This pull request updates the node removal process in 'replication/setNode.ts' to delete the node record locally before notifying the peer, ensuring that authorization is revoked locally first. It also updates the integration test 'hdbNodesReloadSendAuth.test.mjs' to poll and slice logs post-removal to handle a race condition between socket teardown paths. There are no review comments, so I have no feedback to provide.
Contributor
|
Reviewed; no blockers found. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the flaky positive control in
hdbNodesReloadSendAuth.test.mjs("remove_node still genuinely de-authorizes a peer", failed on the 2026-08-22 07:10 UTC main run, shard 4/6, Node 26.5.0) by fixing the ordering defect inremove_nodethat the flake exposed, plus a test-side hardening.Root cause
remove_nodesentremove_node_backto the peer being removed and awaited the peer's response (sendOperationToNoderesolves onOPERATION_RESPONSE) before deleting the peer'shdb_nodesrow locally. That hands the removed peer a full round trip's head start: it deletes its self row and its cooperative teardown closes its dialed sockets (1008 No longer subscribed) — and those sockets are exactly the server-side sockets on the removing node where the dynamic send-authorization watch lives. The watch can then only produce its1008 Unauthorized database subscriptionclose if one of two knife-edge races is won:hdb_nodesdelete event reaches the watch before those close frames land.In the failing CI job both were lost (margins observed locally are ≈1–3 ms): every socket closed
1008 No longer subscribedwithin ~30 ms and theUnauthorized database subscriptionsignature never appeared — not even 48 s later, since a closed socket's watch is retired. The test's single post-disconnect log read then failed the positive control.Timeline from the failing job (A = 127.0.0.2 removed by B = 127.0.0.3):
Product fix (
replication/setNode.ts)Delete the local
hdb_nodesrow first, then sendremove_node_back. Beyond stabilizing the close signature, this is the right revocation order on its own merits:"it may not even be online anymore"— the code contradicted its own comment).Unauthorized database subscriptionclose is produced deterministically instead of racing the peer's cooperative teardown.remove_node_backtravels on its own freshly-dialed operation connection and the peer authorizes it against the removing node's row (still present on the peer), so the reorder does not affect its delivery; the reciprocal self-row deletion on the peer is unchanged and still awaited beforeremove_nodereturns.Test fix (
integrationTests/cluster/hdbNodesReloadSendAuth.test.mjs)The positive control now polls (20 s deadline, 250 ms interval) a post-
remove_nodelog slice for either of the gate's two signatures — theUnauthorized database subscriptionclose reason, or the same watch's warn-levelhdb_nodes no longer authorizesline on the removing node — instead of sampling the full logs once. The slice prevents a vacuous pass from an earlier marker in isolated runs; the warn-line alternative removes a silent dependency on debug-level logging (the close reason only ever appears in debug lines). Both signatures come only from the send-auth watch, so a disarmed gate still turns the control red (re-demonstrated below).Verification
1008 No longer subscribedand noUnauthorized database subscriptionline even 48 s after the assertion. Locally, amplifying the peer's head start by 30 ms (a dist-only patch of the old ordering, simulating a contended main thread) under CPU load (12 spinner processes on 20 cores) reproduced the exact assertion failure in 2/6 iterations, each with the CI signature: zero marker lines anywhere, and the new 20 s marker poll exhausted — the marker never appears once the watch-bearing sockets die; it is not merely late.remove_node, before the peer can have learned of the removal.shouldCloseSendAuthWatchpatched indistto always returnfalse), the positive control fails: the main reload-marker test stays green and the positive control fails after the full 20 s poll — so the control still turns red if the gate stops closing.npm run test:unit): 826 passing. Targeted cluster integration suites coveringremove_node/send-gate behavior — removeNodeBlastRadius, replicationTopology, reverseBaseCopyDeclined, replicationReconnect, blockCacheEviction, systemDbDynamicSendGate, systemDbExcludedPeerChurn — 22/22 passing. Fulltest:integration:allleft to PR CI.Refs the 2026-08-22 Integration Tests failure on
main(run 32558915650, sha d2a193f).For the human reviewer
These are the decision-ledger items from the independent review (codex + cursor-grok + harper-domain; verdict COMMENTS, adjudicated severity minor; all 3 findings fixed pre-push):
removeNodeBlastRadiusasserts end-state only, and passes).remove_node_back: kept awaited, soremove_node's success response still means "both sides attempted cleanup" and the offline-peer warning lands before returning. The deleted stale comment claimed fire-and-forget was the intent; switching is cheap but is an API-semantics call I did not make unilaterally.1008rejections with no scheduled reconciliation (only warn-line evidence). That replaces the previous inverse window (peer told to stop while we still authorized it). Fail-closed is the right default, but the orphaned-peer-row cleanup gap predates and survives this change.Complexity: small — one ordering change in a cold administrative operation plus test hardening.
— Claude Fable 5
🤖 Generated with Claude Code
https://claude.ai/code/session_01GbV4RZzUbNLa97qTLpkcCb
Review-Coverage: authored=unknown; ran=none; rounds=1 @ e88d47a
Human-Review-Need: 4 @ e88d47a