[#1055] Report a ReplicaOfflineMsg forwarded once it is written to the peer, not once it is queued - #1057
Conversation
… it is written to the peer, not once it is queued ServerWriter reported the forward as soon as Session.publish() had handed the message to the send queue of the session, and Session.close() drops that queue without draining it: a session thread busy writing an earlier buffer when the message was queued let the shutdown, released by that report, close the session with the message still queued, and the peer received the StopMsg alone. Session.publish(msg, whenWritten) runs the callback once, on the thread which wrote the message, after the write returned, and never for a message which was not written; it returns false for a message it neither wrote nor queued. ServerWriter reports the forward from that callback, and gives the peer up when the session refuses the message.
a863e3a to
c577881
Compare
|
@maximthomas rebased onto master at The conflict was in Re-run after the rebase: Not measured again: the three mutations of the table. The sources under test are those the table was measured on. |
Fixes #1055
ServerWriterreported a ReplicaOfflineMsg forwarded as soon asSession.publish()had handedit to the send queue of the session, and
Session.close()interrupts and joins the sessionthread without draining that queue. A session thread busy writing an earlier buffer when the
message was queued - a heartbeat, a TopologyMsg, an update to a peer whose TCP window is full -
let the shutdown, released by that report, reach
close()with the message still queued: thepeer received the StopMsg and never the message the shutdown had been told was forwarded, and
its
ChangeNumberIndexerkept the medium consistency point pinned to the last CSN of thereplica until the replica came back - the cost of #917 and #983, on a peer the wait believed it
had served. Recorded as a limitation by #919 and #947, whose wait proved the message was handed
to the session, not written.
The change
Session.publish(msg, whenWritten)runs the callback once, on the thread which wrote themessage, after the write returned - and never for a message which was not written: one whose
write failed, or one still queued when the session is closed. It returns
falsefor a messageit neither wrote nor queued: one the protocol version of the peer cannot carry, or one
published while the session is being closed.
publish(msg)delegates to it, so nothing elsechanges for the messages which have no callback.
ServerWriterreports the forward from that callback, so "forwarded" now means written to thesocket - the bytes are with the kernel, which delivers them whatever the process does next,
since no replication socket sets
SO_LINGER- which is what the grace period is spent on. Amessage the session refuses is reported as a give-up, as the message the writer's own filter
drops already was ([#917] Wait for every peer replication server to forward the ReplicaOfflineMsg #947): nothing will ever forward it, and the shutdown must not wait for it.
Session.close()is unchanged. What it drops is simply no longer reported as forwarded, so theshutdown does not reach
close()before the message is written unless the grace period runsout - and a session thread held inside a write is something
close()already waited for, inits
join().The tests
SessionTest(3, new) pins the contract of the callback on a session whose peer reads only whenthe test lets it: the callback does not run while the message is queued behind a write the peer
has not consumed, and runs once it has; a message with no encoding for the peer is refused and
its callback never runs; a session with no thread of its own writes on the publishing thread and
runs the callback before
publish()returns.ReplicationServerShutdownSyncTest(17, 2 new):thePeerStillReadingAnEarlierChangeIsToldTheReplicaWentOfflineBeforeItIsStoppedholds thesession thread serving a peer inside the write of a 4 MiB change - the peer reads nothing and
the socket buffers on both sides of its connection are bounded to 8 KiB - queues the
ReplicaOfflineMsg behind it, starts the shutdown, and lets the peer read only once the
shutdown has either closed the session or spent a second not doing so. It asserts that the
peer receives the message, that no forward was reported before the peer read, that one was
reported by the time the shutdown returned, and that the shutdown returned inside the grace
period. On master the peer receives the StopMsg alone, with the forward reported by the writer
and the shutdown over in 39 ms.
theShutdownStopsWaitingForAPeerWhoseProtocolCannotCarryTheMessageconnects a peer speakingprotocol version 7, for which the message has no encoding, and pins the give-up: the refusal
of the session must strike the peer off, not leave the shutdown waiting for a forward nobody
can report.
The peer of the first test is served over a connection the test established itself, as
ReplicationServer.runListen()serves an accepted one, because holding a thread inside a writeneeds the send buffer of the replication server's own socket bounded: the buffers a kernel picks
on its own on a loopback link absorb hundreds of kilobytes (and macOS keeps a few hundred on the
receiving side whatever it is asked for), so only an explicit
SO_SNDBUFon the accepted socketmakes the block deterministic.
Rebased onto master at
129fc4e346(#987), which split the handshake ofFakePeerReplicationServerinto the two phases its own cases need -
handshaking()stops before the TopologyMsg,completeHandshake()sends it - and turned the constructors into the factoriesconnected(...).This PR's two ways in ride on that shape:
connected(port, id, baseDN, generationId, windowSize, protocolVersion)for the peer speaking version 7, andconnected(session, socket, id, baseDN, generationId, windowSize)for the peer over the connection the test established itself. Both runthe first phase in a private constructor over a shared
start(), the second throughcompleted(),and
abandon()closes what a failed first phase leaves behind, as before. The class carries thetwo cases of #987 with it. On this head:
ReplicationServerShutdownSyncTest17/17,SessionTest3/3,DSRSShutdownSyncTest29/29.Every assertion was watched failing against the code it pins:
publish()returns, as on masterthePeerStillReadingAnEarlierChange...- the peer was never told that the replica went offline ... (forward reported by [99], the shutdown took 39 ms);theShutdownStopsWaitingForAPeerWhoseProtocolCannotCarryTheMessage- the writer let the shutdown wait for a peer whose protocol cannot carry the messageSessionTest.theCallbackRunsOnceTheMessageIsWrittenAndNotWhenItIsQueued- the callback did not run once the message had been written;thePeerStillReadingAnEarlierChange...- the message was written to the peer and nothing reported the forward; and the four existing cases which need a forward to be reported at alltheShutdownStopsWaitingForAPeerWhoseProtocolCannotCarryTheMessagealoneRegression:
mvn -Pprecommit -pl opendj-server-legacy verifyoverSessionTest,ReplicationServerShutdownSyncTest,DSRSShutdownSyncTest,ReplicationServerDynamicConfTest,ReplicationServerTest,ReplicationDomainTest,MonitorTest,GenerationIdTest,TopologyViewTest,ReplicationServerFailoverTest,StateMachineTest,ChangelogBackendTestCase,FileChangelogDBTest,ChangeNumberControlPluginTestCase,ReSyncTest,SchemaReplicationTest,AssuredReplicationServerTest,AssuredReplicationPluginTest- 512 tests, no failures - andHandshakeAbortRegistrationTest,HandshakeAbortGenerationIdTeston their own (they collide on the administration connector port in a long run) - 5, no failures.Left out
released a wait with no recipient recorded - the relay and A ReplicaOfflineMsg still queued behind an uncommitted change is recorded as sent, and the shutdown then waits its whole grace period for nothing #918 cases - at once; it is a
give-up now, which such a wait does not see, so a shutdown whose only peers predate protocol
version 8 spends the grace period on those announcements. A peer that old has no
ChangeNumberIndexerto tell, and the alternative is to keep calling a message nobody wroteforwarded.
Session.close()still blocks in itsjoin()for as long as the peer takes to read the bufferthe session thread is writing, and then in the synchronous write of the StopMsg, with no bound
but TCP's own: a peer which stops reading altogether holds the shutdown of the replication
server until the kernel gives the connection up. Older than this change, and its own issue.