Skip to content

Commit 54519e4

Browse files
sfc-gh-anoyessfc-gh-clin
authored andcommitted
Validate subrange reads in simulation (#7597)
* Add extra validation to special key space reads in simulation * Fix bugs turned up by validating subrange reads * Change to validateSpecialSubrangeRead It is in general not safe to expect that a read from the special key space returns the same results if performed again, since the transaction may be being modified concurrently. * Add comment * Add comment
1 parent adc3f0b commit 54519e4

File tree

3 files changed

+104
-4
lines changed

3 files changed

+104
-4
lines changed

fdbclient/SpecialKeySpace.actor.cpp

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -676,13 +676,14 @@ Future<RangeResult> ConflictingKeysImpl::getRange(ReadYourWritesTransaction* ryw
676676
if (ryw->getTransactionState()->conflictingKeys) {
677677
auto krMapPtr = ryw->getTransactionState()->conflictingKeys.get();
678678
auto beginIter = krMapPtr->rangeContaining(kr.begin);
679-
if (beginIter->begin() != kr.begin)
680-
++beginIter;
681679
auto endIter = krMapPtr->rangeContaining(kr.end);
680+
681+
if (!kr.contains(beginIter->begin()) && beginIter != endIter)
682+
++beginIter;
682683
for (auto it = beginIter; it != endIter; ++it) {
683684
result.push_back_deep(result.arena(), KeyValueRef(it->begin(), it->value()));
684685
}
685-
if (endIter->begin() != kr.end)
686+
if (kr.contains(endIter->begin()))
686687
result.push_back_deep(result.arena(), KeyValueRef(endIter->begin(), endIter->value()));
687688
}
688689
return result;
@@ -2817,3 +2818,85 @@ Future<Optional<std::string>> TenantMapRangeImpl::commit(ReadYourWritesTransacti
28172818

28182819
return tag(waitForAll(tenantManagementFutures), Optional<std::string>());
28192820
}
2821+
2822+
ACTOR Future<Void> validateSpecialSubrangeRead(ReadYourWritesTransaction* ryw,
2823+
KeySelector begin,
2824+
KeySelector end,
2825+
GetRangeLimits limits,
2826+
Reverse reverse,
2827+
RangeResult result) {
2828+
if (!result.size()) {
2829+
RangeResult testResult = wait(ryw->getRange(begin, end, limits, Snapshot::True, reverse));
2830+
ASSERT(testResult == result);
2831+
return Void();
2832+
}
2833+
2834+
if (reverse) {
2835+
ASSERT(std::is_sorted(result.begin(), result.end(), KeyValueRef::OrderByKeyBack{}));
2836+
} else {
2837+
ASSERT(std::is_sorted(result.begin(), result.end(), KeyValueRef::OrderByKey{}));
2838+
}
2839+
2840+
// Generate a keyrange where we can determine the expected result based solely on the previous readrange, and whose
2841+
// boundaries may or may not be keys in result.
2842+
std::vector<Key> candidateKeys;
2843+
if (reverse) {
2844+
for (int i = result.size() - 1; i >= 0; --i) {
2845+
candidateKeys.emplace_back(result[i].key);
2846+
if (i - 1 >= 0) {
2847+
candidateKeys.emplace_back(keyBetween(KeyRangeRef(result[i].key, result[i - 1].key)));
2848+
}
2849+
}
2850+
} else {
2851+
for (int i = 0; i < result.size(); ++i) {
2852+
candidateKeys.emplace_back(result[i].key);
2853+
if (i + 1 < result.size()) {
2854+
candidateKeys.emplace_back(keyBetween(KeyRangeRef(result[i].key, result[i + 1].key)));
2855+
}
2856+
}
2857+
}
2858+
std::sort(candidateKeys.begin(), candidateKeys.end());
2859+
int originalSize = candidateKeys.size();
2860+
// Add more candidate keys so that we might read a range between two adjacent result keys.
2861+
for (int i = 0; i < originalSize - 1; ++i) {
2862+
candidateKeys.emplace_back(keyBetween(KeyRangeRef(candidateKeys[i], candidateKeys[i + 1])));
2863+
}
2864+
std::vector<Key> keys;
2865+
keys = { deterministicRandom()->randomChoice(candidateKeys), deterministicRandom()->randomChoice(candidateKeys) };
2866+
std::sort(keys.begin(), keys.end());
2867+
state KeySelector testBegin = firstGreaterOrEqual(keys[0]);
2868+
state KeySelector testEnd = firstGreaterOrEqual(keys[1]);
2869+
2870+
// Generate expected result. Linear time is ok here since we're in simulation, and there's a benefit to keeping this
2871+
// simple (as we're using it as an test oracle)
2872+
state RangeResult expectedResult;
2873+
// The reverse parameter should be the same as for the original read, so if
2874+
// reverse is true then the results are _already_ in reverse order.
2875+
for (const auto& kr : result) {
2876+
if (kr.key >= keys[0] && kr.key < keys[1]) {
2877+
expectedResult.push_back(expectedResult.arena(), kr);
2878+
}
2879+
}
2880+
2881+
// Test
2882+
RangeResult testResult = wait(ryw->getRange(testBegin, testEnd, limits, Snapshot::True, reverse));
2883+
if (testResult != expectedResult) {
2884+
fmt::print("Reverse: {}\n", reverse);
2885+
fmt::print("Original range: [{}, {})\n", begin.toString(), end.toString());
2886+
fmt::print("Original result:\n");
2887+
for (const auto& kr : result) {
2888+
fmt::print(" {} -> {}\n", kr.key.printable(), kr.value.printable());
2889+
}
2890+
fmt::print("Test range: [{}, {})\n", testBegin.getKey().printable(), testEnd.getKey().printable());
2891+
fmt::print("Expected:\n");
2892+
for (const auto& kr : expectedResult) {
2893+
fmt::print(" {} -> {}\n", kr.key.printable(), kr.value.printable());
2894+
}
2895+
fmt::print("Got:\n");
2896+
for (const auto& kr : testResult) {
2897+
fmt::print(" {} -> {}\n", kr.key.printable(), kr.value.printable());
2898+
}
2899+
ASSERT(testResult == expectedResult);
2900+
}
2901+
return Void();
2902+
}

fdbclient/SpecialKeySpace.actor.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,5 +550,15 @@ class TenantMapRangeImpl : public SpecialKeyRangeRWImpl {
550550
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
551551
};
552552

553+
// If the underlying set of key-value pairs of a key space is not changing, then we expect repeating a read to give the
554+
// same result. Additionally, we can generate the expected result of any read if that read is reading a subrange. This
555+
// actor performs a read of an arbitrary subrange of [begin, end) and validates the results.
556+
ACTOR Future<Void> validateSpecialSubrangeRead(ReadYourWritesTransaction* ryw,
557+
KeySelector begin,
558+
KeySelector end,
559+
GetRangeLimits limits,
560+
Reverse reverse,
561+
RangeResult result);
562+
553563
#include "flow/unactorcompiler.h"
554564
#endif

fdbserver/workloads/ReportConflictingKeys.actor.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,16 @@ struct ReportConflictingKeysWorkload : TestWorkload {
192192
LiteralStringRef("\xff\xff").withPrefix(conflictingKeysRange.begin));
193193
// The getRange here using the special key prefix "\xff\xff/transaction/conflicting_keys/" happens
194194
// locally Thus, the error handling is not needed here
195-
Future<RangeResult> conflictingKeyRangesFuture = tr2->getRange(ckr, CLIENT_KNOBS->TOO_MANY);
195+
state Future<RangeResult> conflictingKeyRangesFuture = tr2->getRange(ckr, CLIENT_KNOBS->TOO_MANY);
196196
ASSERT(conflictingKeyRangesFuture.isReady());
197197

198+
wait(validateSpecialSubrangeRead(tr2.getPtr(),
199+
firstGreaterOrEqual(ckr.begin),
200+
firstGreaterOrEqual(ckr.end),
201+
GetRangeLimits(),
202+
Reverse::False,
203+
conflictingKeyRangesFuture.get()));
204+
198205
tr2 = makeReference<ReadYourWritesTransaction>(cx);
199206

200207
const RangeResult conflictingKeyRanges = conflictingKeyRangesFuture.get();

0 commit comments

Comments
 (0)