Deleting a block doesn't set the version number to MAX_UINT64, but just increases it by one. This makes sure, that if (in case of a synchronization conflict) the block is reintroduced by another client, this is accepted.

This commit is contained in:
Sebastian Messmer 2016-06-23 15:33:47 -07:00
parent 5534b56ce3
commit 1a72d3c226
5 changed files with 44 additions and 44 deletions

View File

@ -71,20 +71,16 @@ bool KnownBlockVersions::checkAndUpdateVersion(uint32_t clientId, const Key &key
uint64_t KnownBlockVersions::incrementVersion(const Key &key, uint64_t lastVersion) { uint64_t KnownBlockVersions::incrementVersion(const Key &key, uint64_t lastVersion) {
unique_lock<mutex> lock(_mutex); unique_lock<mutex> lock(_mutex);
uint64_t &found = _knownVersions[{_myClientId, key}]; // If the entry doesn't exist, this creates it with value 0. uint64_t &found = _knownVersions[{_myClientId, key}]; // If the entry doesn't exist, this creates it with value 0.
found = std::max(lastVersion + 1, found + 1); uint64_t newVersion = std::max(lastVersion + 1, found + 1);
if (newVersion == std::numeric_limits<uint64_t>::max()) {
// It's *very* unlikely we ever run out of version numbers in 64bit...but just to be sure...
throw std::runtime_error("Version overflow");
}
found = newVersion;
_lastUpdateClientId[key] = _myClientId; _lastUpdateClientId[key] = _myClientId;
return found; return found;
} }
void KnownBlockVersions::setVersion(uint32_t clientId, const Key &key, uint64_t version) {
uint64_t &found = _knownVersions[{clientId, key}];
if (found > version) {
throw std::runtime_error("Version can only be set to higher version numbers.");
}
found = version;
_lastUpdateClientId[key] = clientId;
}
void KnownBlockVersions::_loadStateFile() { void KnownBlockVersions::_loadStateFile() {
optional<Data> file = Data::LoadFromFile(_stateFilePath); optional<Data> file = Data::LoadFromFile(_stateFilePath);
if (file == none) { if (file == none) {

View File

@ -26,7 +26,6 @@ namespace blockstore {
uint64_t incrementVersion(const Key &key, uint64_t lastVersion); uint64_t incrementVersion(const Key &key, uint64_t lastVersion);
uint64_t getBlockVersion(uint32_t clientId, const Key &key) const; uint64_t getBlockVersion(uint32_t clientId, const Key &key) const;
void setVersion(uint32_t clientId, const Key &key, uint64_t version);
uint32_t myClientId() const; uint32_t myClientId() const;

View File

@ -41,6 +41,7 @@ public:
size_t size() const override; size_t size() const override;
void resize(size_t newSize) override; void resize(size_t newSize) override;
uint64_t version() const;
cpputils::unique_ref<Block> releaseBlock(); cpputils::unique_ref<Block> releaseBlock();
private: private:
@ -59,14 +60,13 @@ private:
// This header is prepended to blocks to allow future versions to have compatibility. // This header is prepended to blocks to allow future versions to have compatibility.
static constexpr uint16_t FORMAT_VERSION_HEADER = 0; static constexpr uint16_t FORMAT_VERSION_HEADER = 0;
static constexpr uint64_t VERSION_ZERO = 0;
std::mutex _mutex; std::mutex _mutex;
DISALLOW_COPY_AND_ASSIGN(VersionCountingBlock); DISALLOW_COPY_AND_ASSIGN(VersionCountingBlock);
public: public:
static constexpr uint64_t VERSION_ZERO = 0;
static constexpr uint64_t VERSION_DELETED = std::numeric_limits<uint64_t>::max();
static constexpr unsigned int CLIENTID_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER); static constexpr unsigned int CLIENTID_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER);
static constexpr unsigned int VERSION_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t); static constexpr unsigned int VERSION_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t);
static constexpr unsigned int HEADER_LENGTH = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t) + sizeof(VERSION_ZERO); static constexpr unsigned int HEADER_LENGTH = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t) + sizeof(VERSION_ZERO);
@ -186,11 +186,6 @@ inline void VersionCountingBlock::resize(size_t newSize) {
inline void VersionCountingBlock::_storeToBaseBlock() { inline void VersionCountingBlock::_storeToBaseBlock() {
if (_dataChanged) { if (_dataChanged) {
_version = _knownBlockVersions->incrementVersion(key(), _version); _version = _knownBlockVersions->incrementVersion(key(), _version);
if (_version == VERSION_DELETED) {
// It's *very* unlikely we ever run out of version numbers in 64bit...but just to be sure...
throw std::runtime_error("Version overflow");
static_assert(VERSION_DELETED == std::numeric_limits<uint64_t>::max(), "The check above assumes VERSION_DELETE to be an upper bound.");
}
uint32_t myClientId = _knownBlockVersions->myClientId(); uint32_t myClientId = _knownBlockVersions->myClientId();
std::memcpy(_dataWithHeader.dataOffset(CLIENTID_HEADER_OFFSET), &myClientId, sizeof(myClientId)); std::memcpy(_dataWithHeader.dataOffset(CLIENTID_HEADER_OFFSET), &myClientId, sizeof(myClientId));
std::memcpy(_dataWithHeader.dataOffset(VERSION_HEADER_OFFSET), &_version, sizeof(_version)); std::memcpy(_dataWithHeader.dataOffset(VERSION_HEADER_OFFSET), &_version, sizeof(_version));
@ -212,6 +207,10 @@ inline uint64_t VersionCountingBlock::blockSizeFromPhysicalBlockSize(uint64_t bl
return blockSize - HEADER_LENGTH; return blockSize - HEADER_LENGTH;
} }
inline uint64_t VersionCountingBlock::version() const {
return _version;
}
} }
} }

