blob: 6245240c3b99ef35c8961e59eb15508e0db51dc8 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <atomic>
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "catalog/CatalogConfig.h"
#include "storage/EvictionPolicy.hpp"
#include "storage/StorageBlob.hpp"
#include "storage/StorageBlockInfo.hpp"
#include "storage/StorageConstants.hpp"
#include "storage/StorageManager.hpp"
#include "utility/ShardedLockManager.hpp"
#ifdef QUICKSTEP_HAVE_LIBNUMA
#include <numa.h>
#endif
namespace quickstep {
TEST(StorageManagerTest, NUMAAgnosticBlobTest) {
std::unique_ptr<StorageManager> storage_manager;
static constexpr std::size_t kNumSlots = 10;
storage_manager.reset(new StorageManager("temp_storage"));
// Create kNumSlots * kSlotSizeBytes of memory.
block_id blob_id = storage_manager->createBlob(kNumSlots);
MutableBlobReference blob_obj = storage_manager->getBlobMutable(blob_id);
char *blob_memory = static_cast<char*>(blob_obj->getMemoryMutable());
// Write some contents into the blob's memory.
for (std::size_t i = 0; i < kNumSlots * kSlotSizeBytes; ++i) {
blob_memory[i] = static_cast<char>(i);
}
// Dereference the blob.
blob_obj.release();
BlobReference new_blob_obj = storage_manager->getBlob(blob_id);
const char *new_blob_memory = static_cast<const char*>(new_blob_obj->getMemory());
// Read the contents of the blob and verify if they
// match with what was written previously.
for (std::size_t i = 0; i < kNumSlots * kSlotSizeBytes; ++i) {
EXPECT_EQ(static_cast<char>(i), new_blob_memory[i]);
}
}
#ifdef QUICKSTEP_HAVE_LIBNUMA
TEST(StorageManagerTest, NUMAAwareBlobTest) {
std::unique_ptr<StorageManager> storage_manager;
static constexpr std::size_t kNumSlots = 10;
storage_manager.reset(new StorageManager("temp_storage"));
const std::size_t num_numa_nodes = numa_num_configured_nodes();
block_id blob_id;
MutableBlobReference blob_obj;
char* blob_memory;
BlobReference new_blob_obj;
const char* new_blob_memory;
for (std::size_t numa_node = 0; numa_node < num_numa_nodes; ++numa_node) {
blob_id = storage_manager->createBlob(kNumSlots, numa_node);
blob_obj =
storage_manager->getBlobMutable(blob_id, numa_node);
blob_memory =
static_cast<char*>(blob_obj->getMemoryMutable());
// Write some contents into the memory.
for (std::size_t i = 0; i < kNumSlots * kSlotSizeBytes; ++i) {
blob_memory[i] = static_cast<char>(i);
}
// Dereference the blob.
blob_obj.release();
new_blob_obj =
storage_manager->getBlob(blob_id, numa_node);
new_blob_memory =
static_cast<const char*>(new_blob_obj->getMemory());
// Read the contents of the blob on the same NUMA node on which the blob was
// created and verify if they match with what we wrote into the blob.
for (std::size_t i = 0; i < kNumSlots * kSlotSizeBytes; ++i) {
EXPECT_EQ(static_cast<char>(i), new_blob_memory[i]);
}
}
}
TEST(StorageManagerTest, DifferentNUMANodeBlobTest) {
std::unique_ptr<StorageManager> storage_manager;
static constexpr std::size_t kNumSlots = 10;
storage_manager.reset(new StorageManager("temp_storage"));
const std::size_t num_numa_nodes = numa_num_configured_nodes();
block_id blob_id;
MutableBlobReference blob_obj;
char* blob_memory;
BlobReference new_blob_obj;
const char* new_blob_memory;
std::size_t new_numa_node = 0;
for (std::size_t numa_node = 0; numa_node < num_numa_nodes; ++numa_node) {
blob_id = storage_manager->createBlob(kNumSlots, numa_node);
blob_obj =
storage_manager->getBlobMutable(blob_id, numa_node);
blob_memory =
static_cast<char*>(blob_obj->getMemoryMutable());
// Write some contents into the memory.
for (std::size_t i = 0; i < kNumSlots * kSlotSizeBytes; ++i) {
blob_memory[i] = static_cast<char>(i);
}
// Dereference the blob.
blob_obj.release();
new_numa_node = (numa_node + 1) % num_numa_nodes;
new_blob_obj =
storage_manager->getBlob(blob_id, new_numa_node);
new_blob_memory =
static_cast<const char*>(new_blob_obj->getMemory());
// Read the contents of the blob by giving a different NUMA node hint and
// verify if we still read the same blob that we actually wrote to.
for (std::size_t i = 0; i < kNumSlots * kSlotSizeBytes; ++i) {
EXPECT_EQ(static_cast<char>(i), new_blob_memory[i]);
}
}
}
TEST(StorageManagerTest, DifferentNUMANodeBlobTestWithEviction) {
EvictionPolicy *eviction_policy = LRUKEvictionPolicyFactory::ConstructLRUKEvictionPolicy(
2, std::chrono::seconds(100));
EvictionPolicy::Status status;
static constexpr std::size_t kNumSlots = 10;
const block_id_domain block_domain = 1000;
// Set the max_memory_usage to 4 GB.
const size_t max_memory_usage = 2000;
std::unique_ptr<StorageManager> storage_manager;
storage_manager.reset(
new StorageManager("temp_storage", block_domain, max_memory_usage, eviction_policy));
const std::size_t num_numa_nodes = numa_num_configured_nodes();
block_id blob_id;
MutableBlobReference blob_obj;
char* blob_memory;
BlobReference new_blob_obj;
const char* new_blob_memory;
std::size_t new_numa_node = 0;
for (std::size_t numa_node = 0; numa_node < num_numa_nodes; ++numa_node) {
blob_id = storage_manager->createBlob(kNumSlots, numa_node);
blob_obj =
storage_manager->getBlobMutable(blob_id, numa_node);
blob_memory =
static_cast<char*>(blob_obj->getMemoryMutable());
// Write some contents into the memory.
for (std::size_t i = 0; i < kNumSlots * kSlotSizeBytes; ++i) {
blob_memory[i] = static_cast<char>(i);
}
// Dereference the blob.
blob_obj.release();
// Choose a blob for eviction.
status = storage_manager->eviction_policy_->chooseBlockToEvict(&blob_id);
ASSERT_EQ(EvictionPolicy::Status::kOk, status);
// Save the blob to disk.
storage_manager->saveBlockOrBlob(blob_id, true);
// Evict the blob from the buffer pool.
storage_manager->evictBlockOrBlob(blob_id);
// Inform the eviction policy that this blob has been evicted.
storage_manager->eviction_policy_->blockEvicted(blob_id);
new_numa_node = (numa_node + 1) % num_numa_nodes;
new_blob_obj =
storage_manager->getBlob(blob_id, new_numa_node);
new_blob_memory =
static_cast<const char*>(new_blob_obj->getMemory());
// Read the contents of the blob by giving a different NUMA node hint and
// verify if we still read the same blob that we actually wrote to.
for (std::size_t i = 0; i < kNumSlots * kSlotSizeBytes; ++i) {
EXPECT_EQ(static_cast<char>(i), new_blob_memory[i]);
}
}
}
#endif // QUICKSTEP_HAVE_LIBNUMA
// Trigger an eviction from the same shard in StorageManager's
// ShardedLockManager while attempting to load a blob. Previously, a bug
// existed that caused a self-deadlock in such situations. This test reproduces
// the issue and validates the fix.
TEST(StorageManagerTest, EvictFromSameShardTest) {
// Set up a StorageManager with a soft memory limit of only one slot.
StorageManager storage_manager("eviction_test_storage", 1);
// Create a blob.
const block_id blob_a_id = storage_manager.createBlob(1);
// Blob "a" is now memory-resident in StorageManager, but has a reference
// count of zero.
EXPECT_TRUE(storage_manager.blockOrBlobIsLoaded(blob_a_id));
EXPECT_EQ(kSlotSizeBytes, storage_manager.getMemorySize());
// Manually alter 'block_index_' inside 'storage_manager' so that the next
// block_id generated will be in the same shard as 'blob_id_a'.
storage_manager.block_index_.fetch_add(StorageManager::kLockManagerNumShards - 1);
// Create another blob and verify that it is in the same shard.
const block_id blob_b_id = storage_manager.createBlob(1);
EXPECT_EQ(storage_manager.lock_manager_.get(blob_a_id),
storage_manager.lock_manager_.get(blob_b_id));
// Creating a second blob should have triggered an eviction that kicked
// blob A out.
EXPECT_FALSE(storage_manager.blockOrBlobIsLoaded(blob_a_id));
EXPECT_TRUE(storage_manager.blockOrBlobIsLoaded(blob_b_id));
EXPECT_EQ(kSlotSizeBytes, storage_manager.getMemorySize());
// Try and get a reference to blob A. Blob A must be reloaded from disk.
// This will trigger an eviction of blob B. This is the point where the
// self-deadlock bug could be observed.
BlobReference blob_a_ref = storage_manager.getBlob(blob_a_id);
// Reaching this point means we have not self-deadlocked. Now clean up.
blob_a_ref.release();
storage_manager.deleteBlockOrBlobFile(blob_a_id);
storage_manager.deleteBlockOrBlobFile(blob_b_id);
}
} // namespace quickstep