296 lines
7.6 KiB
C++
296 lines
7.6 KiB
C++
/*
|
|
* ThreadSafety.actor.cpp
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2022 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 "fdbrpc/simulator.h"
|
|
#include "flow/DeterministicRandom.h"
|
|
#include "fdbserver/TesterInterface.actor.h"
|
|
#include "fdbserver/QuietDatabase.h"
|
|
#include "fdbserver/ServerDBInfo.h"
|
|
#include "fdbclient/ThreadSafeTransaction.h"
|
|
#include "fdbclient/MultiVersionTransaction.h"
|
|
#include "fdbserver/workloads/workloads.actor.h"
|
|
#include "flow/actorcompiler.h" // This must be the last #include.
|
|
|
|
struct ThreadSafetyWorkload;
|
|
|
|
// Parameters passed to each thread
|
|
struct ThreadInfo {
|
|
int id;
|
|
ThreadSafetyWorkload* self;
|
|
|
|
Promise<Void> done;
|
|
DeterministicRandom random;
|
|
|
|
ThreadInfo(int id, ThreadSafetyWorkload* self)
|
|
: id(id), self(self), random(deterministicRandom()->randomInt(1, 1e9)) {}
|
|
};
|
|
|
|
// A thread barrier implementation. Reached() method blocks until the required number of threads reach it.
|
|
struct Barrier {
|
|
Mutex mutex;
|
|
std::vector<Event*> events;
|
|
|
|
int numRequired;
|
|
int numReached;
|
|
|
|
Barrier() : numRequired(0), numReached(0) {}
|
|
|
|
~Barrier() { fire(); }
|
|
|
|
void decrementNumRequired() {
|
|
mutex.enter();
|
|
if (--numRequired == numReached)
|
|
fire();
|
|
mutex.leave();
|
|
}
|
|
|
|
void setNumRequired(int numRequired) {
|
|
mutex.enter();
|
|
this->numRequired = numRequired;
|
|
if (numRequired > 0 && numRequired <= numReached)
|
|
fire();
|
|
mutex.leave();
|
|
}
|
|
|
|
// Called by each thread to signal that the barrier has been reached.
|
|
// Blocks until <numRequired> threads have called this function.
|
|
void reached() {
|
|
mutex.enter();
|
|
bool ready = (++numReached == numRequired);
|
|
Event* myEvent = nullptr;
|
|
|
|
if (ready)
|
|
fire();
|
|
else {
|
|
myEvent = new Event();
|
|
events.push_back(myEvent);
|
|
}
|
|
mutex.leave();
|
|
|
|
if (!ready) {
|
|
myEvent->block();
|
|
delete myEvent;
|
|
}
|
|
}
|
|
|
|
private:
|
|
void fire() {
|
|
numReached = 0;
|
|
for (int i = 0; i < events.size(); ++i)
|
|
events[i]->set();
|
|
|
|
events.clear();
|
|
}
|
|
};
|
|
|
|
// A workload which uses the thread safe API from multiple threads
|
|
struct ThreadSafetyWorkload : TestWorkload {
|
|
int threadsPerClient;
|
|
double threadDuration;
|
|
|
|
// Used to generate keys for the workload. This is the number of keys that will be available for operations.
|
|
int numKeys;
|
|
|
|
bool success;
|
|
bool stopped;
|
|
|
|
Mutex mutex;
|
|
Barrier commitBarrier;
|
|
|
|
Reference<IDatabase> db;
|
|
|
|
// ThreadFutures are not thread safe, so they must be copied in other threads inside of mutexes
|
|
ThreadFuture<Void> commitFuture;
|
|
|
|
Reference<ITransaction> tr;
|
|
|
|
ThreadSafetyWorkload(WorkloadContext const& wcx) : TestWorkload(wcx), stopped(false) {
|
|
|
|
threadsPerClient = getOption(options, "threadsPerClient"_sr, 3);
|
|
threadDuration = getOption(options, "threadDuration"_sr, 60.0);
|
|
numKeys = getOption(options, "numKeys"_sr, 100);
|
|
|
|
commitBarrier.setNumRequired(threadsPerClient);
|
|
|
|
success = true;
|
|
|
|
// This test is not deterministic
|
|
noUnseed = true;
|
|
}
|
|
|
|
std::string description() const override { return "ThreadSafety"; }
|
|
|
|
Future<Void> setup(Database const& cx) override { return Void(); }
|
|
|
|
Future<Void> start(Database const& cx) override { return _start(cx, this); }
|
|
|
|
ACTOR Future<Void> _start(Database cx, ThreadSafetyWorkload* self) {
|
|
state std::vector<ThreadInfo*> threadInfo;
|
|
|
|
Reference<IDatabase> dbRef =
|
|
wait(unsafeThreadFutureToFuture(ThreadSafeDatabase::createFromExistingDatabase(cx)));
|
|
self->db = dbRef;
|
|
|
|
if (deterministicRandom()->coinflip()) {
|
|
MultiVersionApi::api->selectApiVersion(cx->apiVersion.version());
|
|
self->db = MultiVersionDatabase::debugCreateFromExistingDatabase(dbRef);
|
|
}
|
|
|
|
state int i;
|
|
for (i = 0; i < self->threadsPerClient; ++i) {
|
|
threadInfo.push_back(new ThreadInfo(i, self));
|
|
g_network->startThread(self->threadStart, threadInfo[i]);
|
|
}
|
|
|
|
wait(delay(self->threadDuration));
|
|
|
|
// Signals the threads to stop
|
|
self->mutex.enter();
|
|
self->stopped = true;
|
|
self->mutex.leave();
|
|
|
|
for (i = 0; i < threadInfo.size(); ++i) {
|
|
try {
|
|
wait(threadInfo[i]->done.getFuture());
|
|
} catch (Error& e) {
|
|
self->success = false;
|
|
printf("Thread %d.%d failed: %s\n", self->clientId, i, e.name());
|
|
TraceEvent(SevError, "ThreadSafety_ThreadFailed").error(e);
|
|
}
|
|
|
|
delete threadInfo[i];
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
THREAD_FUNC threadStart(void* arg) {
|
|
ThreadInfo* info = (ThreadInfo*)arg;
|
|
|
|
Error error(error_code_success);
|
|
try {
|
|
info->self->runTest(info);
|
|
} catch (Error& e) {
|
|
error = e;
|
|
}
|
|
|
|
info->self->commitBarrier.decrementNumRequired();
|
|
|
|
// Signal completion back to the main thread
|
|
onMainThreadVoid([=]() {
|
|
if (error.code() != error_code_success)
|
|
info->done.sendError(error);
|
|
else
|
|
info->done.send(Void());
|
|
});
|
|
|
|
THREAD_RETURN;
|
|
}
|
|
|
|
Key getRandomKey(DeterministicRandom& random) {
|
|
return StringRef(format("ThreadSafetyKey%010d", random.randomInt(0, numKeys)));
|
|
}
|
|
|
|
void runTest(ThreadInfo* info) {
|
|
// Create a new transaction
|
|
mutex.enter();
|
|
if (!tr) {
|
|
try {
|
|
tr = db->createTransaction();
|
|
} catch (Error&) {
|
|
mutex.leave();
|
|
throw;
|
|
}
|
|
}
|
|
mutex.leave();
|
|
|
|
loop {
|
|
// Perform a sequence of random operations
|
|
for (int i = 0; i < info->random.randomInt(1, 10); ++i) {
|
|
int operation = info->random.randomInt(0, 6);
|
|
|
|
try {
|
|
if (operation == 0)
|
|
tr->set(getRandomKey(info->random),
|
|
StringRef(std::string(info->random.randomInt(0, 100), 'x')));
|
|
else if (operation == 1)
|
|
tr->get(getRandomKey(info->random)).getBlocking();
|
|
else if (operation == 2)
|
|
tr->getKey(KeySelectorRef(getRandomKey(info->random),
|
|
info->random.randomInt(0, 2) == 1,
|
|
info->random.randomInt(-10, 11)))
|
|
.getBlocking();
|
|
else if (operation == 3) {
|
|
Key key1 = getRandomKey(info->random);
|
|
Key key2 = getRandomKey(info->random);
|
|
GetRangeLimits limits(info->random.randomInt(1, 1000), info->random.randomInt(1, 1e6));
|
|
tr->getRange(KeyRangeRef(std::min(key1, key2), std::max(key1, key2)),
|
|
limits,
|
|
info->random.randomInt(0, 2) != 0,
|
|
info->random.randomInt(0, 2) != 0)
|
|
.getBlocking();
|
|
} else if (operation == 4)
|
|
tr->clear(getRandomKey(info->random));
|
|
else if (operation == 5) {
|
|
Key key1 = getRandomKey(info->random);
|
|
Key key2 = getRandomKey(info->random);
|
|
tr->clear(KeyRangeRef(std::min(key1, key2), std::max(key1, key2)));
|
|
}
|
|
} catch (Error&) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
commitBarrier.reached();
|
|
|
|
// One thread starts a commit, and all threads wait on that commit
|
|
mutex.enter();
|
|
if (!commitFuture.isValid())
|
|
commitFuture = tr->commit();
|
|
ThreadFuture<Void> commit = commitFuture;
|
|
mutex.leave();
|
|
|
|
try {
|
|
commit.getBlocking();
|
|
} catch (Error&) {
|
|
}
|
|
|
|
commitBarrier.reached();
|
|
|
|
mutex.enter();
|
|
if (commitFuture.isValid())
|
|
commitFuture = ThreadFuture<Void>();
|
|
|
|
if (stopped) {
|
|
mutex.leave();
|
|
break;
|
|
}
|
|
|
|
mutex.leave();
|
|
}
|
|
}
|
|
|
|
Future<bool> check(Database const& cx) override { return success; }
|
|
|
|
void getMetrics(std::vector<PerfMetric>& m) override {}
|
|
};
|
|
|
|
WorkloadFactory<ThreadSafetyWorkload> ThreadSafetyWorkloadFactory("ThreadSafety");
|