Conversation
…e asks for while a total update runs A change which could not be replayed is given back and the session restarted for it, by the thread which released it: recoverFromReplayFailure() and the last resort of replay() run the restart synchronously, and the restart stops the session whatever it carries. An export from this replica publishes its entries over that session, exportLDIFEntry() gives it up as ERR_INIT_RS_DISCONNECTION_DURING_EXPORT, and the replica being initialized is left to be initialized again - minutes on a large backend, for a change which would have waited. runRequestedSessionRestarts() now leaves the requests standing while a total update is being processed, in either direction, and says whether it did; the state checkpointer, which already held its own restarts back while ieRunning(), runs them once the total update is over, as it runs every restart abandonReplay() asks for. The gate moves from the checkpointer's pre-check into the one method every road runs its restarts through. NOTE_REPLAY_SESSION_RESTART_HELD_BY_TOTAL_UPDATE says the restart the retry warning announced is waiting, and for what. ReplayDuringExportTest drives the export from a broker of the test, which withholds its acknowledgement so that the exporter waits inside the initialization window, replays a change which fails there, reads the rest of the export and the DoneMsg, then the change delivered again over the restarted session. TestSynchronousReplayQueue becomes safe for the listener-to-test hand-off that redelivery is.
104d8a4 to
34cab95
Compare
|
@maximthomas rebuilt on master at
|
Fixes #1048.
On master, over #981. #981 is merged (
c6b483127f), and the branch is the one commit[#1048]onorigin/master, 34cab95, which is the diff to review. This change is only safe on top of #981, as the issue says: a request the two synchronous roads leave standing is run by nobody but the state checkpointer #981 adds. The copy of #981 this branch carried (c2455f8) was one round behind what master took -[#925] Time disable() against the woken backoff, count the wakes, and pin the clears and the give-back level- and the commit goes over the merged head without a conflict: that round is in the wait ofrestartSession()and inenable(), away from the gate this change adds.What was wrong
A change which could not be replayed is given back and the session restarted for it, so that the replication server sends it again - and the restart is run by the thread which released the change, on two roads:
recoverFromReplayFailure()once the attempts in place are spent, and the last resort ofreplay(). On bothrestartSession()stops the session whatever it is carrying. An export from this replica - the initialization of another replica from this one - publishes its entries over that very broker:disableService()isbroker.stop()on the same object,exportLDIFEntry()falls out of its flow-control loop onbroker.shuttingDown(),publish()answersfalse, and the export is given up asERR_INIT_RS_DISCONNECTION_DURING_EXPORT. The replica being initialized is left to be initialized again: minutes on a large backend, spent for a change which would have waited. (TheNewAttemptretry ininitializeRemote()does not save it:while (!done && ++attempt < 2)leaves the loop on thecontinue, soNOTE_RESENDING_INIT_TARGETis logged and nothing is resent - pre-existing, and not touched here.)#968 decided this on purpose for its own change: an export is not an owner of the session, because owning it would leave the change given back with nothing to restart the session for it - an export reloads no state, so no import-end reset would ask again. #981 is what changes that premise: the request outlives the thread which made it, and the checkpointer runs what stands, already holding its own restarts back while
ieRunning(). The two synchronous roads were left restarting mid-export, and the round-1 review of #981 filed them as #1048.What changed
runRequestedSessionRestarts()leaves the requests standing while a total update is being processed, in either direction, and returns whether it did. It is the one method every road runs its restarts through - the replay thread's own restart, the last resort ofreplay(), and the checkpointer - so the rule holds for all of them in one place: the checkpointer's pre-check loses itsieRunning(), and the paragraph which said why moves to the method which now decides it. The import direction is unchanged in effect: on it the request is not made at all (sessionHasAnOwner()returns before it), except by the last resort, whose request now stands untilimportBackend()clears it where beforerestartSession()consumed it under the owner guard.recoverFromReplayFailure()says the restart is waiting, withNOTE_REPLAY_SESSION_RESTART_HELD_BY_TOTAL_UPDATE(331): theWARN_REPLAY_RETRYING_CHANGEline it has just logged ends in "restarting the session to the replication server so that it is sent again", and for the length of an export that is not what happens - a change not delivered again for minutes should not read as a change nobody asked for. Not built on the road out of a JVM which has run out of memory, for the reason the warning is not built there either.abandonReplay()'s hold, which [#925] Keep asking for a session restart until it has run #981 accepted without a line, is a second long; this one is the export.replay()is unchanged in code: it asksNOW, calls the same method, and a restart held there is left to the checkpointer like a restart which threw; its comment says so.What stays open, deliberately. The read of
ieRunning()and the export's claim of its context share no lock, so an export which begins between the read anddisableService()is still cut - the same window #1041 describes on the import side, a few statements wide where the whole export was. #1045 (#1041) turns the owner read ofrestartSession()into a claim onimportExportContext, and keeps "a claim which fails against an export stops the session, as before" for this issue; once both are in, that claim failing against an export can give the request back instead of stopping, which closes the window for good. Filed against whichever lands second rather than stacked on two open PRs.restartService()during an export - a configuration change accepted before the export began - is #1040's shape (#1042), andisConfigurationChangeAcceptable()already refuses one whileieRunning().Two consequences worth stating. The ServerState of this domain stands behind the change for the length of the export, as it does behind the checkpointer's hold in #981; the
ds-sync-statethe export carries does not cover the change, so the replica being initialized asks the replication server for it and everything after it - nothing is lost. Andreplay-give-up-delay(300 000 ms by default) is measured from the first failure of the change, so an export which outlasts it has the next failed delivery give the change up at once: the budget is a wall-clock one by its documented design, an administrator who runs a long export on a domain whose backend is failing a change can raise it, and subtracting the hold fromReplayFailurewould complicate it for that one combination. Left as is, said here.Tests
ReplayDuringExportTest.aReplayWhichFailsDuringTheExportLeavesTheSessionToTheExport(new)DoneMsgwith every entry of the domain in it,NOTE_REPLAY_SESSION_RESTART_HELD_BY_TOTAL_UPDATEnames the change, and the change is delivered again over the session the checkpointer restarts once the export is over, applied, and covered by the ServerStateTwo things about the fixture, since neither is written down anywhere else. An
EntryMsgis a buffer of the export stream (BufferedOutputStream, 8 KiB), not an entry, so the data has to outgrow(window + 1) × 8 KiBbefore the exporter waits: the test says so in a constant and asserts the arithmetic. AndwaitForRemoteEndOfInit()loops for as long as the importer is in the full update status,ieRunning()with it: the broker leaves that status in afinally-reStart(true), asInitOnLineTestdoes - whatever the case found before, or the export never ends. The export is held across one tick of the checkpointer before it is released, so whichever thread comes for the standing request while the export runs is the one under test.TestSynchronousReplayQueueis backed by aConcurrentLinkedDequerather than aLinkedList: the redelivery is offered by the listener thread of the domain and taken by the thread of the test, which no case did before;take()still throws on an empty queue, which is what makes it the synchronous queue it is.Mutants, each compiled in place of the class and run against the new test:
ieRunning()gate ofrunRequestedSessionRestarts()removed (the fix reverted, the message kept)Starting total update: exporting 10 entries, the retry warning,connection to Replication Server ... is lostfrom the export, and the rest of the entries never arriverunPendingSessionRestart()removedThe class was run on the unfixed tree first, before the fix was written, and failed the same way as the first mutant.
Run on this head, one JVM per class, all green:
ReplayDuringExportTest1/1 (three runs),ReplayDuringImportTest3/3,NamingConflictTest18/18,SessionRestartBackoffTest2/2,SessionRestartTest2/2,DisabledDomainServerStateTest2/2,LDAPReplicationDomainConfigChangeTest9/9,SessionRestartRequestsTest7/7,ServerStateFlushTest4/4,UpdateOperationTest32/32,RemotePendingChangesTest21/21,AssuredReplicationPluginTest14/14,DependencyTest3/3,InitOnLineTest10/10,GenerationIdTest4/4,ReSyncTest2/2 - the classes which restart a session or drive a total update, 134 tests, nothing skipped. On the head over master:UpdateOperationTest33/33,ReplayDuringExportTest1/1,ReplayDuringImportTest4/4,SessionRestartBackoffTest3/3 in one pass,Skipped: 0.Ordinal
NOTE_REPLAY_SESSION_RESTART_HELD_BY_TOTAL_UPDATE_331:masterholds up to 327 (325 with #981); of the open PRs which add toreplication.properties, #1019 holds 328, #1044 329 and #1045 330 - checked on every open PR head, not on the descriptions. 331 is claimed by nothing.