628 lines
23 KiB
C++
628 lines
23 KiB
C++
/*
|
|
* TesterServer.cpp
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2026 Apple Inc. and the FoundationDB project authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "flow/ActorCollection.h"
|
|
#include "flow/CoroUtils.h"
|
|
#include "flow/DeterministicRandom.h"
|
|
#include "flow/Platform.h"
|
|
#include "flow/Trace.h"
|
|
#include "fdbrpc/Locality.h"
|
|
#include "fdbrpc/SimulatorProcessInfo.h"
|
|
#include "fdbrpc/simulator.h"
|
|
#include "fdbserver/core/FDBSimulatorProcessInfo.h"
|
|
#include "fdbclient/NativeAPI.actor.h"
|
|
#include "fdbserver/core/ServerDBInfo.h"
|
|
#include "fdbserver/core/WorkerInterface.h"
|
|
#include "TesterServer.h"
|
|
#include "fdbserver/tester/workloads.h"
|
|
|
|
namespace {
|
|
|
|
Standalone<VectorRef<KeyValueRef>> checkAllOptionsConsumed(VectorRef<KeyValueRef> options) {
|
|
static StringRef nothing = ""_sr;
|
|
Standalone<VectorRef<KeyValueRef>> unconsumed;
|
|
for (int i = 0; i < options.size(); i++) {
|
|
if (!(options[i].value == nothing)) {
|
|
TraceEvent(SevError, "OptionNotConsumed")
|
|
.detail("Key", options[i].key.toString().c_str())
|
|
.detail("Value", options[i].value.toString().c_str());
|
|
unconsumed.push_back_deep(unconsumed.arena(), options[i]);
|
|
}
|
|
}
|
|
return unconsumed;
|
|
}
|
|
|
|
Future<Reference<TestWorkload>> getWorkloadIface(WorkloadRequest work,
|
|
Reference<IClusterConnectionRecord> ccr,
|
|
VectorRef<KeyValueRef> options,
|
|
Reference<AsyncVar<ServerDBInfo> const> dbInfo) {
|
|
Reference<TestWorkload> workload;
|
|
Value testName = getOption(options, "testName"_sr, "no-test-specified"_sr);
|
|
WorkloadContext wcx;
|
|
wcx.clientId = work.clientId;
|
|
wcx.clientCount = work.clientCount;
|
|
wcx.ccr = ccr;
|
|
wcx.dbInfo = dbInfo;
|
|
wcx.options = options;
|
|
wcx.sharedRandomNumber = work.sharedRandomNumber;
|
|
wcx.rangesToCheck = work.rangesToCheck;
|
|
|
|
workload = IWorkloadFactory::create(testName.toString(), wcx);
|
|
if (workload) {
|
|
co_await workload->initialized();
|
|
}
|
|
|
|
auto unconsumedOptions = checkAllOptionsConsumed(workload ? workload->options : VectorRef<KeyValueRef>());
|
|
if (!workload || !unconsumedOptions.empty()) {
|
|
TraceEvent evt(SevError, "TestCreationError");
|
|
evt.detail("TestName", testName);
|
|
if (!workload) {
|
|
evt.detail("Reason", "Null workload");
|
|
fprintf(stderr,
|
|
"ERROR: Workload could not be created, perhaps testName (%s) is not a valid workload\n",
|
|
printable(testName).c_str());
|
|
} else {
|
|
evt.detail("Reason", "Not all options consumed");
|
|
fprintf(stderr, "ERROR: Workload had invalid options. The following were unrecognized:\n");
|
|
for (int i = 0; i < unconsumedOptions.size(); i++) {
|
|
fprintf(stderr,
|
|
" '%s' = '%s'\n",
|
|
unconsumedOptions[i].key.toString().c_str(),
|
|
unconsumedOptions[i].value.toString().c_str());
|
|
}
|
|
}
|
|
throw test_specification_invalid();
|
|
}
|
|
co_return workload;
|
|
}
|
|
|
|
Future<Reference<TestWorkload>> getWorkloadIface(WorkloadRequest work,
|
|
Reference<IClusterConnectionRecord> ccr,
|
|
Reference<AsyncVar<ServerDBInfo> const> dbInfo) {
|
|
WorkloadContext wcx;
|
|
std::vector<Future<Reference<TestWorkload>>> ifaces;
|
|
if (work.options.size() < 1) {
|
|
TraceEvent(SevError, "TestCreationError").detail("Reason", "No options provided");
|
|
fprintf(stderr, "ERROR: No options were provided for workload.\n");
|
|
throw test_specification_invalid();
|
|
}
|
|
|
|
wcx.clientId = work.clientId;
|
|
wcx.clientCount = work.clientCount;
|
|
wcx.sharedRandomNumber = work.sharedRandomNumber;
|
|
wcx.ccr = ccr;
|
|
wcx.dbInfo = dbInfo;
|
|
wcx.rangesToCheck = work.rangesToCheck;
|
|
// FIXME: Other stuff not filled in; why isn't this constructed here and passed down to the other
|
|
// getWorkloadIface()?
|
|
for (int i = 0; i < work.options.size(); i++) {
|
|
ifaces.push_back(getWorkloadIface(work, ccr, work.options[i], dbInfo));
|
|
}
|
|
co_await waitForAll(ifaces);
|
|
auto compound = makeReference<CompoundWorkload>(wcx);
|
|
for (int i = 0; i < work.options.size(); i++) {
|
|
compound->add(ifaces[i].getValue());
|
|
}
|
|
compound->addFailureInjection(work);
|
|
co_return compound;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/**
|
|
* Only works in simulation. This method prints all simulated processes in a human readable form to stdout. It groups
|
|
* processes by data center, data hall, zone, and machine (in this order).
|
|
*/
|
|
void printSimulatedTopology() {
|
|
if (!g_network->isSimulated()) {
|
|
return;
|
|
}
|
|
auto processes = g_simulator->getAllProcesses();
|
|
std::sort(processes.begin(), processes.end(), [](ISimulator::ProcessInfo* lhs, ISimulator::ProcessInfo* rhs) {
|
|
auto l = lhs->locality;
|
|
auto r = rhs->locality;
|
|
if (l.dcId() != r.dcId()) {
|
|
return l.dcId() < r.dcId();
|
|
}
|
|
if (l.dataHallId() != r.dataHallId()) {
|
|
return l.dataHallId() < r.dataHallId();
|
|
}
|
|
if (l.zoneId() != r.zoneId()) {
|
|
return l.zoneId() < r.zoneId();
|
|
}
|
|
if (l.machineId() != r.machineId()) {
|
|
return l.machineId() < r.machineId();
|
|
}
|
|
return lhs->address < rhs->address;
|
|
});
|
|
printf("Simulated Cluster Topology:\n");
|
|
printf("===========================\n");
|
|
Optional<Standalone<StringRef>> dcId, dataHallId, zoneId, machineId;
|
|
for (auto p : processes) {
|
|
std::string indent = "";
|
|
if (dcId != p->locality.dcId()) {
|
|
dcId = p->locality.dcId();
|
|
printf("%sdcId: %s\n", indent.c_str(), p->locality.describeDcId().c_str());
|
|
}
|
|
indent += " ";
|
|
if (dataHallId != p->locality.dataHallId()) {
|
|
dataHallId = p->locality.dataHallId();
|
|
printf("%sdataHallId: %s\n", indent.c_str(), p->locality.describeDataHall().c_str());
|
|
}
|
|
indent += " ";
|
|
if (zoneId != p->locality.zoneId()) {
|
|
zoneId = p->locality.zoneId();
|
|
printf("%szoneId: %s\n", indent.c_str(), p->locality.describeZone().c_str());
|
|
}
|
|
indent += " ";
|
|
if (machineId != p->locality.machineId()) {
|
|
machineId = p->locality.machineId();
|
|
printf("%smachineId: %s\n", indent.c_str(), p->locality.describeMachineId().c_str());
|
|
}
|
|
indent += " ";
|
|
printf("%sAddress: %s\n", indent.c_str(), p->address.toString().c_str());
|
|
indent += " ";
|
|
printf("%sClass: %s\n", indent.c_str(), getSimulatorProcessClass(p).toString().c_str());
|
|
printf("%sName: %s\n", indent.c_str(), p->name.c_str());
|
|
}
|
|
}
|
|
|
|
Future<Void> databaseWarmer(Database cx) {
|
|
while (true) {
|
|
Transaction tr(cx);
|
|
co_await tr.getReadVersion();
|
|
co_await delay(0.25);
|
|
}
|
|
co_return; // Unreachable but required for coroutine
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Tries indefinitely to commit a simple, self conflicting transaction
|
|
Future<Void> pingDatabase(Database cx) {
|
|
Transaction tr(cx);
|
|
while (true) {
|
|
bool needsErrorHandling = false;
|
|
Error caughtError;
|
|
|
|
try {
|
|
tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE);
|
|
tr.setOption(FDBTransactionOptions::LOCK_AWARE);
|
|
Optional<Value> v =
|
|
co_await tr.get(StringRef("/Liveness/" + deterministicRandom()->randomUniqueID().toString()));
|
|
tr.makeSelfConflicting();
|
|
co_await tr.commit();
|
|
co_return;
|
|
} catch (Error& e) {
|
|
TraceEvent("PingingDatabaseTransactionError").error(e);
|
|
caughtError = e;
|
|
needsErrorHandling = true;
|
|
}
|
|
|
|
if (needsErrorHandling) {
|
|
co_await tr.onError(caughtError);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Future<Void> testDatabaseLiveness(Database cx, double databasePingDelay, std::string context, double startDelay) {
|
|
co_await delay(startDelay);
|
|
while (true) {
|
|
Error caughtError;
|
|
|
|
try {
|
|
double start = now();
|
|
auto traceMsg = "PingingDatabaseLiveness_" + context;
|
|
TraceEvent(traceMsg.c_str()).log();
|
|
co_await timeoutError(pingDatabase(cx), databasePingDelay);
|
|
double pingTime = now() - start;
|
|
ASSERT(pingTime > 0);
|
|
TraceEvent(("PingingDatabaseLivenessDone_" + context).c_str()).detail("TimeTaken", pingTime);
|
|
co_await delay(databasePingDelay - pingTime);
|
|
} catch (Error& e) {
|
|
if (e.code() != error_code_actor_cancelled) {
|
|
TraceEvent(SevError, ("PingingDatabaseLivenessError_" + context).c_str())
|
|
.error(e)
|
|
.detail("PingDelay", databasePingDelay);
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
template <class T>
|
|
void sendResult(ReplyPromise<T>& reply, Optional<ErrorOr<T>> const& result) {
|
|
auto& res = result.get();
|
|
if (res.isError())
|
|
reply.sendError(res.getError());
|
|
else
|
|
reply.send(res.get());
|
|
}
|
|
|
|
Future<Reference<TestWorkload>> getConsistencyCheckUrgentWorkloadIface(WorkloadRequest work,
|
|
Reference<IClusterConnectionRecord> ccr,
|
|
Reference<AsyncVar<ServerDBInfo> const> dbInfo) {
|
|
WorkloadContext wcx;
|
|
wcx.clientId = work.clientId;
|
|
wcx.clientCount = work.clientCount;
|
|
wcx.sharedRandomNumber = work.sharedRandomNumber;
|
|
wcx.ccr = ccr;
|
|
wcx.dbInfo = dbInfo;
|
|
wcx.rangesToCheck = work.rangesToCheck;
|
|
Reference<TestWorkload> iface = co_await getWorkloadIface(work, ccr, work.options[0], dbInfo);
|
|
co_return iface;
|
|
}
|
|
|
|
Future<Void> runConsistencyCheckUrgentWorkloadAsync(Database cx,
|
|
WorkloadInterface workIface,
|
|
Reference<TestWorkload> workload) {
|
|
ReplyPromise<Void> jobReq = co_await workIface.start.getFuture();
|
|
|
|
try {
|
|
TraceEvent(SevInfo, "ConsistencyCheckUrgent_TesterWorkloadReceived", workIface.id())
|
|
.detail("WorkloadName", workload->description())
|
|
.detail("ClientCount", workload->clientCount)
|
|
.detail("ClientId", workload->clientId);
|
|
co_await workload->start(cx);
|
|
jobReq.send(Void());
|
|
} catch (Error& e) {
|
|
if (e.code() == error_code_actor_cancelled) {
|
|
throw e;
|
|
}
|
|
TraceEvent(SevInfo, "ConsistencyCheckUrgent_TesterWorkloadError", workIface.id())
|
|
.errorUnsuppressed(e)
|
|
.detail("WorkloadName", workload->description())
|
|
.detail("ClientCount", workload->clientCount)
|
|
.detail("ClientId", workload->clientId);
|
|
jobReq.sendError(consistency_check_urgent_task_failed());
|
|
}
|
|
co_return;
|
|
}
|
|
|
|
Future<Void> testerServerConsistencyCheckerUrgentWorkload(WorkloadRequest work,
|
|
Reference<IClusterConnectionRecord> ccr,
|
|
Reference<AsyncVar<struct ServerDBInfo> const> dbInfo) {
|
|
WorkloadInterface workIface;
|
|
bool replied = false;
|
|
|
|
try {
|
|
Database cx = openDBOnServer(dbInfo);
|
|
co_await delay(1.0);
|
|
Reference<TestWorkload> workload = co_await getConsistencyCheckUrgentWorkloadIface(work, ccr, dbInfo);
|
|
Future<Void> test = runConsistencyCheckUrgentWorkloadAsync(cx, workIface, workload);
|
|
work.reply.send(workIface);
|
|
replied = true;
|
|
co_await test;
|
|
} catch (Error& e) {
|
|
if (e.code() == error_code_actor_cancelled) {
|
|
throw e;
|
|
}
|
|
TraceEvent(SevWarn, "ConsistencyCheckUrgent_TesterRunWorkloadFailed").errorUnsuppressed(e);
|
|
if (!replied) {
|
|
work.reply.sendError(e);
|
|
}
|
|
}
|
|
co_return;
|
|
}
|
|
|
|
Future<Void> runWorkloadAsync(Database cx,
|
|
WorkloadInterface workIface,
|
|
Reference<TestWorkload> workload,
|
|
double databasePingDelay) {
|
|
Optional<ErrorOr<Void>> setupResult;
|
|
Optional<ErrorOr<Void>> startResult;
|
|
Optional<ErrorOr<CheckReply>> checkResult;
|
|
ReplyPromise<Void> setupReq;
|
|
ReplyPromise<Void> startReq;
|
|
ReplyPromise<CheckReply> checkReq;
|
|
ReplyPromise<std::vector<PerfMetric>> metricsReq;
|
|
|
|
TraceEvent("TestBeginAsync", workIface.id())
|
|
.detail("Workload", workload->description())
|
|
.detail("DatabasePingDelay", databasePingDelay);
|
|
|
|
Future<Void> databaseError =
|
|
databasePingDelay == 0.0 ? Never() : testDatabaseLiveness(cx, databasePingDelay, "RunWorkloadAsync");
|
|
|
|
ReplyPromise<Void> stopReq;
|
|
while (true) {
|
|
int action = 0;
|
|
co_await Choose()
|
|
.When(workIface.setup.getFuture(),
|
|
[&](ReplyPromise<Void> const& req) {
|
|
setupReq = req;
|
|
action = 1;
|
|
})
|
|
.When(workIface.start.getFuture(),
|
|
[&](ReplyPromise<Void> const& req) {
|
|
startReq = req;
|
|
action = 2;
|
|
})
|
|
.When(workIface.check.getFuture(),
|
|
[&](ReplyPromise<CheckReply> const& req) {
|
|
checkReq = req;
|
|
action = 3;
|
|
})
|
|
.When(workIface.metrics.getFuture(),
|
|
[&](ReplyPromise<std::vector<PerfMetric>> const& req) {
|
|
metricsReq = req;
|
|
action = 4;
|
|
})
|
|
.When(workIface.stop.getFuture(),
|
|
[&](ReplyPromise<Void> const& r) {
|
|
stopReq = r;
|
|
action = 5;
|
|
})
|
|
.run();
|
|
|
|
if (action == 5) {
|
|
stopReq.send(Void());
|
|
break;
|
|
}
|
|
|
|
if (action == 1) {
|
|
// setup
|
|
printf("Test received trigger for setup...\n");
|
|
TraceEvent("TestSetupBeginning", workIface.id()).detail("Workload", workload->description());
|
|
if (!setupResult.present()) {
|
|
try {
|
|
co_await (workload->setup(cx) || databaseError);
|
|
TraceEvent("TestSetupComplete", workIface.id()).detail("Workload", workload->description());
|
|
setupResult = Void();
|
|
} catch (Error& e) {
|
|
setupResult = operation_failed();
|
|
TraceEvent(SevError, "TestSetupError", workIface.id())
|
|
.error(e)
|
|
.detail("Workload", workload->description());
|
|
if (e.code() == error_code_please_reboot || e.code() == error_code_please_reboot_delete)
|
|
throw;
|
|
}
|
|
}
|
|
sendResult(setupReq, setupResult);
|
|
} else if (action == 2) {
|
|
// start
|
|
if (!startResult.present()) {
|
|
try {
|
|
TraceEvent("TestStarting", workIface.id()).detail("Workload", workload->description());
|
|
co_await (workload->start(cx) || databaseError);
|
|
startResult = Void();
|
|
} catch (Error& e) {
|
|
startResult = operation_failed();
|
|
if (e.code() == error_code_please_reboot || e.code() == error_code_please_reboot_delete)
|
|
throw;
|
|
TraceEvent(SevError, "TestFailure", workIface.id())
|
|
.errorUnsuppressed(e)
|
|
.detail("Reason", "Error starting workload")
|
|
.detail("Workload", workload->description());
|
|
}
|
|
TraceEvent("TestComplete", workIface.id())
|
|
.detail("Workload", workload->description())
|
|
.detail("OK", !startResult.get().isError());
|
|
printf("%s complete\n", workload->description().c_str());
|
|
}
|
|
sendResult(startReq, startResult);
|
|
} else if (action == 3) {
|
|
// check
|
|
if (!checkResult.present()) {
|
|
try {
|
|
TraceEvent("TestChecking", workIface.id()).detail("Workload", workload->description());
|
|
bool check = co_await timeoutError(workload->check(cx), workload->getCheckTimeout());
|
|
checkResult = CheckReply{ (!startResult.present() || !startResult.get().isError()) && check };
|
|
} catch (Error& e) {
|
|
checkResult = operation_failed();
|
|
if (e.code() == error_code_please_reboot || e.code() == error_code_please_reboot_delete)
|
|
throw;
|
|
TraceEvent(SevError, "TestFailure", workIface.id())
|
|
.error(e)
|
|
.detail("Reason", "Error checking workload")
|
|
.detail("Workload", workload->description());
|
|
}
|
|
TraceEvent("TestCheckComplete", workIface.id()).detail("Workload", workload->description());
|
|
}
|
|
sendResult(checkReq, checkResult);
|
|
} else if (action == 4) {
|
|
// metrics
|
|
try {
|
|
std::vector<PerfMetric> m = co_await workload->getMetrics();
|
|
TraceEvent("WorkloadSendMetrics", workIface.id()).detail("Count", m.size());
|
|
metricsReq.send(m);
|
|
} catch (Error& e) {
|
|
if (e.code() == error_code_please_reboot || e.code() == error_code_please_reboot_delete)
|
|
throw;
|
|
TraceEvent(SevError, "WorkloadSendMetrics", workIface.id()).error(e);
|
|
metricsReq.sendError(operation_failed());
|
|
}
|
|
}
|
|
}
|
|
co_return;
|
|
}
|
|
|
|
Future<Void> testerServerWorkload(WorkloadRequest work,
|
|
Reference<IClusterConnectionRecord> ccr,
|
|
Reference<AsyncVar<struct ServerDBInfo> const> dbInfo,
|
|
LocalityData locality) {
|
|
WorkloadInterface workIface;
|
|
bool replied = false;
|
|
Database cx;
|
|
|
|
try {
|
|
std::map<std::string, std::string> details;
|
|
details["WorkloadTitle"] = printable(work.title);
|
|
details["ClientId"] = format("%d", work.clientId);
|
|
details["ClientCount"] = format("%d", work.clientCount);
|
|
details["WorkloadTimeout"] = format("%d", work.timeout);
|
|
startRole(Role::TESTER, workIface.id(), UID(), details);
|
|
|
|
if (work.useDatabase) {
|
|
cx = Database::createDatabase(ccr, ApiVersion::LATEST_VERSION, IsInternal::True, locality);
|
|
co_await delay(1.0);
|
|
}
|
|
|
|
// add test for "done" ?
|
|
TraceEvent("WorkloadReceived", workIface.id()).detail("Title", work.title);
|
|
Reference<TestWorkload> workload = co_await getWorkloadIface(work, ccr, dbInfo);
|
|
if (!workload) {
|
|
TraceEvent("TestCreationError").detail("Reason", "Workload could not be created");
|
|
fprintf(stderr, "ERROR: The workload could not be created.\n");
|
|
throw test_specification_invalid();
|
|
}
|
|
Future<Void> test = runWorkloadAsync(cx, workIface, workload, work.databasePingDelay) ||
|
|
traceRole(Role::TESTER, workIface.id());
|
|
work.reply.send(workIface);
|
|
replied = true;
|
|
|
|
if (work.timeout > 0) {
|
|
test = timeoutError(test, work.timeout);
|
|
}
|
|
|
|
co_await test;
|
|
|
|
endRole(Role::TESTER, workIface.id(), "Complete");
|
|
} catch (Error& e) {
|
|
TraceEvent(SevDebug, "TesterWorkloadFailed").errorUnsuppressed(e);
|
|
if (!replied) {
|
|
if (e.code() == error_code_test_specification_invalid)
|
|
work.reply.sendError(e);
|
|
else
|
|
work.reply.sendError(operation_failed());
|
|
}
|
|
|
|
bool ok = e.code() == error_code_please_reboot || e.code() == error_code_please_reboot_delete ||
|
|
e.code() == error_code_actor_cancelled;
|
|
endRole(Role::TESTER, workIface.id(), "Error", ok, e);
|
|
|
|
if (e.code() != error_code_test_specification_invalid && e.code() != error_code_timed_out) {
|
|
throw; // fatal errors will kill the testerServer as well
|
|
}
|
|
}
|
|
co_return;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Future<Void> testerServerCore(TesterInterface const& interf,
|
|
Reference<IClusterConnectionRecord> const& ccr,
|
|
Reference<AsyncVar<struct ServerDBInfo> const> const& dbInfo,
|
|
LocalityData const& locality,
|
|
Optional<std::string> const& expectedWorkLoad) {
|
|
// C++20 coroutine safety: const& parameters only store the reference in the coroutine frame,
|
|
// not the object. The referred-to object may be destroyed after the coroutine suspends
|
|
// (e.g. local variables in a caller's if-block, or temporaries from default arguments).
|
|
// Copy all const& parameters to ensure they survive across suspend points.
|
|
TesterInterface interfCopy = interf;
|
|
Reference<IClusterConnectionRecord> ccrCopy = ccr;
|
|
Reference<AsyncVar<struct ServerDBInfo> const> dbInfoCopy = dbInfo;
|
|
LocalityData localityCopy = locality;
|
|
Optional<std::string> expectedWorkLoadCopy = expectedWorkLoad;
|
|
|
|
PromiseStream<Future<Void>> addWorkload;
|
|
Future<Void> workerFatalError = actorCollection(addWorkload.getFuture());
|
|
|
|
// Dedicated to consistencyCheckerUrgent
|
|
// At any time, we only allow at most 1 consistency checker workload on a server
|
|
std::pair<int64_t, Future<Void>> consistencyCheckerUrgentTester = std::make_pair(0, Future<Void>());
|
|
|
|
TraceEvent("StartingTesterServerCore", interfCopy.id())
|
|
.detail("ExpectedWorkload", expectedWorkLoadCopy.present() ? expectedWorkLoadCopy.get() : "[Unset]");
|
|
|
|
while (true) {
|
|
int action = 0;
|
|
WorkloadRequest workReq;
|
|
Future<Void> checkerDone =
|
|
consistencyCheckerUrgentTester.second.isValid() ? consistencyCheckerUrgentTester.second : Never();
|
|
|
|
co_await Choose()
|
|
.When(workerFatalError, [&](Void const&) { action = 1; })
|
|
.When(checkerDone, [&](Void const&) { action = 2; })
|
|
.When(interfCopy.recruitments.getFuture(),
|
|
[&](WorkloadRequest const& work) {
|
|
workReq = work;
|
|
action = 3;
|
|
})
|
|
.run();
|
|
|
|
if (action == 1) {
|
|
break;
|
|
}
|
|
|
|
if (action == 2) {
|
|
ASSERT(consistencyCheckerUrgentTester.first != 0);
|
|
TraceEvent(SevInfo, "ConsistencyCheckUrgent_TesterWorkloadEnd", interfCopy.id())
|
|
.detail("ConsistencyCheckerId", consistencyCheckerUrgentTester.first);
|
|
consistencyCheckerUrgentTester = std::make_pair(0, Future<Void>()); // reset
|
|
continue;
|
|
}
|
|
|
|
if (action == 3) {
|
|
if (expectedWorkLoadCopy.present() && expectedWorkLoadCopy.get() != workReq.title) {
|
|
TraceEvent(SevError, "StartingTesterServerCoreUnexpectedWorkload", interfCopy.id())
|
|
.detail("ClientId", workReq.clientId)
|
|
.detail("ClientCount", workReq.clientCount)
|
|
.detail("ExpectedWorkLoad", expectedWorkLoadCopy.get())
|
|
.detail("WorkLoad", workReq.title);
|
|
// Drop the workload
|
|
} else if (workReq.title == "ConsistencyCheckUrgent") {
|
|
// The workload is a consistency checker urgent workload
|
|
if (workReq.sharedRandomNumber == consistencyCheckerUrgentTester.first) {
|
|
// A single req can be sent for multiple times. In this case, the sharedRandomNumber is same as
|
|
// the existing one. For this scenario, we reply an error. This case should be rare.
|
|
TraceEvent(SevWarn, "ConsistencyCheckUrgent_TesterDuplicatedRequest", interfCopy.id())
|
|
.detail("ConsistencyCheckerId", workReq.sharedRandomNumber)
|
|
.detail("ClientId", workReq.clientId)
|
|
.detail("ClientCount", workReq.clientCount);
|
|
workReq.reply.sendError(consistency_check_urgent_duplicate_request());
|
|
} else {
|
|
// When the req.sharedRandomNumber is different from the existing one, the cluster has muiltiple
|
|
// consistencycheckurgent roles at the same time. Evenutally, the cluster will have only one
|
|
// consistencycheckurgent role in a stable state. So, in this case, we simply let the new request to
|
|
// overwrite the old request. After the work is destroyed, the broken_promise will be replied.
|
|
if (consistencyCheckerUrgentTester.second.isValid() &&
|
|
!consistencyCheckerUrgentTester.second.isReady()) {
|
|
TraceEvent(SevWarnAlways, "ConsistencyCheckUrgent_TesterWorkloadConflict", interfCopy.id())
|
|
.detail("ExistingConsistencyCheckerId", consistencyCheckerUrgentTester.first)
|
|
.detail("ArrivingConsistencyCheckerId", workReq.sharedRandomNumber)
|
|
.detail("ClientId", workReq.clientId)
|
|
.detail("ClientCount", workReq.clientCount);
|
|
}
|
|
consistencyCheckerUrgentTester =
|
|
std::make_pair(workReq.sharedRandomNumber,
|
|
testerServerConsistencyCheckerUrgentWorkload(workReq, ccrCopy, dbInfoCopy));
|
|
TraceEvent(SevInfo, "ConsistencyCheckUrgent_TesterWorkloadInitialized", interfCopy.id())
|
|
.detail("ConsistencyCheckerId", consistencyCheckerUrgentTester.first)
|
|
.detail("ClientId", workReq.clientId)
|
|
.detail("ClientCount", workReq.clientCount);
|
|
}
|
|
} else {
|
|
addWorkload.send(testerServerWorkload(workReq, ccrCopy, dbInfoCopy, localityCopy));
|
|
}
|
|
}
|
|
}
|
|
co_return;
|
|
}
|