[#986] Give back the changes a replay thread the pool stopped had parked - #988
[#986] Give back the changes a replay thread the pool stopped had parked#988vharseko wants to merge 2 commits into
Conversation
639c297 to
ff67b27
Compare
|
Rebased on The one conflict was the import block of The one thing worth a look is that #941 landed a test of the neighbouring road in the same class: The merge leaves the class with two ways of changing the number of replay threads - The PR description is updated to match. No review comments to answer yet - this is the rebase only. |
|
Ordinal moved: Nothing catches this on the way in. The additions land in different parts of the file, so git merges The open PRs which add to the file now hold 310-325 with nothing claimed twice:
No Java moved with it: the generated constant is the key name without its ordinal, so the rename is This branch is not one of the six, and its collision is the one which does not merely muddy a log.
|
4d1a83a to
8c8323f
Compare
|
Rebased, and the branch is mergeable again (8c8323f). Nothing of the change itself moved.
The conflict which mattered was reported nowhere#944 replaced They take the road the rest of Rebuilt on the heads of the stack, not on the copies it carriedThis branch was carrying the revisions of #958 and #985 as they were when it was written. It is Moving the The Run on this head
The description is updated to match. |
8c8323f to
dd3cafe
Compare
|
Rebased, and the branch is mergeable again (dd3cafe). Nothing of the change itself moved. Rebuilt on the heads of the stackThe branch is rebuilt on the current head of #958 - five commits now, the fifth being Moving the The conflict which mattered was reported nowhere#973 landed on They use Run on this head
The description is updated to match. |
…wound parked as dependencies A change which waits for another one is parked and stays owned by the replay thread which parked it: getNextUpdate() is what hands it out again, to whichever thread clears the change it was waiting for. A replay which is unwound leaves that thread without the road back - it takes the next delivery off the shared queue - so the change stayed owned by a thread which never came back to it, while every redelivery of a change a replay thread owns is refused as a duplicate. On a domain which then went quiet that change was where this replica's ServerState, and every change behind it from every master, stopped. RemotePendingChanges gives back the changes the calling thread parked and unparks them in the same step, under both locks, so that only one road can hand a change out: a change released while it is still listed as waiting would be handed to the thread getNextUpdate() gives it to and to the thread which takes over the delivery which follows. The changes another thread parked are left alone, as everywhere else. replay() gives them back before the road of the change it was replaying runs, since that road restarts the session and a change which is still owned when the replication server sends it again is turned down. They are handed back without a failure counted against them - they were never applied here - and the session is restarted for them. Which change this thread owns is read before they are given back, and not after. The read is a plain map lookup which allocates nothing, and the give-back below it allocates - it builds the list of what it released and the line which reports each one. A throw from it on the road it exists for, a JVM which has just refused an allocation, would otherwise reach the last resort of replay() with nothing read, and the change this thread was replaying would be left listed, uncommitted and owned by a thread which is ending: the wedge OpenIdentityPlatform#922 is about, one road over. The order of the roads is unchanged - the parked changes are still handed back before the road of the change this thread was replaying restarts the session. Rebased onto the head of OpenIdentityPlatform#958, itself rebased onto master. That branch grew a fifth commit, "[OpenIdentityPlatform#922] Pin the ownership index on the hand-out and the give-back, and assert the alert an OutOfMemoryError leaves behind": the javadoc of the ownership index conflicted where both branches rewrote it and is merged, and the comment that commit wrote on the OutOfMemoryError arm of the ack - the give-back finds nothing once the change was committed - now says which parked changes the give-back does hand back on that road. Master's OpenIdentityPlatform#928 steps over a modify whose entry DN does not parse rather than asking for it again, so the end-to-end test builds the change it parks behind from an operation which is built and then refused, the way the tests of OpenIdentityPlatform#958 do since that merge; it takes the configured replay give-up budget, since OpenIdentityPlatform#901 replaced the setters it used.
…pool stopped had parked A change which waits for another one is parked and stays owned by the replay thread which parked it, while that thread goes back to the pool: getNextUpdate() is what hands it out again, to whichever thread clears the change it was waiting for. Changing the number of replay threads stops the whole pool and creates another one, so a thread which parked a change and went back to the queue was joined while it was idle and left recorded as the owner of that change - a thread which does not exist anymore, while every redelivery of a change a replay thread owns is refused as a duplicate. On a domain which then went quiet that change was where this replica's ServerState, and every change behind it from every master, stopped. The give-back of OpenIdentityPlatform#954 lives in the catch of replay() and no throw is on that road: the idle thread is not inside replay() at all, and the one which meets the shutdown flag returns from it normally. A replay thread now gives back what it parked on its way out, in every domain of this server: it is done by the thread which owns those changes, so the rule every road which reads ownership follows - a change is given back by the thread it was handed to and by nobody else (OpenIdentityPlatform#922) - holds here as well, and it is the one place which sees them all, since the pool is shared by every domain while a replay knows only the one it was replaying for. The session which brings them back is restarted by the thread which stopped the pool, once the new one is up, so that a change delivered again has a replay thread to be given to. Only the restart those changes asked for is run there: a restart which a failed replay requested is left to the replay thread which asked for it, which sits through the backoff this domain has reached on the change it can not apply (OpenIdentityPlatform#889). Fixes OpenIdentityPlatform#986
dd3cafe to
9766ccb
Compare
A change which waits for another one is parked -
RemotePendingChanges.checkDependencies()puts it independentChanges- and stays owned by the replay thread which parked it while that thread goes back to the pool and takes the changes which follow.getNextUpdate()is what hands it out again, to whichever replay thread clears the change it was waiting for, and that thread takes it over: the ownership of a parked change is deliberately held by a thread which is not replaying it right now.That holds for as long as the parking thread comes back to the pool. It does not hold when the pool is stopped.
MultimasterReplication.applyConfigurationChange()callsstopReplayThreads(), which sets the shutdown flag on everyReplayThreadand joins them, and thencreateReplayThreads()makes a new pool. A thread which parked a change and came back to the queue is idle when it is stopped, and it ended still recorded as the owner of that change - a thread which does not exist anymore - whileputRemoteUpdate()refuses every redelivery of a change a replay thread owns. On a domain which then goes quiet, that change is where this replica's ServerState, and every change behind it from every master, stops.The trigger is wider than the number of replay threads:
applyConfigurationChange()is the change listener of the wholecn=Multimaster Synchronizationentry and stops the pool before it looks at what changed, so a modification ofds-cfg-connection-timeoutdoes it too.Where the give-back of #954 does not reach
It lives in the
catchofreplay(), and neither road out of a stopped pool throws:replay()at all - it parked a change, met agetNextUpdate()which returned nothing, and returned to the queue;replayThreadShutdownat the top of the replay loop and leaves byabandonReplay(), which returns fromreplay()normally: what it was replaying is handed back, what it parked is not.The thread gives back what it parked
ReplayThread.run()gives back, in afinally, the changes this thread parked - in every domain of this server, throughLDAPReplicationDomain.giveBackChangesParkedByStoppingThread(). Done by the thread which owns them rather than by whoever stopped it, so the rule every road which reads ownership follows holds here as well: a change is given back by the thread it was handed to and by nobody else (#922). Doing it there is also what sees them all - the pool is shared by every domain of this server, and a thread may have parked a change in any of them, while areplay()knows only the domain it was replaying for. It covers the abandoned-replay road at the same time, so that road needs no give-back of its own.The loop of
run()moves intoreplayUntilStopped()so that thefinallydoes not re-indent it.The restart is for those changes and no others
A change which nobody owns is one only a new delivery brings back, so the session has to be restarted for it. It is run by the thread which stopped the pool, from
applyConfigurationChange(), once the new pool is up - a change delivered again while the pool is empty would wait in the replay queue rather than be replayed - and last in that method, so the sessions which start run on the configuration this change carries. A thread on its way out is not held for a session either.Only the restart these changes asked for is run there:
restartSessionForChangesGivenBackByStoppedThreads()acts on a flag of its own, set when a stopping thread actually gave something back. A restart which a failed replay requested is left to the replay thread which asked for it - that one sits through the backoff this domain has reached on the change it can not apply, and running it from the configuration change would spend that wait on a road which never failed (#889).The changes handed back stay listed and uncommitted, and stay among the changes the newer ones are checked against, the way a change whose replay failed does. They are handed back without a failure counted against them - they were never applied here - as on the road #954 takes. Message 318 is generalised: it is now the report of a parked change whose replay thread went away, whichever way it went.
Tests
UpdateOperationTest.aChangeParkedByAThreadThePoolStoppedIsDeliveredAgain: one replay thread, a change whose replay fails is the barrier, the change which follows it on the same entry is parked behind it by that thread, andds-cfg-num-update-replay-threadsis then changed - the pool is stopped and created again, the way an administrator has it. It watchesdependent-changes-sizego back to zero, then has the changes delivered again - nothing else sends them, they never travelled a session - and checks that the change which was parked is applied. It was watched failing on this branch without the fix:dependent-changes-size expected [0] but found [1].Run locally on this head:
UpdateOperationTest33/33. That class is where #941 putaChangeAStoppedReplayThreadHeldIsGivenBackAndDeliveredAgain, the test of the change a stopped replay thread was holding, and it walks theapplyConfigurationChange()this change adds a session restart to: it is the one test master has on the road this change touches, and it is green here.The classes around it in one run:
RemotePendingChangesTest24/24,AssuredReplicationPluginTest14/14,IsServerFailureTest23/23,ModifyConflictTest36/36,NamingConflictTest8/8,PendingChangesTest5/5,DependencyTest3/3 - 146 withUpdateOperationTest, green together, andStateMachineTest5/5 in a run of its own. A run of that set was what caught an earlier version of this change, where the configuration change ran every session restart which was pending rather than only the ones these changes asked for: it took the backoff of #889 away from the road which had asked for it.Rebased on the heads of the stack, and on master
The branch is rebuilt on the current heads of what it is stacked on - #958, five commits now, the fifth being
[#922] Pin the ownership index on the hand-out and the give-back, and assert the alert an OutOfMemoryError leaves behind, rebased ontomasterat 13d57e0, and the[#954]commit of #985 as it stands there, where the owner of the change is read before the parked changes are given back - and the[#986]commit applies on top of them with no conflict at all.Moving the
[#954]commit onto the current #958 met one conflict, in prose: the javadoc ofchangeBeingReplayedinRemotePendingChanges, where the fifth commit of #958 added the paragraph on who writes an entry of that index and #954 rewrote the sentence on the parked changes. It carries both.The conflict which mattered is one git reported nowhere. #973 landed on
masterwhile this waited: a modify whose entry DN does not parse is reported once and stepped over rather than thrown on, so its CSN goes into the ServerState on the delivery which met it.ModifyMsgWithAnUnparseableOperationDNis that message, and the two tests of this stack which need a change whose replay fails and stays listed as the barrier the parked change waits behind -aChangeParkedByAnUnwoundReplayIsDeliveredAgainandaChangeParkedByAThreadThePoolStoppedIsDeliveredAgain- were built on it. Both sides merge without a marker and both tests lose their premise: the barrier is committed, nothing is parked behind it, anddependent-changes-sizenever reaches 1. They useModifyMsgWhoseOperationRefusesAControlnow, which #973 introduced for exactly the case those tests want - an operation which is built and fails before the CSN of the change is read, so the change is kept out of the ServerState and asked for again - and which is what the rebase of #958 moved its own failing messages onto. Four lines, the barrier delivery and the redelivery of it in each test.The earlier round of this rebase, on #944, is still in place: the two tests shorten the
replay-give-up-delaybudget throughsetReplayGiveUpDelay(TEST_GIVE_UP_DELAY)withresetReplayGiveUpDelay()in thefinally, in the middle of the test rather than at its head, so that the phase which parks a change behind one whose replay keeps failing runs on the default budget.The class carries two ways of changing the number of replay threads:
setNumberOfReplayThreads(), which #941 added and which applies a mocked configuration so that it can be run from a thread of its own, and thesetNumUpdateReplayThreads()of this branch, which modifiesds-cfg-num-update-replay-threadson the configuration entry the way an administrator does. They are left as they are: what each test needs of that road is not the same.Stacked on #985
This is written on top of #985, which is where the give-back of a parked change lives, and on #958 under it, which gives ownership an owner. The first six commits here are those PRs'; only
[#986]is new. Once they are merged this PR is that one commit.Fixes #986
Ordinal
NOTE_REPLAY_PARKED_CHANGE_GIVEN_BACKis 318, which is where #985 - the [#954] work this is stacked on - has it. The commit which moved it from 316 is gone with the rebase: the message comes in on that PR's commit at 318 now, and this branch only generalises its text. It is not merely untidy to have the two apart: the generated constant is the key name without its ordinal, so the same message at 316 here and at 318 there declares the field twice inReplicationMessagesonce both are in, and the build stops - and git reports no conflict on it, the two lines sitting in different places in the file.#935, #945 and #959 have landed, so 310-314, 319-320 and 326-327 are master's. The open PRs which add to
replication.propertieshold the rest with nothing claimed twice:315-317#958, #985, #988 (and #981, which carries the same three [#922] messages) ·318#985, #988 ·321#964 ·322#968 ·323-324#977 ·325#981.