//////////////////////////////////////////////////////////////////////////////// /// @brief test suite for arangodb::cache::State /// /// @file /// /// DISCLAIMER /// /// Copyright 2017 ArangoDB GmbH, Cologne, Germany /// /// 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. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Daniel H. Larkin /// @author Copyright 2017, ArangoDB GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "Cache/Table.h" #include "Basics/Common.h" #include "Cache/Common.h" #include "Cache/PlainBucket.h" #include "catch.hpp" #include #include using namespace arangodb::cache; TEST_CASE("cache::Table", "[cache]") { SECTION("test static allocation size method") { for (uint32_t i = Table::minLogSize; i <= Table::maxLogSize; i++) { REQUIRE(Table::allocationSize(i) == (sizeof(Table) + (BUCKET_SIZE << i) + Table::padding)); } } SECTION("test basic constructor behavior") { for (uint32_t i = Table::minLogSize; i <= 20; i++) { auto table = std::make_shared(i); REQUIRE(table.get() != nullptr); REQUIRE(table->memoryUsage() == (sizeof(Table) + (BUCKET_SIZE << i) + Table::padding)); REQUIRE(table->logSize() == i); REQUIRE(table->size() == (static_cast(1) << i)); } } SECTION("test basic bucket-fetching behavior") { auto table = std::make_shared
(Table::minLogSize); REQUIRE(table.get() != nullptr); table->enable(); for (uint64_t i = 0; i < table->size(); i++) { uint32_t hash = static_cast(i << (32 - Table::minLogSize)); auto pair = table->fetchAndLockBucket(hash, -1); auto bucket = reinterpret_cast(pair.first); auto source = pair.second; REQUIRE(bucket != nullptr); REQUIRE(bucket->isLocked()); REQUIRE(source != nullptr); REQUIRE(source == table.get()); auto rawBucket = reinterpret_cast(table->primaryBucket(i)); REQUIRE(bucket == rawBucket); auto badPair = table->fetchAndLockBucket(hash, 10); auto badBucket = reinterpret_cast(badPair.first); auto badSource = badPair.second; REQUIRE(badBucket == nullptr); REQUIRE(badSource == nullptr); bucket->unlock(); } } SECTION("make sure migration functions work as intended") { auto small = std::make_shared
(Table::minLogSize); auto large = std::make_shared
(Table::minLogSize + 2); auto huge = std::make_shared
(Table::minLogSize + 4); small->enable(); large->enable(); huge->enable(); SECTION("check that setAuxiliary works as intended") { auto res = small->setAuxiliary(large); REQUIRE(res.get() == nullptr); res = small->setAuxiliary(huge); REQUIRE(res.get() == huge.get()); res = small->setAuxiliary(std::shared_ptr
(nullptr)); REQUIRE(res.get() == large.get()); } SECTION("check that bucket locking falls through appropriately") { auto res = small->setAuxiliary(large); REQUIRE(res.get() == nullptr); uint32_t indexSmall = 17; // picked something at "random" uint32_t indexLarge = indexSmall << 2; uint32_t hash = indexSmall << (32 - small->logSize()); auto pair = small->fetchAndLockBucket(hash, -1); auto bucket = reinterpret_cast(pair.first); auto source = pair.second; REQUIRE(bucket == reinterpret_cast(small->primaryBucket(indexSmall))); bucket->_state.toggleFlag(BucketState::Flag::migrated); bucket->unlock(); REQUIRE(source == small.get()); pair = small->fetchAndLockBucket(hash, -1); bucket = reinterpret_cast(pair.first); source = pair.second; REQUIRE(bucket == reinterpret_cast(large->primaryBucket(indexLarge))); REQUIRE(source == large.get()); pair = small->fetchAndLockBucket(hash, 10); REQUIRE(pair.first == nullptr); REQUIRE(pair.second == nullptr); bucket->unlock(); } SECTION("check subtable fetching for moving to a smaller table") { auto res = large->setAuxiliary(small); REQUIRE(res.get() == nullptr); uint32_t indexLarge = 822; // picked something at "random" uint32_t indexSmall = indexLarge >> 2; uint32_t hash = indexLarge << (32 - large->logSize()); auto subtable = large->auxiliaryBuckets(indexLarge); REQUIRE(subtable.get() != nullptr); auto bucket = subtable->fetchBucket(hash); REQUIRE(bucket == small->primaryBucket(indexSmall)); } SECTION("check subtable fetching for moving to a larger table") { auto res = small->setAuxiliary(large); REQUIRE(res.get() == nullptr); uint32_t indexSmall = 217; // picked something at "random" uint32_t indexLargeBase = indexSmall << 2; auto subtable = small->auxiliaryBuckets(indexSmall); REQUIRE(subtable.get() != nullptr); for (uint32_t i = 0; i < 4; i++) { uint32_t indexLarge = indexLargeBase + i; uint32_t hash = indexLarge << (32 - large->logSize()); REQUIRE(subtable->fetchBucket(hash) == large->primaryBucket(indexLarge)); } } SECTION("check subtable apply all works") { auto res = small->setAuxiliary(large); REQUIRE(res.get() == nullptr); uint32_t indexSmall = 172; // picked something at "random" uint32_t indexLargeBase = indexSmall << 2; auto subtable = small->auxiliaryBuckets(indexSmall); REQUIRE(subtable.get() != nullptr); subtable->applyToAllBuckets([](void* ptr) -> bool { PlainBucket* bucket = reinterpret_cast(ptr); return bucket->lock(-1); }); for (uint32_t i = 0; i < 4; i++) { uint32_t indexLarge = indexLargeBase + i; uint32_t hash = indexLarge << (32 - large->logSize()); auto bucket = reinterpret_cast(subtable->fetchBucket(hash)); REQUIRE(bucket->isLocked()); } subtable->applyToAllBuckets([](void* ptr) -> bool { PlainBucket* bucket = reinterpret_cast(ptr); bucket->unlock(); return true; }); } SECTION("test fill ratio methods") { for (uint64_t i = 0; i < large->size(); i++) { bool res = large->slotFilled(); if (static_cast(i + 1) < 0.04 * static_cast(large->size())) { REQUIRE(large->idealSize() == large->logSize() - 1); REQUIRE(res == false); } else if (static_cast(i + 1) > 0.25 * static_cast(large->size())) { REQUIRE(large->idealSize() == large->logSize() + 1); REQUIRE(res == true); } else { REQUIRE(large->idealSize() == large->logSize()); REQUIRE(res == false); } } for (uint64_t i = large->size(); i > 0; i--) { bool res = large->slotEmptied(); if (static_cast(i - 1) < 0.04 * static_cast(large->size())) { REQUIRE(large->idealSize() == large->logSize() - 1); REQUIRE(res == true); } else if (static_cast(i - 1) > 0.25 * static_cast(large->size())) { REQUIRE(large->idealSize() == large->logSize() + 1); REQUIRE(res == false); } else { REQUIRE(large->idealSize() == large->logSize()); REQUIRE(res == false); } } } } }