Skip to content

[#861] Fail fast when a total update request gets no answer - #864

Merged
vharseko merged 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:issues/861-replication-init-hang
Aug 12, 2026
Merged

vharseko merged 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:issues/861-replication-init-hang

Conversation

@vharseko

Copy link
Copy Markdown
Member

Refs #861 (root-cause analysis and the fourth-bug addendum are in the issue comments).

dsreplication enable/initialize can hang forever in the window between publishing the InitializeRequestMsg and receiving the InitializeTargetMsg: nothing bounds that wait, and both the request and its error answer can be lost silently. A CI occurrence held a runner for 6 hours (mitigated by #862). Four fixes:

  1. Do not lose the request — ReplicationDomain.initializeFromRemote() and its retry path now check the result of broker.publish(...): a request silently dropped by a broker caught between two sessions (connection error, recovery pending after a reconnect) fails the task immediately with ERR_INITIALIZATION_FAILED_NOCONN instead of leaving it waiting forever. The broker's resend-on-reconnect procedure only replays UpdateMsgs, so nothing else would ever deliver that request.
  2. Stalled-request watchdog — new ReplicationDomain.abortStalledInitializeFromRemote(timeoutMs), polled every second by InitializeTask while it waits: if neither InitializeTargetMsg nor ErrorMsg arrives within 2 minutes of the request, the task fails with ERR_NO_REACHABLE_PEER_IN_THE_DOMAIN (which the dsreplication CLI already retries). A late InitializeTargetMsg racing the abort is detected under the context's monitor and ignored by the listener, so the abort cannot race the start of an import.
  3. Same-millisecond ErrorMsg gate — both staleness gates (errorMsg.getCreationTime() > ieCtx.startTime) become >=: with all servers on one host the whole request/rejection round-trip can complete within the millisecond the import/export context was created in, and the strict comparison discarded the legitimate answer as stale — the exact single-host CI topology where the hang was observed.
  4. setExceptionIfNoneSet never set anything — the null check was made on the argument instead of the field (upstream bug present since the 2014 "Recreate OpenDJ 3 development branch" import). Every error funneled through it was silently dropped: an accepted ErrorMsg, a failed LDIF import (partial data, no generation-id recomputation) and mid-import disconnections all ended with the initialize task reporting COMPLETED_SUCCESSFULLY.

New regression tests in ReplicationDomainTest:

  • errorMsgFromSameMillisecondTerminatesPendingInitialize — a crafted ErrorMsg timestamped one millisecond before the context stays ignored, one timestamped in the same millisecond terminates the pending initialization with the right error (covers fixes 3 and 4).
  • stalledInitializeFromRemoteIsAborted — a peer that never answers the request: the watchdog refuses to abort before the delay, aborts after it, fails the task with the peer-unreachable error, releases the import/export context, and a second call is a no-op.

Test results: ReplicationDomainTest 12/12, InitOnLineTest 10/10 (the end-to-end online-initialization suite most sensitive to fix 4).

… no answer

Four fixes for the dsreplication enable/initialize hang and its silent
failure modes:
- initializeFromRemote and its retry now fail the task immediately when
  the broker cannot publish the InitializeRequestMsg instead of silently
  dropping it and waiting forever;
- InitializeTask arms a watchdog that aborts the initialization when
  neither InitializeTargetMsg nor ErrorMsg arrives within 2 minutes;
- the ErrorMsg staleness gates accept answers created in the same
  millisecond as the import/export context (single-host round-trips);
- setExceptionIfNoneSet checked the argument instead of the field for
  null (upstream bug since 2014), so every failed total update reported
  success over partially imported data.
@vharseko
vharseko requested a review from maximthomas August 12, 2026 06:57
@vharseko vharseko added replication bug concurrency Thread-safety / race-condition bugs data-loss Data integrity / loss of entries tests Test suites: fixing, enabling, un-disabling labels Aug 12, 2026
…roughout initialize()

CodeQL flagged four may-be-null dereferences of ieCtx in initialize()
after the stalled-watchdog guard was added. Three were real on the
remotely-initiated import path: when acquireIEContext() rejected a
concurrent import/export, the catch/finally blocks dereferenced a null
ieCtx - or, now that setExceptionIfNoneSet() actually records errors,
poisoned and released the context of the unrelated on-going operation.

Acquire (or validate) the context before entering the import
try/finally so ieCtx is final and provably non-null; a rejected
initialization now answers the exporter with an ErrorMsg and leaves
the on-going operation's context untouched. Also collapse the
duplicated 3A Systems Portions lines in the file header.
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 data-loss Data integrity / loss of entries replication tests Test suites: fixing, enabling, un-disabling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dsreplication enable hangs initializing registration information when adding a third replica; CI Test replication step has no timeout

2 participants