769 lines
25 KiB
C++
769 lines
25 KiB
C++
/*
|
|
* ApiCorrectness.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 "fdbserver/QuietDatabase.h"
|
|
|
|
#include "fdbserver/MutationTracking.h"
|
|
#include "fdbserver/workloads/workloads.actor.h"
|
|
#include "fdbserver/workloads/ApiWorkload.h"
|
|
#include "fdbserver/workloads/MemoryKeyValueStore.h"
|
|
#include "flow/actorcompiler.h" // This must be the last #include.
|
|
|
|
// An enum of API operation types used in the random test
|
|
enum OperationType { SET, GET, GET_RANGE, GET_RANGE_SELECTOR, GET_KEY, CLEAR, CLEAR_RANGE, UNINITIALIZED };
|
|
|
|
// A workload that executes the NativeAPIs functions and verifies that their outcomes are correct
|
|
struct ApiCorrectnessWorkload : ApiWorkload {
|
|
|
|
private:
|
|
// Enable to track the activity on a particular key
|
|
#if CENABLED(0, NOT_IN_CLEAN)
|
|
#define targetKey LiteralStringRef( ??? )
|
|
|
|
void debugKey(KeyRef key, std::string context) {
|
|
if (key == targetKey)
|
|
TraceEvent("ApiCorrectnessDebugKey").detail("Context", context).detail("Key", printable(key));
|
|
}
|
|
|
|
void debugKey(KeyRangeRef keyRange, std::string context) {
|
|
if (keyRange.contains(targetKey))
|
|
TraceEvent("ApiCorrectnessDebugKey")
|
|
.detail("Context", context)
|
|
.detail("Key", printable(targetKey))
|
|
.detail("RangeBegin", printable(keyRange.begin))
|
|
.detail("RangeEnd", printable(keyRange.end));
|
|
}
|
|
|
|
#else
|
|
void debugKey(KeyRef key, std::string context) {}
|
|
void debugKey(KeyRangeRef keyRange, std::string context) {}
|
|
|
|
#endif
|
|
|
|
public:
|
|
// The number of gets that should be performed
|
|
int numGets;
|
|
|
|
// The number of getRanges that should be performed
|
|
int numGetRanges;
|
|
|
|
// The number of getRanges using key selectors that should be performed
|
|
int numGetRangeSelectors;
|
|
|
|
// The number of getKeys that should be performed
|
|
int numGetKeys;
|
|
|
|
// The number of clears that should be performed
|
|
int numClears;
|
|
|
|
// The number of clears using key ranges that should be performed
|
|
int numClearRanges;
|
|
|
|
// The smallest legal size of the database after a clear. A smaller size will trigger a database reset
|
|
int minSizeAfterClear;
|
|
|
|
// The maximum number of keys that can be in this client's key space when performing the random test
|
|
int maxRandomTestKeys;
|
|
|
|
// The amount of time to run the random tests
|
|
double randomTestDuration;
|
|
|
|
// The maximum number of keys operated on in a transaction; used to prevent transaction_too_old errors
|
|
int maxKeysPerTransaction;
|
|
|
|
// The number of API calls made by the random test
|
|
PerfIntCounter numRandomOperations;
|
|
|
|
// The API being used by this client
|
|
TransactionType transactionType;
|
|
|
|
// Maximum time to reset DB to the original state
|
|
double resetDBTimeout;
|
|
|
|
ApiCorrectnessWorkload(WorkloadContext const& wcx)
|
|
: ApiWorkload(wcx), numRandomOperations("Num Random Operations") {
|
|
numGets = getOption(options, LiteralStringRef("numGets"), 1000);
|
|
numGetRanges = getOption(options, LiteralStringRef("numGetRanges"), 100);
|
|
numGetRangeSelectors = getOption(options, LiteralStringRef("numGetRangeSelectors"), 100);
|
|
numGetKeys = getOption(options, LiteralStringRef("numGetKeys"), 100);
|
|
numClears = getOption(options, LiteralStringRef("numClears"), 100);
|
|
numClearRanges = getOption(options, LiteralStringRef("numClearRanges"), 100);
|
|
minSizeAfterClear = getOption(options, LiteralStringRef("minSizeAfterClear"), (int)(0.1 * numKeys));
|
|
|
|
maxRandomTestKeys = getOption(options, LiteralStringRef("maxRandomTestKeys"), numKeys);
|
|
randomTestDuration = getOption(options, LiteralStringRef("randomTestDuration"), 60.0);
|
|
|
|
int maxTransactionBytes = getOption(options, LiteralStringRef("maxTransactionBytes"), 500000);
|
|
maxKeysPerTransaction = std::max(1, maxTransactionBytes / (maxValueLength + maxLongKeyLength));
|
|
|
|
resetDBTimeout = getOption(options, LiteralStringRef("resetDBTimeout"), 1800.0);
|
|
|
|
if (maxTransactionBytes > 500000) {
|
|
TraceEvent("RemapEventSeverity")
|
|
.detail("TargetEvent", "LargePacketSent")
|
|
.detail("OriginalSeverity", SevWarnAlways)
|
|
.detail("NewSeverity", SevInfo);
|
|
TraceEvent("RemapEventSeverity")
|
|
.detail("TargetEvent", "LargePacketReceived")
|
|
.detail("OriginalSeverity", SevWarnAlways)
|
|
.detail("NewSeverity", SevInfo);
|
|
TraceEvent("RemapEventSeverity")
|
|
.detail("TargetEvent", "LargeTransaction")
|
|
.detail("OriginalSeverity", SevWarnAlways)
|
|
.detail("NewSeverity", SevInfo);
|
|
TraceEvent("RemapEventSeverity")
|
|
.detail("TargetEvent", "DiskQueueMemoryWarning")
|
|
.detail("OriginalSeverity", SevWarnAlways)
|
|
.detail("NewSeverity", SevInfo);
|
|
}
|
|
}
|
|
|
|
~ApiCorrectnessWorkload() override {}
|
|
|
|
std::string description() const override { return "ApiCorrectness"; }
|
|
|
|
void getMetrics(std::vector<PerfMetric>& m) override {
|
|
m.emplace_back("Number of Random Operations Performed", numRandomOperations.getValue(), Averaged::False);
|
|
}
|
|
|
|
ACTOR Future<Void> performSetup(Database cx, ApiCorrectnessWorkload* self) {
|
|
// Choose a random transaction type (NativeAPI, ReadYourWrites, ThreadSafe, MultiVersion)
|
|
std::vector<TransactionType> types;
|
|
types.push_back(NATIVE);
|
|
types.push_back(READ_YOUR_WRITES);
|
|
types.push_back(THREAD_SAFE);
|
|
types.push_back(MULTI_VERSION);
|
|
|
|
wait(self->chooseTransactionFactory(cx, types));
|
|
|
|
return Void();
|
|
}
|
|
|
|
Future<Void> performSetup(Database const& cx) override { return performSetup(cx, this); }
|
|
|
|
ACTOR Future<Void> performTest(Database cx, Standalone<VectorRef<KeyValueRef>> data, ApiCorrectnessWorkload* self) {
|
|
// Run the scripted test for a maximum of 10 minutes
|
|
wait(timeout(self->runScriptedTest(self, data), 600, Void()));
|
|
|
|
if (!self->hasFailed()) {
|
|
// Return database to original state (for a maximum of resetDBTimeout seconds)
|
|
try {
|
|
wait(timeoutError(::success(self->runSet(data, self)), self->resetDBTimeout));
|
|
} catch (Error& e) {
|
|
if (e.code() == error_code_timed_out) {
|
|
if (!self->hasFailed())
|
|
self->testFailure("Timeout during database reset");
|
|
|
|
return Void();
|
|
}
|
|
|
|
throw;
|
|
}
|
|
|
|
// Run the random test for the user-specified duration
|
|
wait(timeout(self->runRandomTest(self, data), self->randomTestDuration, Void()));
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
Future<Void> performTest(Database const& cx, Standalone<VectorRef<KeyValueRef>> const& data) override {
|
|
return performTest(cx, data, this);
|
|
}
|
|
|
|
// Run a scripted set of API operations
|
|
ACTOR Future<Void> runScriptedTest(ApiCorrectnessWorkload* self, VectorRef<KeyValueRef> data) {
|
|
// Test the set function
|
|
bool setResult = wait(self->runSet(data, self));
|
|
if (!setResult)
|
|
return Void();
|
|
|
|
// Test the get function
|
|
wait(::success(self->runGet(data, self->numGets, self)));
|
|
|
|
// Test the getRange function
|
|
state int i;
|
|
for (i = 0; i < self->numGetRanges; i++)
|
|
wait(::success(self->runGetRange(data, self)));
|
|
|
|
// Test the getRange function using key selectors
|
|
for (i = 0; i < self->numGetRangeSelectors; i++)
|
|
wait(::success(self->runGetRangeSelector(data, self)));
|
|
|
|
// Test the getKey function
|
|
wait(::success(self->runGetKey(data, self->numGetKeys, self)));
|
|
|
|
// Test the clear function
|
|
bool clearResult = wait(self->runClear(data, self->numClears, self));
|
|
if (!clearResult)
|
|
return Void();
|
|
|
|
// Test the clear function using keyRanges
|
|
for (i = 0; i < self->numClearRanges; i++) {
|
|
// Alternate restoring the database to its original state and clearing a single range
|
|
if (self->store.size() < self->minSizeAfterClear) {
|
|
bool resetResult = wait(self->runSet(data, self));
|
|
if (!resetResult)
|
|
return Void();
|
|
}
|
|
|
|
bool clearRangeResults = wait(self->runClearRange(data, self));
|
|
if (!clearRangeResults)
|
|
return Void();
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
// Generate and execute a sequence of random operations
|
|
ACTOR Future<Void> runRandomTest(ApiCorrectnessWorkload* self, Standalone<VectorRef<KeyValueRef>> data) {
|
|
loop {
|
|
double setProbability = 1 - ((double)self->store.size()) / self->maxRandomTestKeys;
|
|
int pdfArray[] = { 0,
|
|
(int)(100 * setProbability),
|
|
100,
|
|
50,
|
|
50,
|
|
20,
|
|
(int)(100 * (1 - setProbability)),
|
|
(int)(10 * (1 - setProbability)) };
|
|
std::vector<int> pdf = std::vector<int>(pdfArray, pdfArray + 8);
|
|
|
|
OperationType operation = UNINITIALIZED;
|
|
|
|
// Choose a random operation type (SET, GET, GET_RANGE, GET_RANGE_SELECTOR, GET_KEY, CLEAR, CLEAR_RANGE).
|
|
int totalDensity = 0;
|
|
for (int i = 0; i < pdf.size(); i++)
|
|
totalDensity += pdf[i];
|
|
|
|
int cumulativeDensity = 0;
|
|
int random = deterministicRandom()->randomInt(0, totalDensity);
|
|
for (int i = 0; i < pdf.size() - 1; i++) {
|
|
if (cumulativeDensity + pdf[i] <= random && random < cumulativeDensity + pdf[i] + pdf[i + 1]) {
|
|
operation = (OperationType)i;
|
|
break;
|
|
}
|
|
|
|
cumulativeDensity += pdf[i];
|
|
}
|
|
ASSERT(operation != UNINITIALIZED);
|
|
|
|
++self->numRandomOperations;
|
|
|
|
// Test the set operation
|
|
if (operation == SET) {
|
|
bool useShortKeys = deterministicRandom()->randomInt(0, 2) == 1;
|
|
int minKeyLength = useShortKeys ? self->minShortKeyLength : self->minLongKeyLength;
|
|
int maxKeyLength = useShortKeys ? self->maxShortKeyLength : self->maxLongKeyLength;
|
|
|
|
state Standalone<VectorRef<KeyValueRef>> newData =
|
|
self->generateData(std::min((uint64_t)100, self->maxRandomTestKeys - self->store.size()),
|
|
minKeyLength,
|
|
maxKeyLength,
|
|
self->minValueLength,
|
|
self->maxValueLength,
|
|
self->clientPrefix,
|
|
true);
|
|
|
|
data.append_deep(data.arena(), newData.begin(), newData.size());
|
|
|
|
bool result = wait(self->runSet(newData, self));
|
|
if (!result)
|
|
return Void();
|
|
}
|
|
|
|
// Test the get operation
|
|
else if (operation == GET) {
|
|
bool result = wait(self->runGet(data, 10, self));
|
|
if (!result)
|
|
return Void();
|
|
}
|
|
|
|
// Test the getRange operation
|
|
else if (operation == GET_RANGE) {
|
|
bool result = wait(self->runGetRange(data, self));
|
|
if (!result)
|
|
return Void();
|
|
}
|
|
|
|
// Test the getRange operation with key selectors
|
|
else if (operation == GET_RANGE_SELECTOR) {
|
|
bool result = wait(self->runGetRangeSelector(data, self));
|
|
if (!result)
|
|
return Void();
|
|
}
|
|
|
|
// Test the getKey operation
|
|
else if (operation == GET_KEY) {
|
|
bool result = wait(self->runGetKey(data, 10, self));
|
|
if (!result)
|
|
return Void();
|
|
}
|
|
|
|
// Test the clear operation
|
|
else if (operation == CLEAR) {
|
|
bool result = wait(self->runClear(data, 10, self));
|
|
if (!result)
|
|
return Void();
|
|
}
|
|
|
|
// Test the clear operation (using key range)
|
|
else if (operation == CLEAR_RANGE) {
|
|
bool result = wait(self->runClearRange(data, self));
|
|
if (!result)
|
|
return Void();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Adds the key-value pairs in data to the database and memory store
|
|
ACTOR Future<bool> runSet(VectorRef<KeyValueRef> data, ApiCorrectnessWorkload* self) {
|
|
state int currentIndex = 0;
|
|
while (currentIndex < data.size()) {
|
|
state Reference<TransactionWrapper> transaction = self->createTransaction();
|
|
|
|
// Set keys in the database
|
|
loop {
|
|
try {
|
|
// For now, make this transaction self-conflicting to avoid commit errors
|
|
Optional<Value> value = wait(transaction->get(data[currentIndex].key));
|
|
|
|
for (int i = currentIndex; i < std::min(currentIndex + self->maxKeysPerTransaction, data.size());
|
|
i++) {
|
|
transaction->addReadConflictRange(singleKeyRange(data[i].key));
|
|
transaction->set(data[i].key, data[i].value);
|
|
}
|
|
|
|
wait(transaction->commit());
|
|
for (int i = currentIndex; i < std::min(currentIndex + self->maxKeysPerTransaction, data.size());
|
|
i++)
|
|
DEBUG_MUTATION("ApiCorrectnessSet",
|
|
transaction->getCommittedVersion(),
|
|
MutationRef(MutationRef::DebugKey, data[i].key, data[i].value));
|
|
|
|
currentIndex += self->maxKeysPerTransaction;
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(transaction->onError(e));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set keys in memory
|
|
for (int i = 0; i < data.size(); i++) {
|
|
self->store.set(data[i].key, data[i].value);
|
|
self->debugKey(data[i].key, "Set");
|
|
}
|
|
|
|
// Check that the database and memory store are the same
|
|
bool result = wait(self->compareDatabaseToMemory());
|
|
if (!result)
|
|
self->testFailure("Set resulted in incorrect database");
|
|
|
|
return result;
|
|
}
|
|
|
|
// Gets a specified number of values from the database and memory store and compares them, returning true if all
|
|
// results were the same
|
|
ACTOR Future<bool> runGet(VectorRef<KeyValueRef> data, int numReads, ApiCorrectnessWorkload* self) {
|
|
// Generate a set of random keys to get
|
|
state Standalone<VectorRef<KeyRef>> keys;
|
|
for (int i = 0; i < numReads; i++)
|
|
keys.push_back_deep(keys.arena(), self->selectRandomKey(data, 0.9));
|
|
|
|
state std::vector<Optional<Value>> values;
|
|
|
|
state int currentIndex = 0;
|
|
while (currentIndex < keys.size()) {
|
|
state Reference<TransactionWrapper> transaction = self->createTransaction();
|
|
|
|
// Get the values from the database
|
|
loop {
|
|
try {
|
|
state std::vector<Future<Optional<Value>>> dbValueFutures;
|
|
for (int i = currentIndex; i < std::min(currentIndex + self->maxKeysPerTransaction, keys.size());
|
|
i++)
|
|
dbValueFutures.push_back(transaction->get(keys[i]));
|
|
|
|
wait(waitForAll(dbValueFutures));
|
|
|
|
for (int i = 0; i < dbValueFutures.size(); i++)
|
|
values.push_back(dbValueFutures[i].get());
|
|
|
|
currentIndex += self->maxKeysPerTransaction;
|
|
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(transaction->onError(e));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool result = true;
|
|
|
|
// Get the values from the memory store and compare them
|
|
for (int i = 0; i < keys.size(); i++) {
|
|
if (values[i] != self->store.get(keys[i])) {
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!result)
|
|
self->testFailure("Get returned incorrect results");
|
|
|
|
return result;
|
|
}
|
|
|
|
// Gets a single range of values from the database and memory stores and compares them, returning true if the
|
|
// results were the same
|
|
ACTOR Future<bool> runGetRange(VectorRef<KeyValueRef> data, ApiCorrectnessWorkload* self) {
|
|
state Reverse reverse = deterministicRandom()->coinflip();
|
|
|
|
// Generate a random range
|
|
Key key = self->selectRandomKey(data, 0.5);
|
|
Key key2 = self->selectRandomKey(data, 0.5);
|
|
|
|
state Key start = std::min(key, key2);
|
|
state Key end = std::max(key, key2);
|
|
|
|
// Generate a random maximum number of results
|
|
state int limit = deterministicRandom()->randomInt(0, 101);
|
|
|
|
// Get the range from memory
|
|
state RangeResult storeResults = self->store.getRange(KeyRangeRef(start, end), limit, reverse);
|
|
|
|
// Get the range from the database
|
|
state RangeResult dbResults;
|
|
state Version readVersion;
|
|
|
|
state Reference<TransactionWrapper> transaction = self->createTransaction();
|
|
|
|
loop {
|
|
try {
|
|
Version version = wait(transaction->getReadVersion());
|
|
readVersion = version;
|
|
|
|
KeyRangeRef range(start, end);
|
|
RangeResult rangeResults = wait(transaction->getRange(range, limit, reverse));
|
|
dbResults = rangeResults;
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(transaction->onError(e));
|
|
}
|
|
}
|
|
|
|
// Compare the ranges
|
|
bool result = self->compareResults(dbResults, storeResults, readVersion);
|
|
if (!result)
|
|
self->testFailure("GetRange returned incorrect results");
|
|
|
|
return result;
|
|
}
|
|
|
|
// Gets a single range of values using key selectors from the database and memory store and compares them, returning
|
|
// true if the results were the same
|
|
ACTOR Future<bool> runGetRangeSelector(VectorRef<KeyValueRef> data, ApiCorrectnessWorkload* self) {
|
|
state Reverse reverse = deterministicRandom()->coinflip();
|
|
|
|
KeySelector selectors[2];
|
|
Key keys[2];
|
|
|
|
int maxSelectorAttempts = 100;
|
|
int currentSelectorAttempts = 0;
|
|
|
|
// Generate a random pair of key selectors and determine the keys they point to
|
|
// Don't use key selectors which would return results outside the key-space of this client unless this client
|
|
// is the first or last client
|
|
for (int i = 0; i < 2; i++) {
|
|
loop {
|
|
// Gradually decrease the maximum offset to increase the likelihood of finding a valid key selector in a
|
|
// small store
|
|
selectors[i] =
|
|
self->generateKeySelector(data, std::min(100, maxSelectorAttempts - currentSelectorAttempts));
|
|
keys[i] = self->store.getKey(selectors[i]);
|
|
|
|
if (keys[i].startsWith(StringRef(self->clientPrefix)) ||
|
|
(keys[i].size() == 0 && self->clientPrefixInt == 0) ||
|
|
(keys[i].startsWith(LiteralStringRef("\xff")) && self->clientPrefixInt == self->clientCount - 1)) {
|
|
break;
|
|
}
|
|
|
|
// Don't loop forever trying to generate valid key selectors if there are no keys in the store
|
|
if (++currentSelectorAttempts == maxSelectorAttempts)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
state KeySelector startSelector;
|
|
state KeySelector endSelector;
|
|
|
|
state Key startKey;
|
|
state Key endKey;
|
|
|
|
// Make sure startKey is less than endKey
|
|
if (keys[0] < keys[1]) {
|
|
startSelector = selectors[0];
|
|
startKey = keys[0];
|
|
endSelector = selectors[1];
|
|
endKey = keys[1];
|
|
} else {
|
|
startSelector = selectors[1];
|
|
startKey = keys[1];
|
|
endSelector = selectors[0];
|
|
endKey = keys[0];
|
|
}
|
|
|
|
// Choose a random maximum number of results
|
|
state int limit = deterministicRandom()->randomInt(0, 101);
|
|
|
|
// Get the range from the memory store
|
|
state RangeResult storeResults = self->store.getRange(KeyRangeRef(startKey, endKey), limit, reverse);
|
|
|
|
// Get the range from the database
|
|
state RangeResult dbResults;
|
|
|
|
state Reference<TransactionWrapper> transaction = self->createTransaction();
|
|
state Version readVersion;
|
|
|
|
loop {
|
|
try {
|
|
Version version = wait(transaction->getReadVersion());
|
|
readVersion = version;
|
|
|
|
RangeResult range = wait(transaction->getRange(startSelector, endSelector, limit, reverse));
|
|
|
|
if (endKey == self->store.endKey()) {
|
|
for (int i = 0; i < range.size(); i++) {
|
|
// Don't include results in the 0xFF key-space
|
|
if (!range[i].key.startsWith(LiteralStringRef("\xff")))
|
|
dbResults.push_back_deep(dbResults.arena(), range[i]);
|
|
}
|
|
if (reverse && dbResults.size() < storeResults.size()) {
|
|
storeResults.resize(storeResults.arena(), dbResults.size());
|
|
}
|
|
} else
|
|
dbResults = range;
|
|
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(transaction->onError(e));
|
|
}
|
|
}
|
|
|
|
// Compare the results
|
|
bool result = self->compareResults(dbResults, storeResults, readVersion);
|
|
|
|
if (!result)
|
|
self->testFailure("GetRange (KeySelector) returned incorrect results");
|
|
|
|
return result;
|
|
}
|
|
|
|
// Gets a specified number of keys from the database and memory store and compares them, returning true if all
|
|
// results were the same
|
|
ACTOR Future<bool> runGetKey(VectorRef<KeyValueRef> data, int numGetKeys, ApiCorrectnessWorkload* self) {
|
|
// Generate a set of random key selectors
|
|
state Standalone<VectorRef<KeySelectorRef>> selectors;
|
|
for (int i = 0; i < numGetKeys; i++)
|
|
selectors.push_back_deep(selectors.arena(), self->generateKeySelector(data, 100));
|
|
|
|
state Standalone<VectorRef<KeyRef>> keys;
|
|
|
|
state int currentIndex = 0;
|
|
while (currentIndex < selectors.size()) {
|
|
// Get the keys from the database
|
|
state Reference<TransactionWrapper> transaction = self->createTransaction();
|
|
|
|
loop {
|
|
try {
|
|
state std::vector<Future<Standalone<KeyRef>>> dbKeyFutures;
|
|
for (int i = currentIndex;
|
|
i < std::min(currentIndex + self->maxKeysPerTransaction, selectors.size());
|
|
i++)
|
|
dbKeyFutures.push_back(transaction->getKey(selectors[i]));
|
|
|
|
wait(waitForAll(dbKeyFutures));
|
|
|
|
for (int i = 0; i < dbKeyFutures.size(); i++)
|
|
keys.push_back_deep(keys.arena(), dbKeyFutures[i].get());
|
|
|
|
currentIndex += self->maxKeysPerTransaction;
|
|
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(transaction->onError(e));
|
|
}
|
|
}
|
|
}
|
|
|
|
state bool result = true;
|
|
|
|
// Get the keys from the memory store and compare them
|
|
state int i;
|
|
for (i = 0; i < selectors.size(); i++) {
|
|
Key key = self->store.getKey(selectors[i]);
|
|
if (keys[i].startsWith(StringRef(self->clientPrefix)) && keys[i] != key)
|
|
result = false;
|
|
else if (keys[i] < StringRef(self->clientPrefix) && key != self->store.startKey())
|
|
result = false;
|
|
else if (keys[i] > StringRef(self->clientPrefix + "\xff") && key != self->store.endKey())
|
|
result = false;
|
|
|
|
// If there was a failure, print some debugging info about the failed key
|
|
if (!result) {
|
|
printf("Bad result for key selector %s: db=%s, mem=%s\n",
|
|
selectors[i].toString().c_str(),
|
|
printable(keys[i]).c_str(),
|
|
printable(key).c_str());
|
|
state int dir = selectors[i].offset > 0 ? 1 : -1;
|
|
state int j;
|
|
for (j = 0; j <= abs(selectors[i].offset); j++) {
|
|
state KeySelector sel = KeySelectorRef(selectors[i].getKey(), selectors[i].orEqual, j * dir);
|
|
state Key storeKey = self->store.getKey(sel);
|
|
|
|
state Reference<TransactionWrapper> tr = self->createTransaction();
|
|
|
|
state Key dbKey;
|
|
loop {
|
|
try {
|
|
Key key = wait(tr->getKey(sel));
|
|
dbKey = key;
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(tr->onError(e));
|
|
}
|
|
}
|
|
|
|
if (!(storeKey == self->store.startKey() && dbKey < StringRef(self->clientPrefix)) &&
|
|
!(storeKey == self->store.endKey() && dbKey > StringRef(self->clientPrefix + "\xff")))
|
|
printf("Offset %d: db=%s, mem=%s\n",
|
|
j * dir,
|
|
printable(dbKey).c_str(),
|
|
printable(storeKey).c_str());
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!result)
|
|
self->testFailure("GetKey returned incorrect results");
|
|
|
|
return result;
|
|
}
|
|
|
|
// Clears a specified number of keys from the database and memory store
|
|
ACTOR Future<bool> runClear(VectorRef<KeyValueRef> data, int numClears, ApiCorrectnessWorkload* self) {
|
|
// Generate a random set of keys to clear
|
|
state Standalone<VectorRef<KeyRef>> keys;
|
|
for (int i = 0; i < numClears; i++)
|
|
keys.push_back_deep(keys.arena(), self->selectRandomKey(data, 0.9));
|
|
|
|
state int currentIndex = 0;
|
|
while (currentIndex < keys.size()) {
|
|
// Clear the keys from the database
|
|
state Reference<TransactionWrapper> transaction = self->createTransaction();
|
|
loop {
|
|
try {
|
|
// For now, make this transaction self-conflicting to avoid commit errors
|
|
Optional<Value> value = wait(transaction->get(keys[0]));
|
|
|
|
for (int i = currentIndex; i < std::min(currentIndex + self->maxKeysPerTransaction, keys.size());
|
|
i++) {
|
|
transaction->addReadConflictRange(singleKeyRange(keys[i]));
|
|
transaction->clear(keys[i]);
|
|
}
|
|
|
|
wait(transaction->commit());
|
|
for (int i = currentIndex; i < std::min(currentIndex + self->maxKeysPerTransaction, keys.size());
|
|
i++)
|
|
DEBUG_MUTATION("ApiCorrectnessClear",
|
|
transaction->getCommittedVersion(),
|
|
MutationRef(MutationRef::DebugKey, keys[i], StringRef()));
|
|
|
|
currentIndex += self->maxKeysPerTransaction;
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(transaction->onError(e));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear the keys from the memory store
|
|
for (int i = 0; i < keys.size(); i++) {
|
|
self->store.clear(keys[i]);
|
|
self->debugKey(keys[i], "Clear");
|
|
}
|
|
|
|
// Check that the database and memory store are the same
|
|
bool result = wait(self->compareDatabaseToMemory());
|
|
if (!result)
|
|
self->testFailure("Clear resulted in incorrect database");
|
|
|
|
return result;
|
|
}
|
|
|
|
// Clears a single range of keys from the database and memory store
|
|
ACTOR Future<bool> runClearRange(VectorRef<KeyValueRef> data, ApiCorrectnessWorkload* self) {
|
|
// Generate a random range to clear
|
|
Key key = self->selectRandomKey(data, 0.5);
|
|
Key key2 = self->selectRandomKey(data, 0.5);
|
|
|
|
state Key start = std::min(key, key2);
|
|
state Key end = std::max(key, key2);
|
|
|
|
// Clear the range in memory
|
|
self->store.clear(KeyRangeRef(start, end));
|
|
|
|
// Clear the range in the database
|
|
state Reference<TransactionWrapper> transaction = self->createTransaction();
|
|
|
|
loop {
|
|
try {
|
|
// For now, make this transaction self-conflicting to avoid commit errors
|
|
Optional<Value> value = wait(transaction->get(start));
|
|
|
|
state KeyRangeRef range(start, end);
|
|
if (!range.empty()) {
|
|
transaction->addReadConflictRange(range);
|
|
}
|
|
transaction->clear(range);
|
|
wait(transaction->commit());
|
|
DEBUG_KEY_RANGE("ApiCorrectnessClear", transaction->getCommittedVersion(), range);
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(transaction->onError(e));
|
|
}
|
|
}
|
|
|
|
self->debugKey(KeyRangeRef(start, end), "ClearRange");
|
|
|
|
// Check that the database and memory store are the same
|
|
bool result = wait(self->compareDatabaseToMemory());
|
|
if (!result)
|
|
self->testFailure("Clear (range) resulted in incorrect database");
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
WorkloadFactory<ApiCorrectnessWorkload> ApiCorrectnessWorkloadFactory("ApiCorrectness");
|