Fix fork when COMMIT_TRANSACTION is dropped #2060
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Details
This fixes a possible fork where followers fail to handle a commit sent by leader.
The existing behavior is that leader sends a BEGIN_TRANSACTION followed by a COMMIT_TRANSACTION to followers. When it is shutting down, one of these pairs is the final transaction sent to followers, and then the sockets to followers are closed.
The leader behavior is correct, the problem exists on followers.
When the follower is reading data off the socket, it reads the data that was sent before closing, and then marks the data as closed:
Bedrock/libstuff/STCPManager.cpp
Lines 119 to 139 in a88cf54
When the SQLitePeer object sees that the socket has been closed, it calls
reset()
on it's socket and returnsSOCKET_CLOSED
toSQLiteNode
.The problem is that it's possible that the
COMMIT_TRANSACTION
message is read immediately before theclose
on the socket, in the same call topostPoll
(it's feasible even more messages are read on the same call, but they would all have the same issue).We only tried to handle messages in the case that SQLitePeer::postPoll returned
PeerPostPollStatus::OK
:Bedrock/sqlitecluster/SQLiteNode.cpp
Line 2673 in a88cf54
So in this case, the
COMMIT_TRANSACTION
message was discarded.However, this could lead to a fork, because the follower would never commit the last transaction sent by leader. Then when leader came back up, it could be out of sync with the follower.
This was easily reproducible on the HC-Tree branch, not because of any property of HC-Tree, but because the tests start and stop leader a lot more often for HC-Tree, making this occurrence more likely.
The fix was to:
postPoll
even forPeerPostPollStatus::JUST_CONNECTED
,PeerPostPollStatus::SOCKET_ERROR
, andPeerPostPollStatus::SOCKET_CLOSED
return values fromSQLitePeer::postPoll
(onlySOCKET_CLOSED
actually exhibited the problem here, but the others are for completeness). I extracted the logic here to a method called_processPeerMessages
for this.reset
on theSQLitePeer
's socket before handling them. I moved the call toreset()
out ofSQLitePeer::postPoll
and intoSQLiteNode::postPoll
.Fixed Issues
https://github.com/Expensify/Expensify/issues/337537
Tests
Internal Testing Reminder: when changing bedrock, please compile auth against your new changes