Skip to content

Commit d117128

Browse files
Make sure peek and process happen in the same state
1 parent 61a2248 commit d117128

File tree

4 files changed

+31
-1
lines changed

4 files changed

+31
-1
lines changed

BedrockCommand.h

+3
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ class BedrockCommand : public SQLiteCommand {
150150
// in `process` instead of peek, as it will always be escalated to leader
151151
const bool escalateImmediately;
152152

153+
// Record the state we were acting under in the last call to `peek` or `process`.
154+
SQLiteNode::State lastPeekedOrProcessedInState = SQLiteNode::UNKNOWN;
155+
153156
protected:
154157
// The plugin that owns this command.
155158
BedrockPlugin* _plugin;

BedrockCore.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ bool BedrockCore::isTimedOut(unique_ptr<BedrockCommand>& command) {
6868
BedrockCore::RESULT BedrockCore::peekCommand(unique_ptr<BedrockCommand>& command, bool exclusive) {
6969
AutoTimer timer(command, BedrockCommand::PEEK);
7070
BedrockServer::ScopedStateSnapshot snapshot(_server);
71+
command->lastPeekedOrProcessedInState = _server.getState();
7172

7273
// Convenience references to commonly used properties.
7374
const SData& request = command->request;
@@ -176,6 +177,15 @@ BedrockCore::RESULT BedrockCore::processCommand(unique_ptr<BedrockCommand>& comm
176177
AutoTimer timer(command, BedrockCommand::PROCESS);
177178
BedrockServer::ScopedStateSnapshot snapshot(_server);
178179

180+
// We need to be leading (including standing down) and we need to have peeked this command in the same set of
181+
// states, or we can't complete this command (we can't commit the command of we're not leading, and if we're
182+
// leading but were following when we peeked, we may try to read HTTPS requests we never made).
183+
if ((command->lastPeekedOrProcessedInState != SQLiteNode::LEADING && command->lastPeekedOrProcessedInState != SQLiteNode::STANDINGDOWN) ||
184+
(_server.getState() != SQLiteNode::LEADING && _server.getState() != SQLiteNode::STANDINGDOWN)) {
185+
return RESULT::SERVER_NOT_LEADING;
186+
}
187+
command->lastPeekedOrProcessedInState = _server.getState();
188+
179189
// Convenience references to commonly used properties.
180190
const SData& request = command->request;
181191
SData& response = command->response;

BedrockCore.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ class BedrockCore : public SQLiteCore {
1414
SHOULD_PROCESS = 2,
1515
NEEDS_COMMIT = 3,
1616
NO_COMMIT_REQUIRED = 4,
17-
ABANDONED_FOR_CHECKPOINT = 5
17+
ABANDONED_FOR_CHECKPOINT = 5,
18+
SERVER_NOT_LEADING = 6
1819
};
1920

2021
// Automatic timing class that records an entry corresponding to its lifespan.

BedrockServer.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,22 @@ void BedrockServer::worker(SQLitePool& dbPool,
10931093
} else if (result == BedrockCore::RESULT::NO_COMMIT_REQUIRED) {
10941094
// Nothing to do in this case, `command->complete` will be set and we'll finish as we fall out
10951095
// of this block.
1096+
} else if (result == BedrockCore::RESULT::SERVER_NOT_LEADING) {
1097+
// We won't write regardless.
1098+
core.rollback();
1099+
1100+
// If there are no HTTPS requests, we can just re-queue this command, otherwise, we will
1101+
// potentially run the same HTTPS requests twice.
1102+
if (command->httpsRequests.size()) {
1103+
SALERT("Server stopped leading while running command with HTTPS requests!");
1104+
command->response.methodLine = "500 Leader stopped leading";
1105+
server._reply(command);
1106+
break;
1107+
} else {
1108+
// Allow for an extra retry and start from the top, like with ABANDONED_FOR_CHECKPOINT.
1109+
SINFO("State changed before 'processCommand' but no HTTPS requests so retrying.");
1110+
++retry;
1111+
}
10961112
} else {
10971113
SERROR("processCommand (" << command->request.getVerb() << ") returned invalid result code: " << (int)result);
10981114
}

0 commit comments

Comments
 (0)