View File

@ -62,9 +62,9 @@ inline void VersionCountingBlockStore::remove(cpputils::unique_ref<Block> block)
Key key = block->key(); Key key = block->key();
auto versionCountingBlock = cpputils::dynamic_pointer_move<VersionCountingBlock>(block); auto versionCountingBlock = cpputils::dynamic_pointer_move<VersionCountingBlock>(block);
ASSERT(versionCountingBlock != boost::none, "Block is not an VersionCountingBlock"); ASSERT(versionCountingBlock != boost::none, "Block is not an VersionCountingBlock");
_knownBlockVersions.incrementVersion(key, (*versionCountingBlock)->version());
auto baseBlock = (*versionCountingBlock)->releaseBlock(); auto baseBlock = (*versionCountingBlock)->releaseBlock();
_baseBlockStore->remove(std::move(baseBlock)); _baseBlockStore->remove(std::move(baseBlock));
_knownBlockVersions.setVersion(_knownBlockVersions.myClientId(), key, VersionCountingBlock::VERSION_DELETED);
} }
inline uint64_t VersionCountingBlockStore::numBlocks() const { inline uint64_t VersionCountingBlockStore::numBlocks() const {

View File

@ -19,6 +19,12 @@ public:
TempFile stateFile; TempFile stateFile;
KnownBlockVersions testobj; KnownBlockVersions testobj;
void setVersion(KnownBlockVersions *testobj, uint32_t clientId, const blockstore::Key &key, uint64_t version) {
if (!testobj->checkAndUpdateVersion(clientId, key, version)) {
throw std::runtime_error("Couldn't increase version");
}
}
void EXPECT_VERSION_IS(uint64_t version, KnownBlockVersions *testobj, blockstore::Key &key, uint32_t clientId) { void EXPECT_VERSION_IS(uint64_t version, KnownBlockVersions *testobj, blockstore::Key &key, uint32_t clientId) {
EXPECT_FALSE(testobj->checkAndUpdateVersion(clientId, key, version-1)); EXPECT_FALSE(testobj->checkAndUpdateVersion(clientId, key, version-1));
EXPECT_TRUE(testobj->checkAndUpdateVersion(clientId, key, version+1)); EXPECT_TRUE(testobj->checkAndUpdateVersion(clientId, key, version+1));
@ -26,34 +32,34 @@ public:
}; };
TEST_F(KnownBlockVersionsTest, setandget) { TEST_F(KnownBlockVersionsTest, setandget) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
} }
TEST_F(KnownBlockVersionsTest, setandget_isPerClientId) { TEST_F(KnownBlockVersionsTest, setandget_isPerClientId) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
testobj.setVersion(clientId2, key, 3); setVersion(&testobj, clientId2, key, 3);
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
EXPECT_EQ(3u, testobj.getBlockVersion(clientId2, key)); EXPECT_EQ(3u, testobj.getBlockVersion(clientId2, key));
} }
TEST_F(KnownBlockVersionsTest, setandget_isPerBlock) { TEST_F(KnownBlockVersionsTest, setandget_isPerBlock) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
testobj.setVersion(clientId, key2, 3); setVersion(&testobj, clientId, key2, 3);
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
EXPECT_EQ(3u, testobj.getBlockVersion(clientId, key2)); EXPECT_EQ(3u, testobj.getBlockVersion(clientId, key2));
} }
TEST_F(KnownBlockVersionsTest, setandget_allowsIncreasing) { TEST_F(KnownBlockVersionsTest, setandget_allowsIncreasing) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
testobj.setVersion(clientId, key, 6); setVersion(&testobj, clientId, key, 6);
EXPECT_EQ(6u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(6u, testobj.getBlockVersion(clientId, key));
} }
TEST_F(KnownBlockVersionsTest, setandget_doesntAllowDecreasing) { TEST_F(KnownBlockVersionsTest, setandget_doesntAllowDecreasing) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_ANY_THROW( EXPECT_ANY_THROW(
testobj.setVersion(clientId, key, 4); setVersion(&testobj, clientId, key, 4);
); );
} }
@ -74,28 +80,28 @@ TEST_F(KnownBlockVersionsTest, incrementVersion_newentry_versionnotzero) {
} }
TEST_F(KnownBlockVersionsTest, incrementVersion_oldentry_sameVersion) { TEST_F(KnownBlockVersionsTest, incrementVersion_oldentry_sameVersion) {
testobj.setVersion(testobj.myClientId(), key, 5); setVersion(&testobj, testobj.myClientId(), key, 5);
auto version = testobj.incrementVersion(key, 5); auto version = testobj.incrementVersion(key, 5);
EXPECT_EQ(6u, version); EXPECT_EQ(6u, version);
EXPECT_EQ(6u, testobj.getBlockVersion(testobj.myClientId(), key)); EXPECT_EQ(6u, testobj.getBlockVersion(testobj.myClientId(), key));
} }
TEST_F(KnownBlockVersionsTest, incrementVersion_oldentry_lowerVersion1) { TEST_F(KnownBlockVersionsTest, incrementVersion_oldentry_lowerVersion1) {
testobj.setVersion(testobj.myClientId(), key, 5); setVersion(&testobj, testobj.myClientId(), key, 5);
auto version = testobj.incrementVersion(key, 4); auto version = testobj.incrementVersion(key, 4);
EXPECT_EQ(6u, version); EXPECT_EQ(6u, version);
EXPECT_EQ(6u, testobj.getBlockVersion(testobj.myClientId(), key)); EXPECT_EQ(6u, testobj.getBlockVersion(testobj.myClientId(), key));
} }
TEST_F(KnownBlockVersionsTest, incrementVersion_oldentry_lowerVersion2) { TEST_F(KnownBlockVersionsTest, incrementVersion_oldentry_lowerVersion2) {
testobj.setVersion(testobj.myClientId(), key, 5); setVersion(&testobj, testobj.myClientId(), key, 5);
auto version = testobj.incrementVersion(key, 3); auto version = testobj.incrementVersion(key, 3);
EXPECT_EQ(6u, version); EXPECT_EQ(6u, version);
EXPECT_EQ(6u, testobj.getBlockVersion(testobj.myClientId(), key)); EXPECT_EQ(6u, testobj.getBlockVersion(testobj.myClientId(), key));
} }
TEST_F(KnownBlockVersionsTest, incrementVersion_oldentry_higherVersion) { TEST_F(KnownBlockVersionsTest, incrementVersion_oldentry_higherVersion) {
testobj.setVersion(testobj.myClientId(), key, 5); setVersion(&testobj, testobj.myClientId(), key, 5);
auto version = testobj.incrementVersion(key, 6); auto version = testobj.incrementVersion(key, 6);
EXPECT_EQ(7u, version); EXPECT_EQ(7u, version);
EXPECT_EQ(7u, testobj.getBlockVersion(testobj.myClientId(), key)); EXPECT_EQ(7u, testobj.getBlockVersion(testobj.myClientId(), key));
@ -107,46 +113,46 @@ TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_newentry) {
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientSameVersion) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientSameVersion) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 5)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 5));
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientLowerVersion) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientLowerVersion) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 4)); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 4));
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientNewerVersion) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientNewerVersion) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 6)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 6));
EXPECT_EQ(6u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(6u, testobj.getBlockVersion(clientId, key));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientSameVersion) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientSameVersion) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 5)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 5));
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
EXPECT_EQ(5u, testobj.getBlockVersion(clientId2, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId2, key));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientLowerVersion) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientLowerVersion) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 3)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 3));
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
EXPECT_EQ(3u, testobj.getBlockVersion(clientId2, key)); EXPECT_EQ(3u, testobj.getBlockVersion(clientId2, key));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientHigherVersion) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientHigherVersion) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 7)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 7));
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, key)); EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, key));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVersion) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVersion) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 7)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 7));
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 3)); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 3));
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
@ -154,7 +160,7 @@ TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVers
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersion) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersion) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 7)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 7));
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 5)); // Don't allow rollback to old client's newest block, if it was superseded by another client EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 5)); // Don't allow rollback to old client's newest block, if it was superseded by another client
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
@ -162,7 +168,7 @@ TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersi
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVersion) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVersion) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 7)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 7));
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 6)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 6));
EXPECT_EQ(6u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(6u, testobj.getBlockVersion(clientId, key));
@ -194,7 +200,7 @@ TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVer
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVersion_newClientIsSelf) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVersion_newClientIsSelf) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
testobj.incrementVersion(key, 6); testobj.incrementVersion(key, 6);
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 3)); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 3));
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
@ -202,7 +208,7 @@ TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVers
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersion_newClientIsSelf) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersion_newClientIsSelf) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
testobj.incrementVersion(key, 6); testobj.incrementVersion(key, 6);
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 5)); // Don't allow rollback to old client's newest block, if it was superseded by another client EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 5)); // Don't allow rollback to old client's newest block, if it was superseded by another client
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, key));
@ -210,7 +216,7 @@ TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersi
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVersion_newClientIsSelf) { TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVersion_newClientIsSelf) {
testobj.setVersion(clientId, key, 5); setVersion(&testobj, clientId, key, 5);
testobj.incrementVersion(key, 6); testobj.incrementVersion(key, 6);
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 6)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 6));
EXPECT_EQ(6u, testobj.getBlockVersion(clientId, key)); EXPECT_EQ(6u, testobj.getBlockVersion(clientId, key));