Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix timeouts not working when not LEADING or FOLLOWING #2057

Merged
merged 1 commit into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions BedrockCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ uint64_t BedrockCore::_getRemainingTime(const unique_ptr<BedrockCommand>& comman
return isProcessing ? min(processTimeout, adjustedTimeout) : adjustedTimeout;
}

bool BedrockCore::isTimedOut(unique_ptr<BedrockCommand>& command) {
bool BedrockCore::isTimedOut(unique_ptr<BedrockCommand>& command, SQLite* db, const BedrockServer* server) {
try {
_getRemainingTime(command, false);
} catch (const SException& e) {
// Yep, timed out.
_handleCommandException(command, e);
_handleCommandException(command, e, db, server);
command->complete = true;
return true;
}
Expand Down Expand Up @@ -104,7 +104,7 @@ void BedrockCore::prePeekCommand(unique_ptr<BedrockCommand>& command, bool isBlo
STHROW("555 Timeout prePeeking command");
}
} catch (const SException& e) {
_handleCommandException(command, e);
_handleCommandException(command, e, &_db, &_server);
command->complete = true;
} catch (...) {
SALERT("Unhandled exception typename: " << SGetCurrentExceptionName() << ", command: " << request.methodLine);
Expand Down Expand Up @@ -186,7 +186,7 @@ BedrockCore::RESULT BedrockCore::peekCommand(unique_ptr<BedrockCommand>& command
}
} catch (const SException& e) {
command->repeek = false;
_handleCommandException(command, e);
_handleCommandException(command, e, &_db, &_server);
} catch (const SHTTPSManager::NotLeading& e) {
command->repeek = false;
returnValue = RESULT::SHOULD_PROCESS;
Expand Down Expand Up @@ -284,7 +284,7 @@ BedrockCore::RESULT BedrockCore::processCommand(unique_ptr<BedrockCommand>& comm
}
}
} catch (const SException& e) {
_handleCommandException(command, e);
_handleCommandException(command, e, &_db, &_server);
_db.rollback();
needsCommit = false;
} catch (const SQLite::constraint_error& e) {
Expand Down Expand Up @@ -353,7 +353,7 @@ void BedrockCore::postProcessCommand(unique_ptr<BedrockCommand>& command, bool i
STHROW("555 Timeout postProcessing command");
}
} catch (const SException& e) {
_handleCommandException(command, e);
_handleCommandException(command, e, &_db, &_server);
} catch (...) {
SALERT("Unhandled exception typename: " << SGetCurrentExceptionName() << ", command: " << request.methodLine);
command->response.methodLine = "500 Unhandled Exception";
Expand All @@ -367,7 +367,7 @@ void BedrockCore::postProcessCommand(unique_ptr<BedrockCommand>& command, bool i
_db.setQueryOnly(false);
}

void BedrockCore::_handleCommandException(unique_ptr<BedrockCommand>& command, const SException& e) {
void BedrockCore::_handleCommandException(unique_ptr<BedrockCommand>& command, const SException& e, SQLite* db, const BedrockServer* server) {
string msg = "Error processing command '" + command->request.methodLine + "' (" + e.what() + "), ignoring.";
if (!e.body.empty()) {
msg = msg + " Request body: " + e.body;
Expand Down Expand Up @@ -396,9 +396,11 @@ void BedrockCore::_handleCommandException(unique_ptr<BedrockCommand>& command, c
}

// Add the commitCount header to the response.
command->response["commitCount"] = to_string(_db.getCommitCount());
if (db) {
command->response["commitCount"] = to_string(db->getCommitCount());
}

if (_server.args.isSet("-extraExceptionLogging")) {
if (server && server->args.isSet("-extraExceptionLogging")) {
auto stack = e.details();
command->response["exceptionSource"] = stack.back();
}
Expand Down
6 changes: 3 additions & 3 deletions BedrockCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class BedrockCore : public SQLiteCore {
// Checks if a command has already timed out. Like `peekCommand` without doing any work. Returns `true` and sets
// the same command state as `peekCommand` would if the command has timed out. Returns `false` and does nothing if
// the command hasn't timed out.
bool isTimedOut(unique_ptr<BedrockCommand>& command);
static bool isTimedOut(unique_ptr<BedrockCommand>& command, SQLite* db = nullptr, const BedrockServer* server = nullptr);

void prePeekCommand(unique_ptr<BedrockCommand>& command, bool isBlockingCommitThread);

Expand Down Expand Up @@ -71,8 +71,8 @@ class BedrockCore : public SQLiteCore {
// Gets the amount of time remaining until this command times out. This is the difference between the command's
// 'timeout' value (or the default timeout, if not set) and the time the command was initially scheduled to run. If
// this time is already expired, this throws `555 Timeout`
uint64_t _getRemainingTime(const unique_ptr<BedrockCommand>& command, bool isProcessing);
static uint64_t _getRemainingTime(const unique_ptr<BedrockCommand>& command, bool isProcessing);

void _handleCommandException(unique_ptr<BedrockCommand>& command, const SException& e);
static void _handleCommandException(unique_ptr<BedrockCommand>& command, const SException& e, SQLite* db = nullptr, const BedrockServer* server = nullptr);
const BedrockServer& _server;
};
10 changes: 9 additions & 1 deletion BedrockServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,14 @@ void BedrockServer::runCommand(unique_ptr<BedrockCommand>&& _command, bool isBlo
// We just spin until the node looks ready to go. Typically, this doesn't happen expect briefly at startup.
size_t waitCount = 0;
while (_upgradeInProgress || (getState() != SQLiteNodeState::LEADING && getState() != SQLiteNodeState::FOLLOWING)) {

// It's feasible that our command times out in this loop. In this case, we do not have a DB object to pass.
// The only implication of this is the response does not get the commitCount attached to it.
if (BedrockCore::isTimedOut(command, nullptr, this)) {
_reply(command);
return;
}

// This sleep call is pretty ugly, but it should almost never happen. We're accepting the potential
// looping sleep call for the general case where we just check some bools and continue, instead of
// avoiding the sleep call but having every thread lock a mutex here on every loop.
Expand Down Expand Up @@ -880,7 +888,7 @@ void BedrockServer::runCommand(unique_ptr<BedrockCommand>&& _command, bool isBlo
// to be returned to the main queue, where they would have timed out in `peek`, but it was never called
// because the commands already had a HTTPS request attached, and then they were immediately re-sent to the
// sync queue, because of the QUORUM consistency requirement, resulting in an endless loop.
if (core.isTimedOut(command)) {
if (core.isTimedOut(command, &db, this)) {
_reply(command);
return;
}
Expand Down
5 changes: 5 additions & 0 deletions sqlitecluster/SQLiteCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
class SQLite;
class SQLiteNode;

#include <cstdint>
#include <string>

using namespace std;

class SQLiteCore {
public:
// Constructor that stores the database object we'll be working on.
Expand Down
9 changes: 6 additions & 3 deletions test/lib/BedrockTester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,10 +546,10 @@ void BedrockTester::freeDB() {
_db = nullptr;
}

string BedrockTester::readDB(const string& query, bool online)
string BedrockTester::readDB(const string& query, bool online, int64_t timeoutMS)
{
SQResult result;
bool success = readDB(query, result, online);
bool success = readDB(query, result, online, timeoutMS);
if (!success) {
return "";
}
Expand All @@ -565,7 +565,7 @@ string BedrockTester::readDB(const string& query, bool online)
return result.rows[0][0];
}

bool BedrockTester::readDB(const string& query, SQResult& result, bool online)
bool BedrockTester::readDB(const string& query, SQResult& result, bool online, int64_t timeoutMS)
{
if (ENABLE_HCTREE && online) {
string fixedQuery = query;
Expand All @@ -576,6 +576,9 @@ bool BedrockTester::readDB(const string& query, SQResult& result, bool online)
SData command("Query");
command["Query"] = fixedQuery;
command["Format"] = "JSON";
if (timeoutMS) {
command["timeout"] = to_string(timeoutMS);
}
auto commandResult = executeWaitMultipleData({command}, 1);
auto row0 = SParseJSONObject(commandResult[0].content)["rows"];
auto headerString = SParseJSONObject(commandResult[0].content)["headers"];
Expand Down
5 changes: 3 additions & 2 deletions test/lib/BedrockTester.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ class BedrockTester {

// Read from the DB file, without going through the bedrock server. Two interfaces are provided to maintain
// compatibility with the `SQLite` class.
string readDB(const string& query, bool online = true);
bool readDB(const string& query, SQResult& result, bool online = true);
// Note that timeoutMS only applies in HC-Tree mode. It is ignored in WAL2 mode.
string readDB(const string& query, bool online = true, int64_t timeoutMS = 0);
bool readDB(const string& query, SQResult& result, bool online = true, int64_t timeoutMS = 0);

// Closes and releases any existing DB file.
void freeDB();
Expand Down
Loading