2021-08-05 05:47:18 +08:00
|
|
|
/*
|
|
|
|
* BlobManager.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.
|
|
|
|
*/
|
|
|
|
|
2021-09-03 00:09:37 +08:00
|
|
|
#include <vector>
|
|
|
|
#include <unordered_map>
|
|
|
|
|
2021-08-24 03:14:48 +08:00
|
|
|
#include "fdbclient/BlobWorkerInterface.h"
|
2021-08-05 05:47:18 +08:00
|
|
|
#include "fdbclient/KeyRangeMap.h"
|
2021-08-24 03:14:48 +08:00
|
|
|
#include "fdbclient/ReadYourWrites.h"
|
|
|
|
#include "fdbclient/SystemData.h"
|
2021-09-04 04:13:26 +08:00
|
|
|
#include "fdbclient/Tuple.h"
|
2021-08-05 05:47:18 +08:00
|
|
|
#include "fdbserver/BlobManagerInterface.h"
|
2021-08-24 03:14:48 +08:00
|
|
|
#include "fdbserver/BlobWorker.actor.h"
|
|
|
|
#include "fdbserver/Knobs.h"
|
2021-09-04 04:13:26 +08:00
|
|
|
#include "fdbserver/WaitFailure.h"
|
2021-08-24 03:14:48 +08:00
|
|
|
#include "flow/IRandom.h"
|
2021-08-05 05:47:18 +08:00
|
|
|
#include "flow/UnitTest.h"
|
|
|
|
#include "flow/actorcompiler.h" // has to be last include
|
|
|
|
|
2021-09-01 01:30:43 +08:00
|
|
|
#define BM_DEBUG 1
|
|
|
|
|
2021-08-25 03:15:14 +08:00
|
|
|
// TODO add comments + documentation
|
2021-08-05 05:47:18 +08:00
|
|
|
void handleClientBlobRange(KeyRangeMap<bool>* knownBlobRanges,
|
|
|
|
Arena ar,
|
|
|
|
VectorRef<KeyRangeRef>* rangesToAdd,
|
|
|
|
VectorRef<KeyRangeRef>* rangesToRemove,
|
|
|
|
KeyRef rangeStart,
|
|
|
|
KeyRef rangeEnd,
|
|
|
|
bool rangeActive) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("db range [%s - %s): %s\n",
|
|
|
|
rangeStart.printable().c_str(),
|
|
|
|
rangeEnd.printable().c_str(),
|
|
|
|
rangeActive ? "T" : "F");
|
|
|
|
}
|
2021-08-05 05:47:18 +08:00
|
|
|
KeyRange keyRange(KeyRangeRef(rangeStart, rangeEnd));
|
|
|
|
auto allRanges = knownBlobRanges->intersectingRanges(keyRange);
|
|
|
|
for (auto& r : allRanges) {
|
|
|
|
if (r.value() != rangeActive) {
|
|
|
|
KeyRef overlapStart = (r.begin() > keyRange.begin) ? r.begin() : keyRange.begin;
|
|
|
|
KeyRef overlapEnd = (keyRange.end < r.end()) ? keyRange.end : r.end();
|
|
|
|
KeyRangeRef overlap(overlapStart, overlapEnd);
|
|
|
|
if (rangeActive) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM Adding client range [%s - %s)\n",
|
|
|
|
overlapStart.printable().c_str(),
|
|
|
|
overlapEnd.printable().c_str());
|
|
|
|
}
|
2021-08-05 05:47:18 +08:00
|
|
|
rangesToAdd->push_back_deep(ar, overlap);
|
|
|
|
} else {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM Removing client range [%s - %s)\n",
|
|
|
|
overlapStart.printable().c_str(),
|
|
|
|
overlapEnd.printable().c_str());
|
|
|
|
}
|
2021-08-05 05:47:18 +08:00
|
|
|
rangesToRemove->push_back_deep(ar, overlap);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
knownBlobRanges->insert(keyRange, rangeActive);
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateClientBlobRanges(KeyRangeMap<bool>* knownBlobRanges,
|
|
|
|
RangeResult dbBlobRanges,
|
|
|
|
Arena ar,
|
|
|
|
VectorRef<KeyRangeRef>* rangesToAdd,
|
|
|
|
VectorRef<KeyRangeRef>* rangesToRemove) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Updating %d client blob ranges", dbBlobRanges.size() / 2);
|
|
|
|
for (int i = 0; i < dbBlobRanges.size() - 1; i += 2) {
|
|
|
|
printf(" [%s - %s)", dbBlobRanges[i].key.printable().c_str(), dbBlobRanges[i + 1].key.printable().c_str());
|
|
|
|
}
|
|
|
|
printf("\n");
|
2021-08-05 05:47:18 +08:00
|
|
|
}
|
|
|
|
// essentially do merge diff of current known blob ranges and new ranges, to assign new ranges to
|
|
|
|
// workers and revoke old ranges from workers
|
|
|
|
|
|
|
|
// basically, for any range that is set in results that isn't set in ranges, assign the range to the
|
|
|
|
// worker. for any range that isn't set in results that is set in ranges, revoke the range from the
|
|
|
|
// worker. and, update ranges to match results as you go
|
|
|
|
|
|
|
|
// FIXME: could change this to O(N) instead of O(NLogN) by doing a sorted merge instead of requesting the
|
|
|
|
// intersection for each insert, but this operation is pretty infrequent so it's probably not necessary
|
|
|
|
if (dbBlobRanges.size() == 0) {
|
|
|
|
// special case. Nothing in the DB, reset knownBlobRanges and revoke all existing ranges from workers
|
|
|
|
handleClientBlobRange(
|
|
|
|
knownBlobRanges, ar, rangesToAdd, rangesToRemove, normalKeys.begin, normalKeys.end, false);
|
|
|
|
} else {
|
|
|
|
if (dbBlobRanges[0].key > normalKeys.begin) {
|
|
|
|
handleClientBlobRange(
|
|
|
|
knownBlobRanges, ar, rangesToAdd, rangesToRemove, normalKeys.begin, dbBlobRanges[0].key, false);
|
|
|
|
}
|
|
|
|
for (int i = 0; i < dbBlobRanges.size() - 1; i++) {
|
|
|
|
if (dbBlobRanges[i].key >= normalKeys.end) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Found invalid blob range start %s\n", dbBlobRanges[i].key.printable().c_str());
|
|
|
|
}
|
2021-08-05 05:47:18 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
bool active = dbBlobRanges[i].value == LiteralStringRef("1");
|
|
|
|
if (active) {
|
|
|
|
ASSERT(dbBlobRanges[i + 1].value == StringRef());
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM sees client range [%s - %s)\n",
|
|
|
|
dbBlobRanges[i].key.printable().c_str(),
|
|
|
|
dbBlobRanges[i + 1].key.printable().c_str());
|
|
|
|
}
|
2021-08-05 05:47:18 +08:00
|
|
|
}
|
|
|
|
KeyRef endKey = dbBlobRanges[i + 1].key;
|
|
|
|
if (endKey > normalKeys.end) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Removing system keyspace from blob range [%s - %s)\n",
|
|
|
|
dbBlobRanges[i].key.printable().c_str(),
|
|
|
|
endKey.printable().c_str());
|
|
|
|
}
|
2021-08-05 05:47:18 +08:00
|
|
|
endKey = normalKeys.end;
|
|
|
|
}
|
|
|
|
handleClientBlobRange(
|
|
|
|
knownBlobRanges, ar, rangesToAdd, rangesToRemove, dbBlobRanges[i].key, endKey, active);
|
|
|
|
}
|
|
|
|
if (dbBlobRanges[dbBlobRanges.size() - 1].key < normalKeys.end) {
|
|
|
|
handleClientBlobRange(knownBlobRanges,
|
|
|
|
ar,
|
|
|
|
rangesToAdd,
|
|
|
|
rangesToRemove,
|
|
|
|
dbBlobRanges[dbBlobRanges.size() - 1].key,
|
|
|
|
normalKeys.end,
|
|
|
|
false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
knownBlobRanges->coalesce(normalKeys);
|
|
|
|
}
|
|
|
|
|
|
|
|
void getRanges(std::vector<std::pair<KeyRangeRef, bool>>& results, KeyRangeMap<bool>& knownBlobRanges) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Getting ranges:\n");
|
|
|
|
}
|
2021-08-05 05:47:18 +08:00
|
|
|
auto allRanges = knownBlobRanges.ranges();
|
|
|
|
for (auto& r : allRanges) {
|
|
|
|
results.emplace_back(r.range(), r.value());
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf(
|
|
|
|
" [%s - %s): %s\n", r.begin().printable().c_str(), r.end().printable().c_str(), r.value() ? "T" : "F");
|
|
|
|
}
|
2021-08-05 05:47:18 +08:00
|
|
|
}
|
|
|
|
}
|
2021-08-24 03:14:48 +08:00
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
struct RangeAssignmentData {
|
|
|
|
bool continueAssignment;
|
|
|
|
std::vector<KeyRange> previousRanges;
|
|
|
|
|
|
|
|
RangeAssignmentData() : continueAssignment(false) {}
|
|
|
|
RangeAssignmentData(bool continueAssignment, std::vector<KeyRange> previousRanges)
|
|
|
|
: continueAssignment(continueAssignment), previousRanges(previousRanges) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct RangeRevokeData {
|
|
|
|
bool dispose;
|
|
|
|
|
|
|
|
RangeRevokeData() {}
|
|
|
|
RangeRevokeData(bool dispose) : dispose(dispose) {}
|
|
|
|
};
|
|
|
|
|
2021-08-31 02:07:25 +08:00
|
|
|
struct RangeAssignment {
|
|
|
|
bool isAssign;
|
2021-09-04 04:13:26 +08:00
|
|
|
KeyRange keyRange;
|
|
|
|
Optional<UID> worker;
|
2021-08-31 02:59:53 +08:00
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
// I tried doing this with a union and it was just kind of messy
|
|
|
|
Optional<RangeAssignmentData> assign;
|
|
|
|
Optional<RangeRevokeData> revoke;
|
2021-08-31 02:07:25 +08:00
|
|
|
};
|
|
|
|
|
2021-09-02 11:36:44 +08:00
|
|
|
// TODO: track worker's reads/writes eventually
|
|
|
|
struct BlobWorkerStats {
|
|
|
|
int numGranulesAssigned;
|
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
BlobWorkerStats(int numGranulesAssigned = 0) : numGranulesAssigned(numGranulesAssigned) {}
|
2021-09-02 11:36:44 +08:00
|
|
|
};
|
|
|
|
|
2021-08-24 03:14:48 +08:00
|
|
|
struct BlobManagerData {
|
|
|
|
UID id;
|
|
|
|
Database db;
|
|
|
|
|
2021-09-03 00:09:37 +08:00
|
|
|
std::unordered_map<UID, BlobWorkerInterface> workersById;
|
2021-09-02 11:36:44 +08:00
|
|
|
std::unordered_map<UID, BlobWorkerStats> workerStats; // mapping between workerID -> workerStats
|
2021-09-04 04:13:26 +08:00
|
|
|
std::unordered_map<UID, Future<Void>> workerMonitors;
|
2021-08-31 02:07:25 +08:00
|
|
|
KeyRangeMap<UID> workerAssignments;
|
|
|
|
KeyRangeMap<bool> knownBlobRanges;
|
|
|
|
|
2021-08-28 05:33:07 +08:00
|
|
|
int64_t epoch = -1;
|
|
|
|
int64_t seqNo = 1;
|
|
|
|
|
2021-08-31 02:07:25 +08:00
|
|
|
Promise<Void> iAmReplaced;
|
|
|
|
|
|
|
|
// The order maintained here is important. The order ranges are put into the promise stream is the order they get
|
|
|
|
// assigned sequence numbers
|
|
|
|
PromiseStream<RangeAssignment> rangesToAssign;
|
|
|
|
|
|
|
|
BlobManagerData(UID id, Database db) : id(id), db(db), knownBlobRanges(false, normalKeys.end) {}
|
2021-08-24 03:14:48 +08:00
|
|
|
~BlobManagerData() { printf("Destroying blob manager data for %s\n", id.toString().c_str()); }
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO REMOVE eventually
|
|
|
|
ACTOR Future<Void> nukeBlobWorkerData(BlobManagerData* bmData) {
|
|
|
|
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(bmData->db);
|
|
|
|
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
|
|
|
tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE);
|
|
|
|
loop {
|
|
|
|
try {
|
|
|
|
tr->clear(blobWorkerListKeys);
|
|
|
|
tr->clear(blobGranuleMappingKeys);
|
2021-09-11 00:49:41 +08:00
|
|
|
tr->clear(changeFeedKeys);
|
2021-08-24 03:14:48 +08:00
|
|
|
|
|
|
|
return Void();
|
|
|
|
} catch (Error& e) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Nuking blob worker data got error %s\n", e.name());
|
|
|
|
}
|
2021-08-24 03:14:48 +08:00
|
|
|
wait(tr->onError(e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
ACTOR Future<Standalone<VectorRef<KeyRef>>> splitRange(Reference<ReadYourWritesTransaction> tr, KeyRange range) {
|
2021-08-24 03:14:48 +08:00
|
|
|
// TODO is it better to just pass empty metrics to estimated?
|
|
|
|
// TODO handle errors here by pulling out into its own transaction instead of the main loop's transaction, and
|
|
|
|
// retrying
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Splitting new range [%s - %s)\n", range.begin.printable().c_str(), range.end.printable().c_str());
|
|
|
|
}
|
2021-08-24 03:14:48 +08:00
|
|
|
StorageMetrics estimated = wait(tr->getTransaction().getStorageMetrics(range, CLIENT_KNOBS->TOO_MANY));
|
|
|
|
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Estimated bytes for [%s - %s): %lld\n",
|
|
|
|
range.begin.printable().c_str(),
|
|
|
|
range.end.printable().c_str(),
|
|
|
|
estimated.bytes);
|
|
|
|
}
|
2021-08-24 03:14:48 +08:00
|
|
|
|
|
|
|
if (estimated.bytes > SERVER_KNOBS->BG_SNAPSHOT_FILE_TARGET_BYTES) {
|
|
|
|
// printf(" Splitting range\n");
|
|
|
|
// only split on bytes
|
|
|
|
StorageMetrics splitMetrics;
|
|
|
|
splitMetrics.bytes = SERVER_KNOBS->BG_SNAPSHOT_FILE_TARGET_BYTES;
|
|
|
|
splitMetrics.bytesPerKSecond = splitMetrics.infinity;
|
|
|
|
splitMetrics.iosPerKSecond = splitMetrics.infinity;
|
|
|
|
splitMetrics.bytesReadPerKSecond = splitMetrics.infinity; // Don't split by readBandwidth
|
|
|
|
|
|
|
|
Standalone<VectorRef<KeyRef>> keys =
|
|
|
|
wait(tr->getTransaction().splitStorageMetrics(range, splitMetrics, estimated));
|
|
|
|
return keys;
|
|
|
|
} else {
|
|
|
|
// printf(" Not splitting range\n");
|
|
|
|
Standalone<VectorRef<KeyRef>> keys;
|
|
|
|
keys.push_back_deep(keys.arena(), range.begin);
|
|
|
|
keys.push_back_deep(keys.arena(), range.end);
|
|
|
|
return keys;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-02 11:36:44 +08:00
|
|
|
// Picks a worker with the fewest number of already assigned ranges.
|
|
|
|
// If there is a tie, picks one such worker at random.
|
2021-08-31 02:07:25 +08:00
|
|
|
static UID pickWorkerForAssign(BlobManagerData* bmData) {
|
2021-09-02 11:36:44 +08:00
|
|
|
int minGranulesAssigned = INT_MAX;
|
|
|
|
std::vector<UID> eligibleWorkers;
|
2021-09-04 04:13:26 +08:00
|
|
|
|
|
|
|
for (auto const& worker : bmData->workerStats) {
|
2021-09-02 11:36:44 +08:00
|
|
|
UID currId = worker.first;
|
|
|
|
int granulesAssigned = worker.second.numGranulesAssigned;
|
|
|
|
|
|
|
|
if (granulesAssigned < minGranulesAssigned) {
|
|
|
|
eligibleWorkers.resize(0);
|
|
|
|
minGranulesAssigned = granulesAssigned;
|
|
|
|
eligibleWorkers.emplace_back(currId);
|
|
|
|
} else if (granulesAssigned == minGranulesAssigned) {
|
|
|
|
eligibleWorkers.emplace_back(currId);
|
|
|
|
}
|
2021-09-01 01:30:43 +08:00
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
|
2021-09-02 11:36:44 +08:00
|
|
|
// pick a random worker out of the eligible workers
|
|
|
|
ASSERT(eligibleWorkers.size() > 0);
|
|
|
|
int idx = deterministicRandom()->randomInt(0, eligibleWorkers.size());
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
2021-09-04 04:13:26 +08:00
|
|
|
printf("picked worker %s, which has a minimal number (%d) of granules assigned\n",
|
|
|
|
eligibleWorkers[idx].toString().c_str(),
|
|
|
|
minGranulesAssigned);
|
2021-09-01 01:30:43 +08:00
|
|
|
}
|
2021-09-02 11:36:44 +08:00
|
|
|
|
|
|
|
return eligibleWorkers[idx];
|
2021-08-31 02:07:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
ACTOR Future<Void> doRangeAssignment(BlobManagerData* bmData, RangeAssignment assignment, UID workerID, int64_t seqNo) {
|
2021-08-24 03:14:48 +08:00
|
|
|
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM %s %s range [%s - %s) @ (%lld, %lld)\n",
|
|
|
|
workerID.toString().c_str(),
|
2021-09-04 04:13:26 +08:00
|
|
|
assignment.isAssign ? "assigning" : "revoking",
|
|
|
|
assignment.keyRange.begin.printable().c_str(),
|
|
|
|
assignment.keyRange.end.printable().c_str(),
|
|
|
|
bmData->epoch,
|
|
|
|
seqNo);
|
2021-09-01 01:30:43 +08:00
|
|
|
}
|
2021-08-24 03:14:48 +08:00
|
|
|
|
2021-08-31 02:07:25 +08:00
|
|
|
try {
|
2021-09-04 04:13:26 +08:00
|
|
|
state AssignBlobRangeReply rep;
|
|
|
|
if (assignment.isAssign) {
|
|
|
|
ASSERT(assignment.assign.present());
|
|
|
|
ASSERT(!assignment.revoke.present());
|
|
|
|
|
|
|
|
AssignBlobRangeRequest req;
|
|
|
|
req.keyRange = KeyRangeRef(StringRef(req.arena, assignment.keyRange.begin),
|
|
|
|
StringRef(req.arena, assignment.keyRange.end));
|
|
|
|
req.managerEpoch = bmData->epoch;
|
|
|
|
req.managerSeqno = seqNo;
|
|
|
|
req.continueAssignment = assignment.assign.get().continueAssignment;
|
|
|
|
for (auto& it : assignment.assign.get().previousRanges) {
|
|
|
|
req.previousGranules.push_back_deep(req.arena, it);
|
|
|
|
}
|
|
|
|
AssignBlobRangeReply _rep = wait(bmData->workersById[workerID].assignBlobRangeRequest.getReply(req));
|
|
|
|
rep = _rep;
|
|
|
|
} else {
|
|
|
|
ASSERT(!assignment.assign.present());
|
|
|
|
ASSERT(assignment.revoke.present());
|
|
|
|
|
|
|
|
RevokeBlobRangeRequest req;
|
|
|
|
req.keyRange = KeyRangeRef(StringRef(req.arena, assignment.keyRange.begin),
|
|
|
|
StringRef(req.arena, assignment.keyRange.end));
|
|
|
|
req.managerEpoch = bmData->epoch;
|
|
|
|
req.managerSeqno = seqNo;
|
|
|
|
req.dispose = assignment.revoke.get().dispose;
|
|
|
|
|
|
|
|
AssignBlobRangeReply _rep = wait(bmData->workersById[workerID].revokeBlobRangeRequest.getReply(req));
|
|
|
|
rep = _rep;
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
if (!rep.epochOk) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM heard from BW that there is a new manager with higher epoch\n");
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
if (bmData->iAmReplaced.canBeSet()) {
|
|
|
|
bmData->iAmReplaced.send(Void());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (Error& e) {
|
|
|
|
// TODO confirm: using reliable delivery this should only trigger if the worker is marked as failed, right?
|
|
|
|
// So assignment needs to be retried elsewhere, and a revoke is trivially complete
|
|
|
|
if (assignment.isAssign) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM got error assigning range [%s - %s) to worker %s, requeueing\n",
|
|
|
|
assignment.keyRange.begin.printable().c_str(),
|
|
|
|
assignment.keyRange.end.printable().c_str());
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
// re-send revoke to queue to handle range being un-assigned from that worker before the new one
|
2021-09-04 04:13:26 +08:00
|
|
|
RangeAssignment revokeOld;
|
|
|
|
revokeOld.isAssign = false;
|
|
|
|
revokeOld.worker = workerID;
|
|
|
|
revokeOld.keyRange = assignment.keyRange;
|
|
|
|
revokeOld.revoke = RangeRevokeData(false);
|
|
|
|
bmData->rangesToAssign.send(revokeOld);
|
|
|
|
|
|
|
|
// send assignment back to queue as is, clearing designated worker if present
|
|
|
|
assignment.worker.reset();
|
2021-08-31 02:07:25 +08:00
|
|
|
bmData->rangesToAssign.send(assignment);
|
|
|
|
// FIXME: improvement would be to add history of failed workers to assignment so it can try other ones first
|
2021-09-04 04:13:26 +08:00
|
|
|
} else {
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM got error revoking range [%s - %s) from worker %s",
|
|
|
|
assignment.keyRange.begin.printable().c_str(),
|
|
|
|
assignment.keyRange.end.printable().c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (assignment.revoke.get().dispose) {
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf(", retrying for dispose\n");
|
|
|
|
}
|
|
|
|
// send assignment back to queue as is, clearing designated worker if present
|
|
|
|
assignment.worker.reset();
|
|
|
|
bmData->rangesToAssign.send(assignment);
|
|
|
|
//
|
|
|
|
} else {
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf(", ignoring\n");
|
|
|
|
}
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
}
|
2021-08-28 05:33:07 +08:00
|
|
|
}
|
2021-08-24 03:14:48 +08:00
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
|
2021-08-31 02:07:25 +08:00
|
|
|
ACTOR Future<Void> rangeAssigner(BlobManagerData* bmData) {
|
|
|
|
state PromiseStream<Future<Void>> addActor;
|
|
|
|
state Future<Void> collection = actorCollection(addActor.getFuture());
|
|
|
|
loop {
|
|
|
|
RangeAssignment assignment = waitNext(bmData->rangesToAssign.getFuture());
|
|
|
|
int64_t seqNo = bmData->seqNo;
|
|
|
|
bmData->seqNo++;
|
|
|
|
|
|
|
|
// modify the in-memory assignment data structures, and send request off to worker
|
|
|
|
UID workerId;
|
|
|
|
if (assignment.isAssign) {
|
|
|
|
// Ensure range isn't currently assigned anywhere, and there is only 1 intersecting range
|
|
|
|
auto currentAssignments = bmData->workerAssignments.intersectingRanges(assignment.keyRange);
|
|
|
|
int count = 0;
|
|
|
|
for (auto& it : currentAssignments) {
|
2021-09-04 04:13:26 +08:00
|
|
|
if (assignment.assign.get().continueAssignment) {
|
|
|
|
ASSERT(assignment.worker.present());
|
|
|
|
ASSERT(it.value() == assignment.worker.get());
|
|
|
|
} else {
|
|
|
|
ASSERT(it.value() == UID());
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
count++;
|
|
|
|
}
|
|
|
|
ASSERT(count == 1);
|
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
workerId = assignment.worker.present() ? assignment.worker.get() : pickWorkerForAssign(bmData);
|
2021-08-31 02:07:25 +08:00
|
|
|
bmData->workerAssignments.insert(assignment.keyRange, workerId);
|
2021-09-02 11:36:44 +08:00
|
|
|
bmData->workerStats[workerId].numGranulesAssigned += 1;
|
2021-08-31 02:07:25 +08:00
|
|
|
|
|
|
|
// FIXME: if range is assign, have some sort of semaphore for outstanding assignments so we don't assign
|
|
|
|
// a ton ranges at once and blow up FDB with reading initial snapshots.
|
|
|
|
addActor.send(doRangeAssignment(bmData, assignment, workerId, seqNo));
|
|
|
|
} else {
|
|
|
|
// Revoking a range could be a large range that contains multiple ranges.
|
|
|
|
auto currentAssignments = bmData->workerAssignments.intersectingRanges(assignment.keyRange);
|
|
|
|
for (auto& it : currentAssignments) {
|
|
|
|
// ensure range doesn't truncate existing ranges
|
|
|
|
ASSERT(it.begin() >= assignment.keyRange.begin);
|
2021-08-31 02:59:53 +08:00
|
|
|
ASSERT(it.end() <= assignment.keyRange.end);
|
2021-08-31 02:07:25 +08:00
|
|
|
|
|
|
|
// It is fine for multiple disjoint sub-ranges to have the same sequence number since they were part of
|
|
|
|
// the same logical change
|
2021-09-03 00:09:37 +08:00
|
|
|
bmData->workerStats[it.value()].numGranulesAssigned -= 1;
|
2021-09-04 04:13:26 +08:00
|
|
|
if (!assignment.worker.present() || assignment.worker.get() == it.value())
|
|
|
|
addActor.send(doRangeAssignment(bmData, assignment, it.value(), seqNo));
|
2021-08-31 02:07:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bmData->workerAssignments.insert(assignment.keyRange, UID());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
ACTOR Future<Void> checkManagerLock(Reference<ReadYourWritesTransaction> tr, BlobManagerData* bmData) {
|
|
|
|
Optional<Value> currentLockValue = wait(tr->get(blobManagerEpochKey));
|
|
|
|
ASSERT(currentLockValue.present());
|
|
|
|
int64_t currentEpoch = decodeBlobManagerEpochValue(currentLockValue.get());
|
|
|
|
if (currentEpoch != bmData->epoch) {
|
|
|
|
ASSERT(currentEpoch > bmData->epoch);
|
|
|
|
|
|
|
|
printf("BM %s found new epoch %d > %d in lock check\n",
|
|
|
|
bmData->id.toString().c_str(),
|
|
|
|
currentEpoch,
|
|
|
|
bmData->epoch);
|
|
|
|
if (bmData->iAmReplaced.canBeSet()) {
|
|
|
|
bmData->iAmReplaced.send(Void());
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO different error?
|
|
|
|
throw granule_assignment_conflict();
|
|
|
|
}
|
|
|
|
tr->addReadConflictRange(singleKeyRange(blobManagerEpochKey));
|
|
|
|
|
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
|
2021-08-28 05:33:07 +08:00
|
|
|
// TODO eventually CC should probably do this and pass it as part of recruitment?
|
|
|
|
ACTOR Future<int64_t> acquireManagerLock(BlobManagerData* bmData) {
|
|
|
|
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(bmData->db);
|
2021-09-04 04:13:26 +08:00
|
|
|
|
2021-08-28 05:33:07 +08:00
|
|
|
loop {
|
2021-09-04 04:13:26 +08:00
|
|
|
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
|
|
|
tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE);
|
2021-08-28 05:33:07 +08:00
|
|
|
try {
|
|
|
|
Optional<Value> oldEpoch = wait(tr->get(blobManagerEpochKey));
|
|
|
|
state int64_t newEpoch;
|
|
|
|
if (oldEpoch.present()) {
|
|
|
|
newEpoch = decodeBlobManagerEpochValue(oldEpoch.get()) + 1;
|
|
|
|
} else {
|
|
|
|
newEpoch = 1; // start at 1
|
|
|
|
}
|
|
|
|
|
|
|
|
tr->set(blobManagerEpochKey, blobManagerEpochValueFor(newEpoch));
|
|
|
|
|
|
|
|
wait(tr->commit());
|
|
|
|
return newEpoch;
|
|
|
|
} catch (Error& e) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Acquiring blob manager lock got error %s\n", e.name());
|
|
|
|
}
|
2021-08-28 05:33:07 +08:00
|
|
|
wait(tr->onError(e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
// FIXME: this does all logic in one transaction. Adding a giant range to an existing database to hybridize would spread
|
|
|
|
// require doing a ton of storage metrics calls, which we should split up across multiple transactions likely.
|
2021-08-31 02:07:25 +08:00
|
|
|
ACTOR Future<Void> monitorClientRanges(BlobManagerData* bmData) {
|
2021-08-24 03:14:48 +08:00
|
|
|
loop {
|
2021-08-31 02:07:25 +08:00
|
|
|
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(bmData->db);
|
2021-08-24 03:14:48 +08:00
|
|
|
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Blob manager checking for range updates\n");
|
|
|
|
}
|
2021-08-24 03:14:48 +08:00
|
|
|
loop {
|
|
|
|
try {
|
|
|
|
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
|
|
|
tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE);
|
|
|
|
|
|
|
|
// TODO probably knobs here? This should always be pretty small though
|
|
|
|
RangeResult results = wait(krmGetRanges(
|
|
|
|
tr, blobRangeKeys.begin, KeyRange(normalKeys), 10000, GetRangeLimits::BYTE_LIMIT_UNLIMITED));
|
|
|
|
ASSERT(!results.more && results.size() < CLIENT_KNOBS->TOO_MANY);
|
|
|
|
|
|
|
|
state Arena ar;
|
|
|
|
ar.dependsOn(results.arena());
|
|
|
|
VectorRef<KeyRangeRef> rangesToAdd;
|
|
|
|
VectorRef<KeyRangeRef> rangesToRemove;
|
|
|
|
// TODO hack for simulation
|
2021-08-31 02:07:25 +08:00
|
|
|
updateClientBlobRanges(&bmData->knownBlobRanges, results, ar, &rangesToAdd, &rangesToRemove);
|
2021-08-24 03:14:48 +08:00
|
|
|
|
|
|
|
for (KeyRangeRef range : rangesToRemove) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM Got range to revoke [%s - %s)\n",
|
|
|
|
range.begin.printable().c_str(),
|
|
|
|
range.end.printable().c_str());
|
|
|
|
}
|
2021-08-24 03:14:48 +08:00
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
RangeAssignment ra;
|
|
|
|
ra.isAssign = false;
|
|
|
|
ra.keyRange = range;
|
|
|
|
ra.revoke = RangeRevokeData(true); // dispose=true
|
|
|
|
bmData->rangesToAssign.send(ra);
|
2021-08-24 03:14:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
state std::vector<Future<Standalone<VectorRef<KeyRef>>>> splitFutures;
|
|
|
|
// Divide new ranges up into equal chunks by using SS byte sample
|
|
|
|
for (KeyRangeRef range : rangesToAdd) {
|
2021-08-31 02:07:25 +08:00
|
|
|
// assert that this range contains no currently assigned ranges in this
|
2021-09-04 04:13:26 +08:00
|
|
|
splitFutures.push_back(splitRange(tr, range));
|
2021-08-24 03:14:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for (auto f : splitFutures) {
|
|
|
|
Standalone<VectorRef<KeyRef>> splits = wait(f);
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Split client range [%s - %s) into %d ranges:\n",
|
|
|
|
splits[0].printable().c_str(),
|
|
|
|
splits[splits.size() - 1].printable().c_str(),
|
|
|
|
splits.size() - 1);
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
|
2021-08-24 03:14:48 +08:00
|
|
|
for (int i = 0; i < splits.size() - 1; i++) {
|
|
|
|
KeyRange range = KeyRange(KeyRangeRef(splits[i], splits[i + 1]));
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf(" [%s - %s)\n", range.begin.printable().c_str(), range.end.printable().c_str());
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
RangeAssignment ra;
|
|
|
|
ra.isAssign = true;
|
|
|
|
ra.keyRange = range;
|
|
|
|
ra.assign = RangeAssignmentData(); // continue=false, no previous granules
|
|
|
|
bmData->rangesToAssign.send(ra);
|
2021-08-24 03:14:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state Future<Void> watchFuture = tr->watch(blobRangeChangeKey);
|
|
|
|
wait(tr->commit());
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Blob manager done processing client ranges, awaiting update\n");
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
wait(watchFuture);
|
2021-08-24 03:14:48 +08:00
|
|
|
break;
|
|
|
|
} catch (Error& e) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Blob manager got error looking for range updates %s\n", e.name());
|
|
|
|
}
|
2021-08-24 03:14:48 +08:00
|
|
|
wait(tr->onError(e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
static Key granuleLockKey(KeyRange granuleRange) {
|
|
|
|
Tuple k;
|
|
|
|
k.append(granuleRange.begin).append(granuleRange.end);
|
|
|
|
return k.getDataAsStandalone().withPrefix(blobGranuleLockKeys.begin);
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: propagate errors here
|
|
|
|
ACTOR Future<Void> maybeSplitRange(BlobManagerData* bmData, UID currentWorkerId, KeyRange range) {
|
|
|
|
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(bmData->db);
|
|
|
|
state Standalone<VectorRef<KeyRef>> newRanges;
|
|
|
|
state int64_t newLockSeqno = -1;
|
|
|
|
|
|
|
|
// first get ranges to split
|
|
|
|
loop {
|
|
|
|
try {
|
|
|
|
// redo split if previous txn try failed to calculate it
|
|
|
|
if (newRanges.empty()) {
|
|
|
|
Standalone<VectorRef<KeyRef>> _newRanges = wait(splitRange(tr, range));
|
|
|
|
newRanges = _newRanges;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} catch (Error& e) {
|
|
|
|
wait(tr->onError(e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newRanges.size() == 2) {
|
|
|
|
// not large enough to split, just reassign back to worker
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Not splitting existing range [%s - %s). Continuing assignment to %s\n",
|
|
|
|
range.begin.printable().c_str(),
|
|
|
|
range.end.printable().c_str(),
|
|
|
|
currentWorkerId.toString().c_str());
|
|
|
|
}
|
|
|
|
RangeAssignment raContinue;
|
|
|
|
raContinue.isAssign = true;
|
|
|
|
raContinue.worker = currentWorkerId;
|
|
|
|
raContinue.keyRange = range;
|
|
|
|
raContinue.assign =
|
|
|
|
RangeAssignmentData(true, std::vector<KeyRange>()); // continue, no "previous" range to do handover
|
|
|
|
bmData->rangesToAssign.send(raContinue);
|
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Need to split range. Persist intent to split and split metadata to DB BEFORE sending split requests
|
|
|
|
loop {
|
|
|
|
try {
|
|
|
|
tr->reset();
|
|
|
|
tr->setOption(FDBTransactionOptions::Option::PRIORITY_SYSTEM_IMMEDIATE);
|
|
|
|
tr->setOption(FDBTransactionOptions::Option::ACCESS_SYSTEM_KEYS);
|
|
|
|
ASSERT(newRanges.size() >= 2);
|
|
|
|
|
|
|
|
// make sure we're still manager when this transaction gets committed
|
|
|
|
wait(checkManagerLock(tr, bmData));
|
|
|
|
|
|
|
|
// acquire lock for old granule to make sure nobody else modifies it
|
|
|
|
state Key lockKey = granuleLockKey(range);
|
|
|
|
Optional<Value> lockValue = wait(tr->get(lockKey));
|
|
|
|
ASSERT(lockValue.present());
|
|
|
|
std::tuple<int64_t, int64_t, UID> prevGranuleLock = decodeBlobGranuleLockValue(lockValue.get());
|
|
|
|
if (std::get<0>(prevGranuleLock) > bmData->epoch) {
|
|
|
|
printf("BM %s found a higher epoch %d than %d for granule lock of [%s - %s)\n",
|
|
|
|
bmData->id.toString().c_str(),
|
|
|
|
std::get<0>(prevGranuleLock),
|
|
|
|
bmData->epoch,
|
|
|
|
range.begin.printable().c_str(),
|
|
|
|
range.end.printable().c_str());
|
|
|
|
|
|
|
|
if (bmData->iAmReplaced.canBeSet()) {
|
|
|
|
bmData->iAmReplaced.send(Void());
|
|
|
|
}
|
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
if (newLockSeqno == -1) {
|
|
|
|
newLockSeqno = bmData->seqNo;
|
|
|
|
bmData->seqNo++;
|
|
|
|
ASSERT(newLockSeqno > std::get<1>(prevGranuleLock));
|
|
|
|
} else {
|
|
|
|
// previous transaction could have succeeded but got commit_unknown_result
|
|
|
|
ASSERT(newLockSeqno >= std::get<1>(prevGranuleLock));
|
|
|
|
}
|
|
|
|
|
|
|
|
tr->set(lockKey, blobGranuleLockValueFor(bmData->epoch, newLockSeqno, std::get<2>(prevGranuleLock)));
|
|
|
|
|
|
|
|
// set up split metadata
|
|
|
|
for (int i = 0; i < newRanges.size() - 1; i++) {
|
|
|
|
Tuple key;
|
|
|
|
key.append(range.begin).append(range.end).append(newRanges[i]);
|
|
|
|
tr->set(key.getDataAsStandalone().withPrefix(blobGranuleSplitKeys.begin),
|
|
|
|
blobGranuleSplitValueFor(BlobGranuleSplitState::Started));
|
|
|
|
|
|
|
|
// acquire granule lock so nobody else can make changes to this granule.
|
|
|
|
}
|
|
|
|
wait(tr->commit());
|
|
|
|
break;
|
|
|
|
} catch (Error& e) {
|
|
|
|
wait(tr->onError(e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Splitting range [%s - %s) into:\n", range.begin.printable().c_str(), range.end.printable().c_str());
|
|
|
|
for (int i = 0; i < newRanges.size() - 1; i++) {
|
|
|
|
printf(" [%s - %s)\n", newRanges[i].printable().c_str(), newRanges[i + 1].printable().c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// transaction committed, send range assignments
|
|
|
|
// revoke from current worker
|
|
|
|
RangeAssignment raRevoke;
|
|
|
|
raRevoke.isAssign = false;
|
|
|
|
raRevoke.worker = currentWorkerId;
|
|
|
|
raRevoke.keyRange = range;
|
|
|
|
raRevoke.revoke = RangeRevokeData(false); // not a dispose
|
|
|
|
bmData->rangesToAssign.send(raRevoke);
|
|
|
|
|
|
|
|
std::vector<KeyRange> originalRange;
|
|
|
|
originalRange.push_back(range);
|
|
|
|
for (int i = 0; i < newRanges.size() - 1; i++) {
|
|
|
|
// reassign new range and do handover of previous range
|
|
|
|
RangeAssignment raAssignSplit;
|
|
|
|
raAssignSplit.isAssign = true;
|
|
|
|
raAssignSplit.keyRange = KeyRangeRef(newRanges[i], newRanges[i + 1]);
|
|
|
|
raAssignSplit.assign = RangeAssignmentData(false, originalRange);
|
|
|
|
// don't care who this range gets assigned to
|
|
|
|
bmData->rangesToAssign.send(raAssignSplit);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
|
|
|
|
ACTOR Future<Void> monitorBlobWorker(BlobManagerData* bmData, BlobWorkerInterface bwInterf) {
|
|
|
|
try {
|
|
|
|
state PromiseStream<Future<Void>> addActor;
|
|
|
|
state Future<Void> collection = actorCollection(addActor.getFuture());
|
|
|
|
state Future<Void> waitFailure = waitFailureClient(bwInterf.waitFailure, SERVER_KNOBS->BLOB_WORKER_TIMEOUT);
|
|
|
|
state ReplyPromiseStream<GranuleStatusReply> statusStream =
|
|
|
|
bwInterf.granuleStatusStreamRequest.getReplyStream(GranuleStatusStreamRequest(bmData->epoch));
|
|
|
|
state KeyRangeMap<std::pair<int64_t, int64_t>> lastSeenSeqno;
|
|
|
|
|
|
|
|
loop choose {
|
|
|
|
when(wait(waitFailure)) {
|
|
|
|
// FIXME: actually handle this!!
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM %lld detected BW %s is dead\n", bmData->epoch, bwInterf.id().toString().c_str());
|
|
|
|
}
|
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
when(GranuleStatusReply _rep = waitNext(statusStream.getFuture())) {
|
|
|
|
GranuleStatusReply rep = _rep;
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM %lld got status of [%s - %s) @ (%lld, %lld) from BW %s: %s\n",
|
|
|
|
bmData->epoch,
|
|
|
|
rep.granuleRange.begin.printable().c_str(),
|
|
|
|
rep.granuleRange.end.printable().c_str(),
|
|
|
|
rep.epoch,
|
|
|
|
rep.seqno,
|
|
|
|
bwInterf.id().toString().c_str(),
|
|
|
|
rep.doSplit ? "split" : "");
|
|
|
|
}
|
|
|
|
if (rep.epoch > bmData->epoch) {
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM heard from BW that there is a new manager with higher epoch\n");
|
|
|
|
}
|
|
|
|
if (bmData->iAmReplaced.canBeSet()) {
|
|
|
|
bmData->iAmReplaced.send(Void());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO maybe this won't be true eventually, but right now the only time the blob worker reports back is
|
|
|
|
// to split the range.
|
|
|
|
ASSERT(rep.doSplit);
|
|
|
|
|
|
|
|
// FIXME: only evaluate for split if this worker currently owns the granule in this blob manager's
|
|
|
|
// mapping
|
|
|
|
|
|
|
|
auto lastReqForGranule = lastSeenSeqno.rangeContaining(rep.granuleRange.begin);
|
|
|
|
if (rep.granuleRange.begin == lastReqForGranule.begin() &&
|
|
|
|
rep.granuleRange.end == lastReqForGranule.end() && rep.epoch == lastReqForGranule.value().first &&
|
|
|
|
rep.seqno == lastReqForGranule.value().second) {
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Manager %lld received repeat status for the same granule [%s - %s) @ %lld, ignoring.",
|
|
|
|
bmData->epoch,
|
|
|
|
rep.granuleRange.begin.printable().c_str(),
|
|
|
|
rep.granuleRange.end.printable().c_str());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Manager %lld evaluating [%s - %s) for split\n",
|
|
|
|
bmData->epoch,
|
|
|
|
rep.granuleRange.begin.printable().c_str(),
|
|
|
|
rep.granuleRange.end.printable().c_str());
|
|
|
|
}
|
|
|
|
lastSeenSeqno.insert(rep.granuleRange, std::pair(rep.epoch, rep.seqno));
|
|
|
|
addActor.send(maybeSplitRange(bmData, bwInterf.id(), rep.granuleRange));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (Error& e) {
|
|
|
|
// FIXME: forward errors somewhere from here
|
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("BM got unexpected error %s monitoring BW %s\n", e.name(), bwInterf.id().toString().c_str());
|
|
|
|
}
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-31 02:59:53 +08:00
|
|
|
// TODO this is only for chaos testing right now!! REMOVE LATER
|
|
|
|
ACTOR Future<Void> rangeMover(BlobManagerData* bmData) {
|
|
|
|
loop {
|
|
|
|
wait(delay(30.0));
|
|
|
|
|
|
|
|
if (bmData->workersById.size() > 1) {
|
|
|
|
int tries = 10;
|
|
|
|
while (tries > 0) {
|
|
|
|
tries--;
|
|
|
|
auto randomRange = bmData->workerAssignments.randomRange();
|
|
|
|
if (randomRange.value() != UID()) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Range mover moving range [%s - %s): %s\n",
|
|
|
|
randomRange.begin().printable().c_str(),
|
|
|
|
randomRange.end().printable().c_str(),
|
|
|
|
randomRange.value().toString().c_str());
|
|
|
|
}
|
2021-08-31 02:59:53 +08:00
|
|
|
|
2021-09-04 04:13:26 +08:00
|
|
|
RangeAssignment revokeOld;
|
|
|
|
revokeOld.isAssign = false;
|
|
|
|
revokeOld.keyRange = randomRange.range();
|
|
|
|
revokeOld.worker = randomRange.value();
|
|
|
|
revokeOld.revoke = RangeRevokeData(false);
|
|
|
|
bmData->rangesToAssign.send(revokeOld);
|
|
|
|
|
|
|
|
RangeAssignment assignNew;
|
|
|
|
assignNew.isAssign = true;
|
|
|
|
assignNew.keyRange = randomRange.range();
|
|
|
|
assignNew.assign =
|
|
|
|
RangeAssignmentData(false, std::vector<KeyRange>()); // not a continue, no boundary change
|
|
|
|
bmData->rangesToAssign.send(assignNew);
|
2021-08-31 02:59:53 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-09-01 01:30:43 +08:00
|
|
|
if (tries == 0 && BM_DEBUG) {
|
2021-08-31 02:59:53 +08:00
|
|
|
printf("Range mover couldn't find range to move, skipping\n");
|
|
|
|
}
|
2021-09-01 01:30:43 +08:00
|
|
|
} else if (BM_DEBUG) {
|
2021-08-31 02:59:53 +08:00
|
|
|
printf("Range mover found %d workers, skipping\n", bmData->workerAssignments.size());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-31 02:07:25 +08:00
|
|
|
// TODO MOVE ELSEWHERE
|
|
|
|
// TODO replace locality with full BlobManagerInterface eventually
|
|
|
|
ACTOR Future<Void> blobManager(LocalityData locality, Reference<AsyncVar<ServerDBInfo> const> dbInfo) {
|
|
|
|
state BlobManagerData self(deterministicRandom()->randomUniqueID(),
|
|
|
|
openDBOnServer(dbInfo, TaskPriority::DefaultEndpoint, LockAware::True));
|
|
|
|
|
|
|
|
state PromiseStream<Future<Void>> addActor;
|
|
|
|
state Future<Void> collection = actorCollection(addActor.getFuture());
|
|
|
|
|
|
|
|
// TODO remove once we have persistence + failure detection
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Blob manager nuking previous workers and range assignments on startup\n");
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
wait(nukeBlobWorkerData(&self));
|
|
|
|
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Blob manager nuked previous workers and range assignments\n");
|
|
|
|
printf("Blob manager taking lock\n");
|
|
|
|
}
|
|
|
|
|
2021-08-31 02:07:25 +08:00
|
|
|
int64_t _epoch = wait(acquireManagerLock(&self));
|
|
|
|
self.epoch = _epoch;
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Blob manager acquired lock at epoch %lld\n", _epoch);
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
|
|
|
|
int numWorkers = 2;
|
|
|
|
for (int i = 0; i < numWorkers; i++) {
|
|
|
|
state BlobWorkerInterface bwInterf(locality, deterministicRandom()->randomUniqueID());
|
|
|
|
bwInterf.initEndpoints();
|
|
|
|
self.workersById.insert({ bwInterf.id(), bwInterf });
|
2021-09-02 11:36:44 +08:00
|
|
|
self.workerStats.insert({ bwInterf.id(), BlobWorkerStats() });
|
2021-09-04 04:13:26 +08:00
|
|
|
self.workerMonitors.insert({ bwInterf.id(), monitorBlobWorker(&self, bwInterf) });
|
2021-08-31 02:07:25 +08:00
|
|
|
addActor.send(blobWorker(bwInterf, dbInfo));
|
|
|
|
}
|
|
|
|
|
|
|
|
addActor.send(monitorClientRanges(&self));
|
|
|
|
addActor.send(rangeAssigner(&self));
|
2021-09-01 01:30:43 +08:00
|
|
|
|
|
|
|
// TODO add back once everything is properly implemented!
|
|
|
|
// addActor.send(rangeMover(&self));
|
2021-08-31 02:07:25 +08:00
|
|
|
|
|
|
|
// TODO probably other things here eventually
|
|
|
|
loop choose {
|
|
|
|
when(wait(self.iAmReplaced.getFuture())) {
|
2021-09-01 01:30:43 +08:00
|
|
|
if (BM_DEBUG) {
|
|
|
|
printf("Blob Manager exiting because it is replaced\n");
|
|
|
|
}
|
2021-08-31 02:07:25 +08:00
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-05 05:47:18 +08:00
|
|
|
// Test:
|
|
|
|
// start empty
|
|
|
|
// DB has [A - B). That should show up in knownBlobRanges and should be in added
|
|
|
|
// DB has nothing. knownBlobRanges should be empty and [A - B) should be in removed
|
|
|
|
// DB has [A - B) and [C - D). They should both show up in knownBlobRanges and added.
|
|
|
|
// DB has [A - D). It should show up coalesced in knownBlobRanges, and [B - C) should be in added.
|
|
|
|
// DB has [A - C). It should show up coalesced in knownBlobRanges, and [C - D) should be in removed.
|
|
|
|
// DB has [B - C). It should show up coalesced in knownBlobRanges, and [A - B) should be removed.
|
|
|
|
// DB has [B - D). It should show up coalesced in knownBlobRanges, and [C - D) should be removed.
|
|
|
|
// DB has [A - D). It should show up coalesced in knownBlobRanges, and [A - B) should be removed.
|
|
|
|
// DB has [A - B) and [C - D). They should show up in knownBlobRanges, and [B - C) should be in removed.
|
|
|
|
// DB has [B - C). It should show up in knownBlobRanges, [B - C) should be in added, and [A - B) and [C - D) should be
|
|
|
|
// in removed.
|
|
|
|
TEST_CASE("/blobmanager/updateranges") {
|
|
|
|
KeyRangeMap<bool> knownBlobRanges(false, normalKeys.end);
|
|
|
|
Arena ar;
|
|
|
|
|
|
|
|
VectorRef<KeyRangeRef> added;
|
|
|
|
VectorRef<KeyRangeRef> removed;
|
|
|
|
|
|
|
|
StringRef active = LiteralStringRef("1");
|
|
|
|
StringRef inactive = StringRef();
|
|
|
|
|
|
|
|
RangeResult dbDataEmpty;
|
|
|
|
vector<std::pair<KeyRangeRef, bool>> kbrRanges;
|
|
|
|
|
|
|
|
StringRef keyA = StringRef(ar, LiteralStringRef("A"));
|
|
|
|
StringRef keyB = StringRef(ar, LiteralStringRef("B"));
|
|
|
|
StringRef keyC = StringRef(ar, LiteralStringRef("C"));
|
|
|
|
StringRef keyD = StringRef(ar, LiteralStringRef("D"));
|
|
|
|
|
|
|
|
// db data setup
|
|
|
|
RangeResult dbDataAB;
|
|
|
|
dbDataAB.emplace_back(ar, keyA, active);
|
|
|
|
dbDataAB.emplace_back(ar, keyB, inactive);
|
|
|
|
|
|
|
|
RangeResult dbDataAC;
|
|
|
|
dbDataAC.emplace_back(ar, keyA, active);
|
|
|
|
dbDataAC.emplace_back(ar, keyC, inactive);
|
|
|
|
|
|
|
|
RangeResult dbDataAD;
|
|
|
|
dbDataAD.emplace_back(ar, keyA, active);
|
|
|
|
dbDataAD.emplace_back(ar, keyD, inactive);
|
|
|
|
|
|
|
|
RangeResult dbDataBC;
|
|
|
|
dbDataBC.emplace_back(ar, keyB, active);
|
|
|
|
dbDataBC.emplace_back(ar, keyC, inactive);
|
|
|
|
|
|
|
|
RangeResult dbDataBD;
|
|
|
|
dbDataBD.emplace_back(ar, keyB, active);
|
|
|
|
dbDataBD.emplace_back(ar, keyD, inactive);
|
|
|
|
|
|
|
|
RangeResult dbDataCD;
|
|
|
|
dbDataCD.emplace_back(ar, keyC, active);
|
|
|
|
dbDataCD.emplace_back(ar, keyD, inactive);
|
|
|
|
|
|
|
|
RangeResult dbDataAB_CD;
|
|
|
|
dbDataAB_CD.emplace_back(ar, keyA, active);
|
|
|
|
dbDataAB_CD.emplace_back(ar, keyB, inactive);
|
|
|
|
dbDataAB_CD.emplace_back(ar, keyC, active);
|
|
|
|
dbDataAB_CD.emplace_back(ar, keyD, inactive);
|
|
|
|
|
|
|
|
// key ranges setup
|
|
|
|
KeyRangeRef rangeAB = KeyRangeRef(keyA, keyB);
|
|
|
|
KeyRangeRef rangeAC = KeyRangeRef(keyA, keyC);
|
|
|
|
KeyRangeRef rangeAD = KeyRangeRef(keyA, keyD);
|
|
|
|
|
|
|
|
KeyRangeRef rangeBC = KeyRangeRef(keyB, keyC);
|
|
|
|
KeyRangeRef rangeBD = KeyRangeRef(keyB, keyD);
|
|
|
|
|
|
|
|
KeyRangeRef rangeCD = KeyRangeRef(keyC, keyD);
|
|
|
|
|
|
|
|
KeyRangeRef rangeStartToA = KeyRangeRef(normalKeys.begin, keyA);
|
|
|
|
KeyRangeRef rangeStartToB = KeyRangeRef(normalKeys.begin, keyB);
|
|
|
|
KeyRangeRef rangeStartToC = KeyRangeRef(normalKeys.begin, keyC);
|
|
|
|
KeyRangeRef rangeBToEnd = KeyRangeRef(keyB, normalKeys.end);
|
|
|
|
KeyRangeRef rangeCToEnd = KeyRangeRef(keyC, normalKeys.end);
|
|
|
|
KeyRangeRef rangeDToEnd = KeyRangeRef(keyD, normalKeys.end);
|
|
|
|
|
|
|
|
// actual test
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges.size() == 1);
|
|
|
|
ASSERT(kbrRanges[0].first == normalKeys);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
|
|
|
|
// DB has [A - B)
|
|
|
|
kbrRanges.clear();
|
|
|
|
added.clear();
|
|
|
|
removed.clear();
|
|
|
|
updateClientBlobRanges(&knownBlobRanges, dbDataAB, ar, &added, &removed);
|
|
|
|
|
|
|
|
ASSERT(added.size() == 1);
|
|
|
|
ASSERT(added[0] == rangeAB);
|
|
|
|
|
|
|
|
ASSERT(removed.size() == 0);
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges.size() == 3);
|
|
|
|
ASSERT(kbrRanges[0].first == rangeStartToA);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
ASSERT(kbrRanges[1].first == rangeAB);
|
|
|
|
ASSERT(kbrRanges[1].second);
|
|
|
|
ASSERT(kbrRanges[2].first == rangeBToEnd);
|
|
|
|
ASSERT(!kbrRanges[2].second);
|
|
|
|
|
|
|
|
// DB has nothing
|
|
|
|
kbrRanges.clear();
|
|
|
|
added.clear();
|
|
|
|
removed.clear();
|
|
|
|
updateClientBlobRanges(&knownBlobRanges, dbDataEmpty, ar, &added, &removed);
|
|
|
|
|
|
|
|
ASSERT(added.size() == 0);
|
|
|
|
|
|
|
|
ASSERT(removed.size() == 1);
|
|
|
|
ASSERT(removed[0] == rangeAB);
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges[0].first == normalKeys);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
|
|
|
|
// DB has [A - B) and [C - D)
|
|
|
|
kbrRanges.clear();
|
|
|
|
added.clear();
|
|
|
|
removed.clear();
|
|
|
|
updateClientBlobRanges(&knownBlobRanges, dbDataAB_CD, ar, &added, &removed);
|
|
|
|
|
|
|
|
ASSERT(added.size() == 2);
|
|
|
|
ASSERT(added[0] == rangeAB);
|
|
|
|
ASSERT(added[1] == rangeCD);
|
|
|
|
|
|
|
|
ASSERT(removed.size() == 0);
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges.size() == 5);
|
|
|
|
ASSERT(kbrRanges[0].first == rangeStartToA);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
ASSERT(kbrRanges[1].first == rangeAB);
|
|
|
|
ASSERT(kbrRanges[1].second);
|
|
|
|
ASSERT(kbrRanges[2].first == rangeBC);
|
|
|
|
ASSERT(!kbrRanges[2].second);
|
|
|
|
ASSERT(kbrRanges[3].first == rangeCD);
|
|
|
|
ASSERT(kbrRanges[3].second);
|
|
|
|
ASSERT(kbrRanges[4].first == rangeDToEnd);
|
|
|
|
ASSERT(!kbrRanges[4].second);
|
|
|
|
|
|
|
|
// DB has [A - D)
|
|
|
|
kbrRanges.clear();
|
|
|
|
added.clear();
|
|
|
|
removed.clear();
|
|
|
|
updateClientBlobRanges(&knownBlobRanges, dbDataAD, ar, &added, &removed);
|
|
|
|
|
|
|
|
ASSERT(added.size() == 1);
|
|
|
|
ASSERT(added[0] == rangeBC);
|
|
|
|
|
|
|
|
ASSERT(removed.size() == 0);
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges.size() == 3);
|
|
|
|
ASSERT(kbrRanges[0].first == rangeStartToA);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
ASSERT(kbrRanges[1].first == rangeAD);
|
|
|
|
ASSERT(kbrRanges[1].second);
|
|
|
|
ASSERT(kbrRanges[2].first == rangeDToEnd);
|
|
|
|
ASSERT(!kbrRanges[2].second);
|
|
|
|
|
|
|
|
// DB has [A - C)
|
|
|
|
kbrRanges.clear();
|
|
|
|
added.clear();
|
|
|
|
removed.clear();
|
|
|
|
updateClientBlobRanges(&knownBlobRanges, dbDataAC, ar, &added, &removed);
|
|
|
|
|
|
|
|
ASSERT(added.size() == 0);
|
|
|
|
|
|
|
|
ASSERT(removed.size() == 1);
|
|
|
|
ASSERT(removed[0] == rangeCD);
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges.size() == 3);
|
|
|
|
ASSERT(kbrRanges[0].first == rangeStartToA);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
ASSERT(kbrRanges[1].first == rangeAC);
|
|
|
|
ASSERT(kbrRanges[1].second);
|
|
|
|
ASSERT(kbrRanges[2].first == rangeCToEnd);
|
|
|
|
ASSERT(!kbrRanges[2].second);
|
|
|
|
|
|
|
|
// DB has [B - C)
|
|
|
|
kbrRanges.clear();
|
|
|
|
added.clear();
|
|
|
|
removed.clear();
|
|
|
|
updateClientBlobRanges(&knownBlobRanges, dbDataBC, ar, &added, &removed);
|
|
|
|
|
|
|
|
ASSERT(added.size() == 0);
|
|
|
|
|
|
|
|
ASSERT(removed.size() == 1);
|
|
|
|
ASSERT(removed[0] == rangeAB);
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges.size() == 3);
|
|
|
|
ASSERT(kbrRanges[0].first == rangeStartToB);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
ASSERT(kbrRanges[1].first == rangeBC);
|
|
|
|
ASSERT(kbrRanges[1].second);
|
|
|
|
ASSERT(kbrRanges[2].first == rangeCToEnd);
|
|
|
|
ASSERT(!kbrRanges[2].second);
|
|
|
|
|
|
|
|
// DB has [B - D)
|
|
|
|
kbrRanges.clear();
|
|
|
|
added.clear();
|
|
|
|
removed.clear();
|
|
|
|
updateClientBlobRanges(&knownBlobRanges, dbDataBD, ar, &added, &removed);
|
|
|
|
|
|
|
|
ASSERT(added.size() == 1);
|
|
|
|
ASSERT(added[0] == rangeCD);
|
|
|
|
|
|
|
|
ASSERT(removed.size() == 0);
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges.size() == 3);
|
|
|
|
ASSERT(kbrRanges[0].first == rangeStartToB);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
ASSERT(kbrRanges[1].first == rangeBD);
|
|
|
|
ASSERT(kbrRanges[1].second);
|
|
|
|
ASSERT(kbrRanges[2].first == rangeDToEnd);
|
|
|
|
ASSERT(!kbrRanges[2].second);
|
|
|
|
|
|
|
|
// DB has [A - D)
|
|
|
|
kbrRanges.clear();
|
|
|
|
added.clear();
|
|
|
|
removed.clear();
|
|
|
|
updateClientBlobRanges(&knownBlobRanges, dbDataAD, ar, &added, &removed);
|
|
|
|
|
|
|
|
ASSERT(added.size() == 1);
|
|
|
|
ASSERT(added[0] == rangeAB);
|
|
|
|
|
|
|
|
ASSERT(removed.size() == 0);
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges.size() == 3);
|
|
|
|
ASSERT(kbrRanges[0].first == rangeStartToA);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
ASSERT(kbrRanges[1].first == rangeAD);
|
|
|
|
ASSERT(kbrRanges[1].second);
|
|
|
|
ASSERT(kbrRanges[2].first == rangeDToEnd);
|
|
|
|
ASSERT(!kbrRanges[2].second);
|
|
|
|
|
|
|
|
// DB has [A - B) and [C - D)
|
|
|
|
kbrRanges.clear();
|
|
|
|
added.clear();
|
|
|
|
removed.clear();
|
|
|
|
updateClientBlobRanges(&knownBlobRanges, dbDataAB_CD, ar, &added, &removed);
|
|
|
|
|
|
|
|
ASSERT(added.size() == 0);
|
|
|
|
|
|
|
|
ASSERT(removed.size() == 1);
|
|
|
|
ASSERT(removed[0] == rangeBC);
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges.size() == 5);
|
|
|
|
ASSERT(kbrRanges[0].first == rangeStartToA);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
ASSERT(kbrRanges[1].first == rangeAB);
|
|
|
|
ASSERT(kbrRanges[1].second);
|
|
|
|
ASSERT(kbrRanges[2].first == rangeBC);
|
|
|
|
ASSERT(!kbrRanges[2].second);
|
|
|
|
ASSERT(kbrRanges[3].first == rangeCD);
|
|
|
|
ASSERT(kbrRanges[3].second);
|
|
|
|
ASSERT(kbrRanges[4].first == rangeDToEnd);
|
|
|
|
ASSERT(!kbrRanges[4].second);
|
|
|
|
|
|
|
|
// DB has [B - C)
|
|
|
|
kbrRanges.clear();
|
|
|
|
added.clear();
|
|
|
|
removed.clear();
|
|
|
|
updateClientBlobRanges(&knownBlobRanges, dbDataBC, ar, &added, &removed);
|
|
|
|
|
|
|
|
ASSERT(added.size() == 1);
|
|
|
|
ASSERT(added[0] == rangeBC);
|
|
|
|
|
|
|
|
ASSERT(removed.size() == 2);
|
|
|
|
ASSERT(removed[0] == rangeAB);
|
|
|
|
ASSERT(removed[1] == rangeCD);
|
|
|
|
|
|
|
|
getRanges(kbrRanges, knownBlobRanges);
|
|
|
|
ASSERT(kbrRanges.size() == 3);
|
|
|
|
ASSERT(kbrRanges[0].first == rangeStartToB);
|
|
|
|
ASSERT(!kbrRanges[0].second);
|
|
|
|
ASSERT(kbrRanges[1].first == rangeBC);
|
|
|
|
ASSERT(kbrRanges[1].second);
|
|
|
|
ASSERT(kbrRanges[2].first == rangeCToEnd);
|
|
|
|
ASSERT(!kbrRanges[2].second);
|
|
|
|
|
|
|
|
return Void();
|
|
|
|
}
|