Skip to content

[#821] Unregister the handler when a handshake aborts after registration - #838

Merged
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/821-unregister-aborted-handshake
Aug 4, 2026
Merged

[#821] Unregister the handler when a handshake aborts after registration#838
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/821-unregister-aborted-handshake

Conversation

@vharseko

@vharseko vharseko commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problem

DataServerHandler.startFromRemoteDS() and both ReplicationServerHandler start paths register the handler in the replication server domain before finalizeStart(), which can still fail before the reader and writer threads are started (interrupt in session.waitForStartup(); on the RS side the window also spans receiveTopoInfoFromRS(), which throws IOException/DirectoryException). abortStart() closed the session and rolled back the generation id but never unregistered the handler — and nothing else ever removes it: the normal cleanup is the reader or writer noticing the dead session and calling stopServer(), and neither thread was running.

The stale entry:

  • inflates the connected DS count advertised in ReplServerStartDSMsg, skewing the DS-side weight-based load balancing forever;
  • is published to peers and DSs in every TopologyMsg;
  • keeps resetGenerationIdIfPossible() from ever resetting the generation id;
  • permanently refuses reconnection of that same DS with ERR_DUPLICATE_SERVER_ID on every retry.

Fix

abortStart() now asks the domain to unregister a handler that got as far as registering (ReplicationServerDomain.unregisterFailedHandshake()), running the same cleanup stopServer() would have done: removal from connectedDSs/connectedRSs, monitoring publisher stop when it was the last server, handler shutdown, resetGenerationIdIfPossible() and topology notification.

Two deliberate differences from stopServer():

  • the removal only fires when the map still holds that very handler instance, so aborts that happen before registration (e.g. the duplicate server id rejection) cannot evict a legitimately connected server;
  • the cleanup runs under the already held domain lock instead of stopServer()'s interruptible lock acquisition, which would silently skip the cleanup when the handshake was aborted by an interrupt — the very trigger being cleaned up after.

Tests

New HandshakeAbortRegistrationTest pins the contract in three scenarios: an abort after registration unregisters the DS handler (including generation id reset and unblocked reconnection of the same server id), the same for an RS handler, and an abort before registration leaves the legitimately connected handler untouched. Without the fix the two regression scenarios fail with "the aborted handshake left a dead DataServerHandler/ReplicationServerHandler registered"; with the fix all three pass, as does the neighbouring HandshakeAbortGenerationIdTest, which drives real aborted handshakes through the modified abortStart() path.

Fixes #821.

…orts after registration

DataServerHandler.startFromRemoteDS() and both ReplicationServerHandler
start paths register the handler in the domain before finalizeStart(),
which can still fail before the reader and writer threads are started.
abortStart() closed the session and rolled back the generation id but
never unregistered the handler, and nothing else ever removes it: the
normal cleanup is the reader or writer noticing the dead session and
calling stopServer(), and neither thread was running. The stale entry
inflated the connected DS count fed to the DS-side load balancing, was
advertised in every TopologyMsg, kept the generation id from ever being
reset and permanently refused reconnection of the same server id with
ERR_DUPLICATE_SERVER_ID.

abortStart() now asks the domain to unregister a handler that got as far
as registering, running the same cleanup stopServer() would have done.
The removal is conditional on the map still holding that very handler
instance, so aborts that happen before registration (e.g. the duplicate
server id rejection) cannot evict a legitimately connected server, and
the cleanup runs under the already held domain lock instead of
stopServer()'s interruptible lock acquisition, which would silently skip
the cleanup when the handshake was aborted by an interrupt.

Fixes OpenIdentityPlatform#821.
@vharseko vharseko added bug concurrency Thread-safety / race-condition bugs replication tests Test suites: fixing, enabling, un-disabling labels Aug 3, 2026
@vharseko
vharseko requested a review from maximthomas August 3, 2026 20:41
@vharseko
vharseko merged commit 2b20c60 into OpenIdentityPlatform:master Aug 4, 2026
17 checks passed
@vharseko
vharseko deleted the issues/821-unregister-aborted-handshake branch August 4, 2026 08:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug concurrency Thread-safety / race-condition bugs replication tests Test suites: fixing, enabling, un-disabling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Aborted DS handshake leaves a dead DataServerHandler registered in ReplicationServerDomain

2 participants