306 lines
7.5 KiB
C++
306 lines
7.5 KiB
C++
/*
|
|
* ThreadSafety.actor.cpp
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2018 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(g_random->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 = NULL;
|
|
|
|
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();
|
|
}
|
|
};
|
|
|
|
extern bool noUnseed;
|
|
|
|
//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), tr(NULL), stopped(false) {
|
|
|
|
threadsPerClient = getOption(options, LiteralStringRef("threadsPerClient"), 3);
|
|
threadDuration = getOption(options, LiteralStringRef("threadDuration"), 60.0);
|
|
numKeys = getOption(options, LiteralStringRef("numKeys"), 100);
|
|
|
|
commitBarrier.setNumRequired(threadsPerClient);
|
|
|
|
success = true;
|
|
|
|
// This test is not deterministic
|
|
noUnseed = true;
|
|
}
|
|
|
|
virtual std::string description() {
|
|
return "ThreadSafety";
|
|
}
|
|
|
|
virtual Future<Void> setup(Database const& cx) {
|
|
return Void();
|
|
}
|
|
|
|
virtual Future<Void> start(Database const& cx) {
|
|
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(g_random->coinflip()) {
|
|
MultiVersionApi::api->selectApiVersion(cx->apiVersion);
|
|
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());
|
|
}, NULL );
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
virtual Future<bool> check(Database const& cx) {
|
|
return success;
|
|
}
|
|
|
|
virtual void getMetrics(vector<PerfMetric>& m) {
|
|
|
|
}
|
|
};
|
|
|
|
WorkloadFactory<ThreadSafetyWorkload> ThreadSafetyWorkloadFactory("ThreadSafety");
|