Skip to content

[#1039] Report a total update whose session stops before the DoneMsg as a failed import - #1044

Open
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/1039-total-update-cut-before-done
Open

vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/1039-total-update-cut-before-done

Conversation

@vharseko

Copy link
Copy Markdown
Member

A total update into this replica whose broker is stopped before the exporter's DoneMsg arrives ends as a finished import. receiveEntryBytes() reads a broker which is shutting down as the end of the stream and records nothing:

if (msg == null)
{
  if (broker.shuttingDown())
  {
    // The server is in the shutdown process
    return null;
  }
  ...

ReplInputStream turns that null into the same -1 a DoneMsg produces, so importBackend() closes the import on the entries which had arrived, loadDataState() loads the exporter's ServerState and generationId from the base entry among them - ds-sync-state and ds-sync-generation-id are operational attributes, exported with the base entry, which comes first - and initialize() logs NOTE_FULL_UPDATE_ENGAGED_FROM_REMOTE_END with an empty cause and completes the task as a success. The replica then comes up as a peer of the exporter over part of its data: the changes it is missing are covered by the ServerState it loaded, and the exporter's waitForRemoteEndOfInit() reads the matching generationId as a success too.

The comment reads the stop as the shutdown of the server, and broker.stop() is reached on more roads than that: every restart of the session goes through disableService() - the restart a failed replay asks for (#968 keeps it out of an import; #1041 is the window which remains), the restart a configuration change asks for (#1040) - and a total update into this replica never sets disabled, so none of the owner guards sees it.

What this changes

  • receiveEntryBytes() records the stop as the exception of the import, whichever road stopped the broker: a new ERR_INIT_SESSION_STOPPED_DURING_IMPORT, next to the ERR_INIT_RS_DISCONNECTION_DURING_IMPORT a lost connection already records. Everything after it is what every other failed import already does: importBackend() computes the generationId over the data and stores it in place of the exporter's, initialize() makes no new attempt on a broker which is not connected, logs the end note with the cause, and completes the task as STOPPED_BY_ERROR with it.

On the shutdown road this adds the checksum export of the partial data to the cleanup the listener thread runs while disableService() joins it - a cost proportional to what arrived. The alternative, leaving the generationId unstored for the next start to compute, is not correct for the restart roads: enableService() does not reload the state, and the session would come back up with the exporter's generationId still in memory.

Not in this change

Tests

InitOnLineTest.initializeImportSessionStoppedBeforeDone: the total update asked for by a task on this server, a broker of the test as the exporter, the base entry sent with ds-sync-generation-id: 7777777 the way the base entry of a real export carries the exporter's generationId, one more entry, no DoneMsg; once the task reports two entries left, disableService() - the primitive every road to a stopped session goes through. It asserts the task ends STOPPED_BY_ERROR with two entries left and two done, that its log names the stopped session as the reason, and that neither getGenerationID() nor ds-sync-generation-id on the base entry is the exporter's.

Without the fix it fails on the first assertion: expected [STOPPED_BY_ERROR] but found [COMPLETED_SUCCESSFULLY]. A mutant which records the lost-connection message on the stopped broker instead fails on the reason.

InitOnLineTest is 11/11 under mvn -Pprecommit verify -Dit.test=InitOnLineTest, and GenerationIdTest 4/4, ReSyncTest 2/2, UpdateOperationTest 31/31, AssuredReplicationPluginTest 14/14, StateWithoutBaseEntryTest 3/3 and ReplicationDomainTest 12/12 on the same tree, one JVM per class, nothing skipped.

Ordinal

ERR_INIT_SESSION_STOPPED_DURING_IMPORT_329: master holds up to 327, and the open PRs which add to replication.properties hold 318, 322-325 and 328 (#1019).

Fixes #1039

… before the DoneMsg as a failed import

A total update into this replica whose broker was stopped before the DoneMsg of the
exporter arrived ended as a finished import: receiveEntryBytes() returned null with no
exception recorded when the broker was shutting down, ReplInputStream turned that into
the end of stream a DoneMsg produces, and importBackend() kept the entries which had
arrived under the generationId of the exporter, loaded from the base entry among them.
The task completed as a success, and the replica came up as a peer of the exporter over
part of its data.

The stop is now recorded as the exception of the import, whichever road stopped the
broker - the shutdown of the server, or a restart of the session, which every road takes
through disableService() - so the import is reported as failed and its generationId is
computed over the data, as it is for every other failed import.

InitOnLineTest.initializeImportSessionStoppedBeforeDone drives the total update from a
broker of the test, stops the session between two EntryMsgs, and asserts that the task
fails for that reason and that neither the domain nor the base entry keeps the
generationId of the exporter.
@vharseko vharseko added bug replication data-loss Data integrity / loss of entries java Changes to Java sources tests Test suites: fixing, enabling, un-disabling labels Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug data-loss Data integrity / loss of entries java Changes to Java sources replication tests Test suites: fixing, enabling, un-disabling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replication: a total update whose session stops before the DoneMsg ends as a finished import, with the exporter's generationId over partial data

1 participant