зеркало из https://github.com/microsoft/L4.git
Apply clang-format (Chromium) (#13)
This commit is contained in:
Родитель
32a7737afe
Коммит
64e70ac102
|
@ -67,3 +67,5 @@ ipch/
|
||||||
*.vsp
|
*.vsp
|
||||||
*.vspx
|
*.vspx
|
||||||
*.sap
|
*.sap
|
||||||
|
*.htm
|
||||||
|
*.user
|
||||||
|
|
|
@ -2,91 +2,73 @@
|
||||||
#include "L4/Log/PerfCounter.h"
|
#include "L4/Log/PerfCounter.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <boost/any.hpp>
|
||||||
|
#include <boost/program_options.hpp>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <boost/any.hpp>
|
|
||||||
#include <boost/program_options.hpp>
|
|
||||||
|
|
||||||
class Timer
|
class Timer {
|
||||||
{
|
public:
|
||||||
public:
|
Timer() : m_start{std::chrono::high_resolution_clock::now()} {}
|
||||||
Timer()
|
|
||||||
: m_start{ std::chrono::high_resolution_clock::now() }
|
|
||||||
{}
|
|
||||||
|
|
||||||
void Reset()
|
void Reset() { m_start = std::chrono::high_resolution_clock::now(); }
|
||||||
{
|
|
||||||
m_start = std::chrono::high_resolution_clock::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::chrono::microseconds GetElapsedTime()
|
std::chrono::microseconds GetElapsedTime() {
|
||||||
{
|
|
||||||
return std::chrono::duration_cast<std::chrono::microseconds>(
|
return std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
std::chrono::high_resolution_clock::now() - m_start);
|
std::chrono::high_resolution_clock::now() - m_start);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_start;
|
std::chrono::time_point<std::chrono::steady_clock> m_start;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SynchronizedTimer {
|
||||||
class SynchronizedTimer
|
public:
|
||||||
{
|
|
||||||
public:
|
|
||||||
SynchronizedTimer() = default;
|
SynchronizedTimer() = default;
|
||||||
|
|
||||||
void Start()
|
void Start() {
|
||||||
{
|
if (m_isStarted) {
|
||||||
if (m_isStarted)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_isStarted = true;
|
m_isStarted = true;
|
||||||
m_startCount = std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
m_startCount =
|
||||||
|
std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
||||||
}
|
}
|
||||||
|
|
||||||
void End()
|
void End() {
|
||||||
{
|
m_endCount =
|
||||||
m_endCount = std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::microseconds GetElapsedTime()
|
std::chrono::microseconds GetElapsedTime() {
|
||||||
{
|
std::chrono::nanoseconds start{m_startCount};
|
||||||
std::chrono::nanoseconds start{ m_startCount };
|
std::chrono::nanoseconds end{m_endCount};
|
||||||
std::chrono::nanoseconds end{ m_endCount };
|
|
||||||
|
|
||||||
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::atomic_bool m_isStarted = false;
|
std::atomic_bool m_isStarted = false;
|
||||||
std::atomic_uint64_t m_startCount;
|
std::atomic_uint64_t m_startCount;
|
||||||
std::atomic_uint64_t m_endCount;
|
std::atomic_uint64_t m_endCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PerThreadInfoForWriteTest {
|
||||||
struct PerThreadInfoForWriteTest
|
|
||||||
{
|
|
||||||
std::thread m_thread;
|
std::thread m_thread;
|
||||||
std::size_t m_dataSetSize = 0;
|
std::size_t m_dataSetSize = 0;
|
||||||
std::chrono::microseconds m_totalTime;
|
std::chrono::microseconds m_totalTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PerThreadInfoForReadTest {
|
||||||
struct PerThreadInfoForReadTest
|
|
||||||
{
|
|
||||||
std::thread m_thread;
|
std::thread m_thread;
|
||||||
std::size_t m_dataSetSize = 0;
|
std::size_t m_dataSetSize = 0;
|
||||||
std::chrono::microseconds m_totalTime;
|
std::chrono::microseconds m_totalTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CommandLineOptions {
|
||||||
struct CommandLineOptions
|
|
||||||
{
|
|
||||||
static constexpr std::size_t c_defaultDataSetSize = 1000000;
|
static constexpr std::size_t c_defaultDataSetSize = 1000000;
|
||||||
static constexpr std::uint32_t c_defaultNumBuckets = 1000000;
|
static constexpr std::uint32_t c_defaultNumBuckets = 1000000;
|
||||||
static constexpr std::uint16_t c_defaultKeySize = 16;
|
static constexpr std::uint16_t c_defaultKeySize = 16;
|
||||||
|
@ -116,29 +98,24 @@ struct CommandLineOptions
|
||||||
std::uint64_t m_cacheSizeInBytes = 0U;
|
std::uint64_t m_cacheSizeInBytes = 0U;
|
||||||
bool m_forceTimeBasedEviction = false;
|
bool m_forceTimeBasedEviction = false;
|
||||||
|
|
||||||
bool IsCachingModule() const
|
bool IsCachingModule() const {
|
||||||
{
|
static const std::string c_cachingModulePrefix{"cache"};
|
||||||
static const std::string c_cachingModulePrefix{ "cache" };
|
return m_module.substr(0, c_cachingModulePrefix.size()) ==
|
||||||
return m_module.substr(0, c_cachingModulePrefix.size()) == c_cachingModulePrefix;
|
c_cachingModulePrefix;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DataGenerator {
|
||||||
class DataGenerator
|
public:
|
||||||
{
|
DataGenerator(std::size_t dataSetSize,
|
||||||
public:
|
|
||||||
DataGenerator(
|
|
||||||
std::size_t dataSetSize,
|
|
||||||
std::uint16_t keySize,
|
std::uint16_t keySize,
|
||||||
std::uint32_t valueSize,
|
std::uint32_t valueSize,
|
||||||
bool randomizeValueSize,
|
bool randomizeValueSize,
|
||||||
bool isDebugMode = false)
|
bool isDebugMode = false)
|
||||||
: m_dataSetSize{ dataSetSize }
|
: m_dataSetSize{dataSetSize}, m_keySize{keySize} {
|
||||||
, m_keySize{ keySize }
|
if (isDebugMode) {
|
||||||
{
|
std::cout << "Generating data set with size = " << dataSetSize
|
||||||
if (isDebugMode)
|
<< std::endl;
|
||||||
{
|
|
||||||
std::cout << "Generating data set with size = " << dataSetSize << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer timer;
|
Timer timer;
|
||||||
|
@ -146,11 +123,11 @@ public:
|
||||||
// Populate keys.
|
// Populate keys.
|
||||||
m_keys.resize(m_dataSetSize);
|
m_keys.resize(m_dataSetSize);
|
||||||
m_keysBuffer.resize(m_dataSetSize);
|
m_keysBuffer.resize(m_dataSetSize);
|
||||||
for (std::size_t i = 0; i < m_dataSetSize; ++i)
|
for (std::size_t i = 0; i < m_dataSetSize; ++i) {
|
||||||
{
|
|
||||||
m_keysBuffer[i].resize(keySize);
|
m_keysBuffer[i].resize(keySize);
|
||||||
std::generate(m_keysBuffer[i].begin(), m_keysBuffer[i].end(), std::rand);
|
std::generate(m_keysBuffer[i].begin(), m_keysBuffer[i].end(), std::rand);
|
||||||
std::snprintf(reinterpret_cast<char*>(m_keysBuffer[i].data()), keySize, "%llu", i);
|
std::snprintf(reinterpret_cast<char*>(m_keysBuffer[i].data()), keySize,
|
||||||
|
"%llu", i);
|
||||||
m_keys[i].m_data = m_keysBuffer[i].data();
|
m_keys[i].m_data = m_keysBuffer[i].data();
|
||||||
m_keys[i].m_size = m_keySize;
|
m_keys[i].m_size = m_keySize;
|
||||||
}
|
}
|
||||||
|
@ -161,31 +138,28 @@ public:
|
||||||
// Populate values.
|
// Populate values.
|
||||||
m_values.resize(m_dataSetSize);
|
m_values.resize(m_dataSetSize);
|
||||||
std::size_t currentIndex = 0;
|
std::size_t currentIndex = 0;
|
||||||
for (std::size_t i = 0; i < m_dataSetSize; ++i)
|
for (std::size_t i = 0; i < m_dataSetSize; ++i) {
|
||||||
{
|
|
||||||
m_values[i].m_data = &m_valuesBuffer[currentIndex % c_valuesBufferSize];
|
m_values[i].m_data = &m_valuesBuffer[currentIndex % c_valuesBufferSize];
|
||||||
m_values[i].m_size = randomizeValueSize ? rand() % valueSize : valueSize;
|
m_values[i].m_size = randomizeValueSize ? rand() % valueSize : valueSize;
|
||||||
currentIndex += valueSize;
|
currentIndex += valueSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDebugMode)
|
if (isDebugMode) {
|
||||||
{
|
|
||||||
std::cout << "Finished generating data in "
|
std::cout << "Finished generating data in "
|
||||||
<< timer.GetElapsedTime().count() << " microseconds" << std::endl;
|
<< timer.GetElapsedTime().count() << " microseconds"
|
||||||
|
<< std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
L4::IReadOnlyHashTable::Key GetKey(std::size_t index) const
|
L4::IReadOnlyHashTable::Key GetKey(std::size_t index) const {
|
||||||
{
|
|
||||||
return m_keys[index % m_dataSetSize];
|
return m_keys[index % m_dataSetSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
L4::IReadOnlyHashTable::Value GetValue(std::size_t index) const
|
L4::IReadOnlyHashTable::Value GetValue(std::size_t index) const {
|
||||||
{
|
|
||||||
return m_values[index % m_dataSetSize];
|
return m_values[index % m_dataSetSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::size_t m_dataSetSize;
|
std::size_t m_dataSetSize;
|
||||||
std::uint16_t m_keySize;
|
std::uint16_t m_keySize;
|
||||||
|
|
||||||
|
@ -197,9 +171,7 @@ private:
|
||||||
std::array<std::uint8_t, c_valuesBufferSize> m_valuesBuffer;
|
std::array<std::uint8_t, c_valuesBufferSize> m_valuesBuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void PrintHardwareInfo() {
|
||||||
void PrintHardwareInfo()
|
|
||||||
{
|
|
||||||
SYSTEM_INFO sysInfo;
|
SYSTEM_INFO sysInfo;
|
||||||
GetSystemInfo(&sysInfo);
|
GetSystemInfo(&sysInfo);
|
||||||
|
|
||||||
|
@ -207,108 +179,100 @@ void PrintHardwareInfo()
|
||||||
printf("Hardware information: \n");
|
printf("Hardware information: \n");
|
||||||
printf("-------------------------------------\n");
|
printf("-------------------------------------\n");
|
||||||
printf("%22s | %10u |\n", "OEM ID", sysInfo.dwOemId);
|
printf("%22s | %10u |\n", "OEM ID", sysInfo.dwOemId);
|
||||||
printf("%22s | %10u |\n", "Number of processors", sysInfo.dwNumberOfProcessors);
|
printf("%22s | %10u |\n", "Number of processors",
|
||||||
|
sysInfo.dwNumberOfProcessors);
|
||||||
printf("%22s | %10u |\n", "Page size", sysInfo.dwPageSize);
|
printf("%22s | %10u |\n", "Page size", sysInfo.dwPageSize);
|
||||||
printf("%22s | %10u |\n", "Processor type", sysInfo.dwProcessorType);
|
printf("%22s | %10u |\n", "Processor type", sysInfo.dwProcessorType);
|
||||||
printf("-------------------------------------\n");
|
printf("-------------------------------------\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrintOptions(const CommandLineOptions& options) {
|
||||||
void PrintOptions(const CommandLineOptions& options)
|
|
||||||
{
|
|
||||||
printf("------------------------------------------------------\n");
|
printf("------------------------------------------------------\n");
|
||||||
|
|
||||||
printf("%39s | %10llu |\n", "Data set size", options.m_dataSetSize);
|
printf("%39s | %10llu |\n", "Data set size", options.m_dataSetSize);
|
||||||
printf("%39s | %10lu |\n", "Number of hash table buckets", options.m_numBuckets);
|
printf("%39s | %10lu |\n", "Number of hash table buckets",
|
||||||
|
options.m_numBuckets);
|
||||||
printf("%39s | %10lu |\n", "Key size", options.m_keySize);
|
printf("%39s | %10lu |\n", "Key size", options.m_keySize);
|
||||||
printf("%39s | %10lu |\n", "Value type", options.m_valueSize);
|
printf("%39s | %10lu |\n", "Value type", options.m_valueSize);
|
||||||
printf("%39s | %10lu |\n", "Number of iterations per GetContext()", options.m_numIterationsPerGetContext);
|
printf("%39s | %10lu |\n", "Number of iterations per GetContext()",
|
||||||
printf("%39s | %10lu |\n", "Epoch processing interval (ms)", options.m_epochProcessingIntervalInMilli);
|
options.m_numIterationsPerGetContext);
|
||||||
printf("%39s | %10lu |\n", "Number of actions queue", options.m_numActionsQueue);
|
printf("%39s | %10lu |\n", "Epoch processing interval (ms)",
|
||||||
|
options.m_epochProcessingIntervalInMilli);
|
||||||
|
printf("%39s | %10lu |\n", "Number of actions queue",
|
||||||
|
options.m_numActionsQueue);
|
||||||
|
|
||||||
if (options.IsCachingModule())
|
if (options.IsCachingModule()) {
|
||||||
{
|
printf("%39s | %10lu |\n", "Record time to live (s)",
|
||||||
printf("%39s | %10lu |\n", "Record time to live (s)", options.m_recordTimeToLiveInSeconds);
|
options.m_recordTimeToLiveInSeconds);
|
||||||
printf("%39s | %10llu |\n", "Cache size in bytes", options.m_cacheSizeInBytes);
|
printf("%39s | %10llu |\n", "Cache size in bytes",
|
||||||
printf("%39s | %10lu |\n", "Force time-based eviction", options.m_forceTimeBasedEviction);
|
options.m_cacheSizeInBytes);
|
||||||
|
printf("%39s | %10lu |\n", "Force time-based eviction",
|
||||||
|
options.m_forceTimeBasedEviction);
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("------------------------------------------------------\n\n");
|
printf("------------------------------------------------------\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrintHashTableCounters(const L4::HashTablePerfData& perfData) {
|
||||||
void PrintHashTableCounters(const L4::HashTablePerfData& perfData)
|
|
||||||
{
|
|
||||||
printf("HashTableCounter:\n");
|
printf("HashTableCounter:\n");
|
||||||
printf("----------------------------------------------------\n");
|
printf("----------------------------------------------------\n");
|
||||||
for (auto i = 0; i < static_cast<std::uint16_t>(L4::HashTablePerfCounter::Count); ++i)
|
for (auto i = 0;
|
||||||
{
|
i < static_cast<std::uint16_t>(L4::HashTablePerfCounter::Count); ++i) {
|
||||||
printf("%35s | %12llu |\n",
|
printf("%35s | %12llu |\n", L4::c_hashTablePerfCounterNames[i],
|
||||||
L4::c_hashTablePerfCounterNames[i],
|
|
||||||
perfData.Get(static_cast<L4::HashTablePerfCounter>(i)));
|
perfData.Get(static_cast<L4::HashTablePerfCounter>(i)));
|
||||||
}
|
}
|
||||||
printf("----------------------------------------------------\n\n");
|
printf("----------------------------------------------------\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
L4::HashTableConfig CreateHashTableConfig(const CommandLineOptions& options) {
|
||||||
L4::HashTableConfig CreateHashTableConfig(const CommandLineOptions& options)
|
|
||||||
{
|
|
||||||
return L4::HashTableConfig(
|
return L4::HashTableConfig(
|
||||||
"Table1",
|
"Table1", L4::HashTableConfig::Setting{options.m_numBuckets},
|
||||||
L4::HashTableConfig::Setting{ options.m_numBuckets },
|
|
||||||
options.IsCachingModule()
|
options.IsCachingModule()
|
||||||
? boost::optional<L4::HashTableConfig::Cache>{
|
? boost::optional<
|
||||||
L4::HashTableConfig::Cache{
|
L4::HashTableConfig::Cache>{L4::HashTableConfig::Cache{
|
||||||
options.m_cacheSizeInBytes,
|
options.m_cacheSizeInBytes,
|
||||||
std::chrono::seconds{ options.m_recordTimeToLiveInSeconds },
|
std::chrono::seconds{options.m_recordTimeToLiveInSeconds},
|
||||||
options.m_forceTimeBasedEviction }}
|
options.m_forceTimeBasedEviction}}
|
||||||
: boost::none);
|
: boost::none);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
L4::EpochManagerConfig CreateEpochManagerConfig(
|
||||||
L4::EpochManagerConfig CreateEpochManagerConfig(const CommandLineOptions& options)
|
const CommandLineOptions& options) {
|
||||||
{
|
|
||||||
return L4::EpochManagerConfig(
|
return L4::EpochManagerConfig(
|
||||||
10000U,
|
10000U,
|
||||||
std::chrono::milliseconds(options.m_epochProcessingIntervalInMilli),
|
std::chrono::milliseconds(options.m_epochProcessingIntervalInMilli),
|
||||||
options.m_numActionsQueue);
|
options.m_numActionsQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ReadPerfTest(const CommandLineOptions& options) {
|
||||||
void ReadPerfTest(const CommandLineOptions& options)
|
|
||||||
{
|
|
||||||
printf("Performing read-perf which reads all the records inserted:\n");
|
printf("Performing read-perf which reads all the records inserted:\n");
|
||||||
|
|
||||||
PrintOptions(options);
|
PrintOptions(options);
|
||||||
|
|
||||||
auto dataGenerator = std::make_unique<DataGenerator>(
|
auto dataGenerator = std::make_unique<DataGenerator>(
|
||||||
options.m_dataSetSize,
|
options.m_dataSetSize, options.m_keySize, options.m_valueSize,
|
||||||
options.m_keySize,
|
|
||||||
options.m_valueSize,
|
|
||||||
options.m_randomizeValueSize);
|
options.m_randomizeValueSize);
|
||||||
|
|
||||||
L4::LocalMemory::HashTableService service(CreateEpochManagerConfig(options));
|
L4::LocalMemory::HashTableService service(CreateEpochManagerConfig(options));
|
||||||
const auto hashTableIndex = service.AddHashTable(CreateHashTableConfig(options));
|
const auto hashTableIndex =
|
||||||
|
service.AddHashTable(CreateHashTableConfig(options));
|
||||||
|
|
||||||
// Insert data set.
|
// Insert data set.
|
||||||
auto context = service.GetContext();
|
auto context = service.GetContext();
|
||||||
auto& hashTable = context[hashTableIndex];
|
auto& hashTable = context[hashTableIndex];
|
||||||
|
|
||||||
std::vector<std::uint32_t> randomIndices(options.m_dataSetSize);
|
std::vector<std::uint32_t> randomIndices(options.m_dataSetSize);
|
||||||
for (std::uint32_t i = 0U; i < options.m_dataSetSize; ++i)
|
for (std::uint32_t i = 0U; i < options.m_dataSetSize; ++i) {
|
||||||
{
|
|
||||||
randomIndices[i] = i;
|
randomIndices[i] = i;
|
||||||
}
|
}
|
||||||
if (options.m_numThreads > 0)
|
if (options.m_numThreads > 0) {
|
||||||
{
|
|
||||||
// Randomize index only if multiple threads are running
|
// Randomize index only if multiple threads are running
|
||||||
// not to skew the results.
|
// not to skew the results.
|
||||||
std::random_shuffle(randomIndices.begin(), randomIndices.end());
|
std::random_shuffle(randomIndices.begin(), randomIndices.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < options.m_dataSetSize; ++i)
|
for (int i = 0; i < options.m_dataSetSize; ++i) {
|
||||||
{
|
|
||||||
auto key = dataGenerator->GetKey(randomIndices[i]);
|
auto key = dataGenerator->GetKey(randomIndices[i]);
|
||||||
auto val = dataGenerator->GetValue(randomIndices[i]);
|
auto val = dataGenerator->GetValue(randomIndices[i]);
|
||||||
|
|
||||||
|
@ -324,9 +288,9 @@ void ReadPerfTest(const CommandLineOptions& options)
|
||||||
const auto isCachingModule = options.IsCachingModule();
|
const auto isCachingModule = options.IsCachingModule();
|
||||||
bool isReady = false;
|
bool isReady = false;
|
||||||
|
|
||||||
const std::size_t dataSetSizePerThread = options.m_dataSetSize / options.m_numThreads;
|
const std::size_t dataSetSizePerThread =
|
||||||
for (std::uint16_t i = 0; i < options.m_numThreads; ++i)
|
options.m_dataSetSize / options.m_numThreads;
|
||||||
{
|
for (std::uint16_t i = 0; i < options.m_numThreads; ++i) {
|
||||||
auto& info = allInfo[i];
|
auto& info = allInfo[i];
|
||||||
|
|
||||||
std::size_t startIndex = i * dataSetSizePerThread;
|
std::size_t startIndex = i * dataSetSizePerThread;
|
||||||
|
@ -334,8 +298,8 @@ void ReadPerfTest(const CommandLineOptions& options)
|
||||||
? options.m_dataSetSize - startIndex
|
? options.m_dataSetSize - startIndex
|
||||||
: dataSetSizePerThread;
|
: dataSetSizePerThread;
|
||||||
|
|
||||||
info.m_thread = std::thread([=, &service, &dataGenerator, &info, &mutex, &cv, &isReady, &overallTimer]
|
info.m_thread = std::thread([=, &service, &dataGenerator, &info, &mutex,
|
||||||
{
|
&cv, &isReady, &overallTimer] {
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mutex);
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
cv.wait(lock, [&] { return isReady == true; });
|
cv.wait(lock, [&] { return isReady == true; });
|
||||||
|
@ -349,19 +313,18 @@ void ReadPerfTest(const CommandLineOptions& options)
|
||||||
std::size_t iteration = 0;
|
std::size_t iteration = 0;
|
||||||
bool isDone = false;
|
bool isDone = false;
|
||||||
|
|
||||||
while (!isDone)
|
while (!isDone) {
|
||||||
{
|
|
||||||
auto context = service.GetContext();
|
auto context = service.GetContext();
|
||||||
auto& hashTable = context[hashTableIndex];
|
auto& hashTable = context[hashTableIndex];
|
||||||
|
|
||||||
for (std::uint32_t j = 0; !isDone && j < options.m_numIterationsPerGetContext; ++j)
|
for (std::uint32_t j = 0;
|
||||||
{
|
!isDone && j < options.m_numIterationsPerGetContext; ++j) {
|
||||||
auto key = dataGenerator->GetKey(startIndex + iteration);
|
auto key = dataGenerator->GetKey(startIndex + iteration);
|
||||||
L4::IReadOnlyHashTable::Value val;
|
L4::IReadOnlyHashTable::Value val;
|
||||||
|
|
||||||
if (!hashTable.Get(key, val) && !isCachingModule)
|
if (!hashTable.Get(key, val) && !isCachingModule) {
|
||||||
{
|
throw std::runtime_error(
|
||||||
throw std::runtime_error("Look up failure is not allowed in this test.");
|
"Look up failure is not allowed in this test.");
|
||||||
}
|
}
|
||||||
|
|
||||||
isDone = (++iteration == info.m_dataSetSize);
|
isDone = (++iteration == info.m_dataSetSize);
|
||||||
|
@ -382,8 +345,7 @@ void ReadPerfTest(const CommandLineOptions& options)
|
||||||
// Now, start the benchmarking for all threads.
|
// Now, start the benchmarking for all threads.
|
||||||
cv.notify_all();
|
cv.notify_all();
|
||||||
|
|
||||||
for (auto& info : allInfo)
|
for (auto& info : allInfo) {
|
||||||
{
|
|
||||||
info.m_thread.join();
|
info.m_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,56 +356,46 @@ void ReadPerfTest(const CommandLineOptions& options)
|
||||||
printf(" | micros/op | microseconds | DataSetSize |\n");
|
printf(" | micros/op | microseconds | DataSetSize |\n");
|
||||||
printf(" -----------------------------------------------------------\n");
|
printf(" -----------------------------------------------------------\n");
|
||||||
|
|
||||||
for (std::size_t i = 0; i < allInfo.size(); ++i)
|
for (std::size_t i = 0; i < allInfo.size(); ++i) {
|
||||||
{
|
|
||||||
const auto& info = allInfo[i];
|
const auto& info = allInfo[i];
|
||||||
|
|
||||||
printf(" Thread #%llu | %11.3f | %14llu | %13llu |\n",
|
printf(" Thread #%llu | %11.3f | %14llu | %13llu |\n", (i + 1),
|
||||||
(i + 1),
|
|
||||||
static_cast<double>(info.m_totalTime.count()) / info.m_dataSetSize,
|
static_cast<double>(info.m_totalTime.count()) / info.m_dataSetSize,
|
||||||
info.m_totalTime.count(),
|
info.m_totalTime.count(), info.m_dataSetSize);
|
||||||
info.m_dataSetSize);
|
|
||||||
}
|
}
|
||||||
printf(" -----------------------------------------------------------\n");
|
printf(" -----------------------------------------------------------\n");
|
||||||
|
|
||||||
printf(" Overall | %11.3f | %14llu | %13llu |\n",
|
printf(" Overall | %11.3f | %14llu | %13llu |\n",
|
||||||
static_cast<double>(overallTimer.GetElapsedTime().count()) / options.m_dataSetSize,
|
static_cast<double>(overallTimer.GetElapsedTime().count()) /
|
||||||
overallTimer.GetElapsedTime().count(),
|
options.m_dataSetSize,
|
||||||
options.m_dataSetSize);
|
overallTimer.GetElapsedTime().count(), options.m_dataSetSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WritePerfTest(const CommandLineOptions& options) {
|
||||||
void WritePerfTest(const CommandLineOptions& options)
|
if (options.m_module == "overwrite-perf") {
|
||||||
{
|
printf(
|
||||||
if (options.m_module == "overwrite-perf")
|
"Performing overwrite-perf (writing data with unique keys, then "
|
||||||
{
|
"overwrite data with same keys):\n");
|
||||||
printf("Performing overwrite-perf (writing data with unique keys, then overwrite data with same keys):\n");
|
} else {
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
printf("Performing write-perf (writing data with unique keys):\n");
|
printf("Performing write-perf (writing data with unique keys):\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintOptions(options);
|
PrintOptions(options);
|
||||||
|
|
||||||
auto dataGenerator = std::make_unique<DataGenerator>(
|
auto dataGenerator = std::make_unique<DataGenerator>(
|
||||||
options.m_dataSetSize,
|
options.m_dataSetSize, options.m_keySize, options.m_valueSize,
|
||||||
options.m_keySize,
|
|
||||||
options.m_valueSize,
|
|
||||||
options.m_randomizeValueSize);
|
options.m_randomizeValueSize);
|
||||||
|
|
||||||
L4::LocalMemory::HashTableService service(CreateEpochManagerConfig(options));
|
L4::LocalMemory::HashTableService service(CreateEpochManagerConfig(options));
|
||||||
const auto hashTableIndex = service.AddHashTable(CreateHashTableConfig(options));
|
const auto hashTableIndex =
|
||||||
|
service.AddHashTable(CreateHashTableConfig(options));
|
||||||
|
|
||||||
if (options.m_module == "overwrite-perf")
|
if (options.m_module == "overwrite-perf") {
|
||||||
{
|
|
||||||
std::vector<std::uint32_t> randomIndices(options.m_dataSetSize);
|
std::vector<std::uint32_t> randomIndices(options.m_dataSetSize);
|
||||||
for (std::uint32_t i = 0U; i < options.m_dataSetSize; ++i)
|
for (std::uint32_t i = 0U; i < options.m_dataSetSize; ++i) {
|
||||||
{
|
|
||||||
randomIndices[i] = i;
|
randomIndices[i] = i;
|
||||||
}
|
}
|
||||||
if (options.m_numThreads > 0)
|
if (options.m_numThreads > 0) {
|
||||||
{
|
|
||||||
// Randomize index only if multiple threads are running
|
// Randomize index only if multiple threads are running
|
||||||
// not to skew the results.
|
// not to skew the results.
|
||||||
std::random_shuffle(randomIndices.begin(), randomIndices.end());
|
std::random_shuffle(randomIndices.begin(), randomIndices.end());
|
||||||
|
@ -452,8 +404,7 @@ void WritePerfTest(const CommandLineOptions& options)
|
||||||
auto context = service.GetContext();
|
auto context = service.GetContext();
|
||||||
auto& hashTable = context[hashTableIndex];
|
auto& hashTable = context[hashTableIndex];
|
||||||
|
|
||||||
for (int i = 0; i < options.m_dataSetSize; ++i)
|
for (int i = 0; i < options.m_dataSetSize; ++i) {
|
||||||
{
|
|
||||||
const auto index = randomIndices[i];
|
const auto index = randomIndices[i];
|
||||||
auto key = dataGenerator->GetKey(index);
|
auto key = dataGenerator->GetKey(index);
|
||||||
auto val = dataGenerator->GetValue(index);
|
auto val = dataGenerator->GetValue(index);
|
||||||
|
@ -470,9 +421,9 @@ void WritePerfTest(const CommandLineOptions& options)
|
||||||
std::condition_variable cv;
|
std::condition_variable cv;
|
||||||
bool isReady = false;
|
bool isReady = false;
|
||||||
|
|
||||||
const std::size_t dataSetSizePerThread = options.m_dataSetSize / options.m_numThreads;
|
const std::size_t dataSetSizePerThread =
|
||||||
for (std::uint16_t i = 0; i < options.m_numThreads; ++i)
|
options.m_dataSetSize / options.m_numThreads;
|
||||||
{
|
for (std::uint16_t i = 0; i < options.m_numThreads; ++i) {
|
||||||
auto& info = allInfo[i];
|
auto& info = allInfo[i];
|
||||||
|
|
||||||
std::size_t startIndex = i * dataSetSizePerThread;
|
std::size_t startIndex = i * dataSetSizePerThread;
|
||||||
|
@ -480,8 +431,8 @@ void WritePerfTest(const CommandLineOptions& options)
|
||||||
? options.m_dataSetSize - startIndex
|
? options.m_dataSetSize - startIndex
|
||||||
: dataSetSizePerThread;
|
: dataSetSizePerThread;
|
||||||
|
|
||||||
info.m_thread = std::thread([=, &service, &dataGenerator, &info, &mutex, &cv, &isReady, &overallTimer]
|
info.m_thread = std::thread([=, &service, &dataGenerator, &info, &mutex,
|
||||||
{
|
&cv, &isReady, &overallTimer] {
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mutex);
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
cv.wait(lock, [&] { return isReady == true; });
|
cv.wait(lock, [&] { return isReady == true; });
|
||||||
|
@ -495,13 +446,12 @@ void WritePerfTest(const CommandLineOptions& options)
|
||||||
std::size_t iteration = 0;
|
std::size_t iteration = 0;
|
||||||
bool isDone = false;
|
bool isDone = false;
|
||||||
|
|
||||||
while (!isDone)
|
while (!isDone) {
|
||||||
{
|
|
||||||
auto context = service.GetContext();
|
auto context = service.GetContext();
|
||||||
auto& hashTable = context[hashTableIndex];
|
auto& hashTable = context[hashTableIndex];
|
||||||
|
|
||||||
for (std::uint32_t j = 0; !isDone && j < options.m_numIterationsPerGetContext; ++j)
|
for (std::uint32_t j = 0;
|
||||||
{
|
!isDone && j < options.m_numIterationsPerGetContext; ++j) {
|
||||||
const auto index = startIndex + iteration;
|
const auto index = startIndex + iteration;
|
||||||
auto key = dataGenerator->GetKey(index);
|
auto key = dataGenerator->GetKey(index);
|
||||||
auto val = dataGenerator->GetValue(index);
|
auto val = dataGenerator->GetValue(index);
|
||||||
|
@ -525,8 +475,7 @@ void WritePerfTest(const CommandLineOptions& options)
|
||||||
// Now, start the benchmarking for all threads.
|
// Now, start the benchmarking for all threads.
|
||||||
cv.notify_all();
|
cv.notify_all();
|
||||||
|
|
||||||
for (auto& info : allInfo)
|
for (auto& info : allInfo) {
|
||||||
{
|
|
||||||
info.m_thread.join();
|
info.m_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,69 +486,94 @@ void WritePerfTest(const CommandLineOptions& options)
|
||||||
printf(" | micros/op | microseconds | DataSetSize |\n");
|
printf(" | micros/op | microseconds | DataSetSize |\n");
|
||||||
printf(" -----------------------------------------------------------\n");
|
printf(" -----------------------------------------------------------\n");
|
||||||
|
|
||||||
for (std::size_t i = 0; i < allInfo.size(); ++i)
|
for (std::size_t i = 0; i < allInfo.size(); ++i) {
|
||||||
{
|
|
||||||
const auto& info = allInfo[i];
|
const auto& info = allInfo[i];
|
||||||
|
|
||||||
printf(" Thread #%llu | %11.3f | %14llu | %13llu |\n",
|
printf(" Thread #%llu | %11.3f | %14llu | %13llu |\n", (i + 1),
|
||||||
(i + 1),
|
|
||||||
static_cast<double>(info.m_totalTime.count()) / info.m_dataSetSize,
|
static_cast<double>(info.m_totalTime.count()) / info.m_dataSetSize,
|
||||||
info.m_totalTime.count(),
|
info.m_totalTime.count(), info.m_dataSetSize);
|
||||||
info.m_dataSetSize);
|
|
||||||
}
|
}
|
||||||
printf(" -----------------------------------------------------------\n");
|
printf(" -----------------------------------------------------------\n");
|
||||||
|
|
||||||
printf(" Overall | %11.3f | %14llu | %13llu |\n",
|
printf(" Overall | %11.3f | %14llu | %13llu |\n",
|
||||||
static_cast<double>(overallTimer.GetElapsedTime().count()) / options.m_dataSetSize,
|
static_cast<double>(overallTimer.GetElapsedTime().count()) /
|
||||||
overallTimer.GetElapsedTime().count(),
|
options.m_dataSetSize,
|
||||||
options.m_dataSetSize);
|
overallTimer.GetElapsedTime().count(), options.m_dataSetSize);
|
||||||
|
|
||||||
if (options.m_numThreads == 1)
|
if (options.m_numThreads == 1) {
|
||||||
{
|
|
||||||
auto& perfData = service.GetContext()[hashTableIndex].GetPerfData();
|
auto& perfData = service.GetContext()[hashTableIndex].GetPerfData();
|
||||||
std::uint64_t totalBytes = perfData.Get(L4::HashTablePerfCounter::TotalKeySize)
|
std::uint64_t totalBytes =
|
||||||
+ perfData.Get(L4::HashTablePerfCounter::TotalValueSize);
|
perfData.Get(L4::HashTablePerfCounter::TotalKeySize) +
|
||||||
|
perfData.Get(L4::HashTablePerfCounter::TotalValueSize);
|
||||||
|
|
||||||
auto& info = allInfo[0];
|
auto& info = allInfo[0];
|
||||||
|
|
||||||
double opsPerSec = static_cast<double>(info.m_dataSetSize) / info.m_totalTime.count() * 1000000.0;
|
double opsPerSec = static_cast<double>(info.m_dataSetSize) /
|
||||||
double MBPerSec = static_cast<double>(totalBytes) / info.m_totalTime.count();
|
info.m_totalTime.count() * 1000000.0;
|
||||||
|
double MBPerSec =
|
||||||
|
static_cast<double>(totalBytes) / info.m_totalTime.count();
|
||||||
printf(" %10.3f ops/sec %10.3f MB/sec\n", opsPerSec, MBPerSec);
|
printf(" %10.3f ops/sec %10.3f MB/sec\n", opsPerSec, MBPerSec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandLineOptions Parse(int argc, char** argv) {
|
||||||
CommandLineOptions Parse(int argc, char** argv)
|
|
||||||
{
|
|
||||||
namespace po = boost::program_options;
|
namespace po = boost::program_options;
|
||||||
|
|
||||||
po::options_description general("General options");
|
po::options_description general("General options");
|
||||||
general.add_options()
|
general.add_options()("help", "produce a help message")(
|
||||||
("help", "produce a help message")
|
"help-module", po::value<std::string>(),
|
||||||
("help-module", po::value<std::string>(),
|
|
||||||
"produce a help for the following modules:\n"
|
"produce a help for the following modules:\n"
|
||||||
" write-perf\n"
|
" write-perf\n"
|
||||||
" overwrite-perf\n"
|
" overwrite-perf\n"
|
||||||
" read-perf\n"
|
" read-perf\n"
|
||||||
" cache-read-perf\n"
|
" cache-read-perf\n"
|
||||||
" cache-write-perf\n")
|
" cache-write-perf\n")("module", po::value<std::string>(),
|
||||||
("module", po::value<std::string>(),
|
|
||||||
"Runs the given module");
|
"Runs the given module");
|
||||||
|
|
||||||
po::options_description benchmarkOptions("Benchmark options.");
|
po::options_description benchmarkOptions("Benchmark options.");
|
||||||
benchmarkOptions.add_options()
|
benchmarkOptions.add_options()("dataSetSize",
|
||||||
("dataSetSize", po::value<std::size_t>()->default_value(CommandLineOptions::c_defaultDataSetSize), "data set size")
|
po::value<std::size_t>()->default_value(
|
||||||
("numBuckets", po::value<std::uint32_t>()->default_value(CommandLineOptions::c_defaultNumBuckets), "number of buckets")
|
CommandLineOptions::c_defaultDataSetSize),
|
||||||
("keySize", po::value<std::uint16_t>()->default_value(CommandLineOptions::c_defaultKeySize), "key size in bytes")
|
"data set size")(
|
||||||
("valueSize", po::value<std::uint32_t>()->default_value(CommandLineOptions::c_defaultValueSize), "value size in bytes")
|
"numBuckets",
|
||||||
("randomizeValueSize", "randomize value size")
|
po::value<std::uint32_t>()->default_value(
|
||||||
("numIterationsPerGetContext", po::value<std::uint32_t>()->default_value(CommandLineOptions::c_defaultNumIterationsPerGetContext), "number of iterations per GetContext()")
|
CommandLineOptions::c_defaultNumBuckets),
|
||||||
("numThreads", po::value<std::uint16_t>()->default_value(CommandLineOptions::c_defaultNumThreads), "number of threads to create")
|
"number of buckets")("keySize",
|
||||||
("epochProcessingInterval", po::value<std::uint32_t>()->default_value(CommandLineOptions::c_defaultEpochProcessingIntervalInMilli), "epoch processing interval (ms)")
|
po::value<std::uint16_t>()->default_value(
|
||||||
("numActionsQueue", po::value<std::uint8_t>()->default_value(CommandLineOptions::c_defaultNumActionsQueue), "number of actions queue")
|
CommandLineOptions::c_defaultKeySize),
|
||||||
("recordTimeToLive", po::value<std::uint32_t>()->default_value(CommandLineOptions::c_defaultRecordTimeToLiveInSeconds), "record time to live (s)")
|
"key size in bytes")(
|
||||||
("cacheSize", po::value<std::uint64_t>()->default_value(CommandLineOptions::c_defaultCacheSizeInBytes), "cache size in bytes")
|
"valueSize",
|
||||||
("forceTimeBasedEviction", po::value<bool>()->default_value(CommandLineOptions::c_defaultForceTimeBasedEviction), "force time based eviction");
|
po::value<std::uint32_t>()->default_value(
|
||||||
|
CommandLineOptions::c_defaultValueSize),
|
||||||
|
"value size in bytes")("randomizeValueSize", "randomize value size")(
|
||||||
|
"numIterationsPerGetContext",
|
||||||
|
po::value<std::uint32_t>()->default_value(
|
||||||
|
CommandLineOptions::c_defaultNumIterationsPerGetContext),
|
||||||
|
"number of iterations per GetContext()")(
|
||||||
|
"numThreads",
|
||||||
|
po::value<std::uint16_t>()->default_value(
|
||||||
|
CommandLineOptions::c_defaultNumThreads),
|
||||||
|
"number of threads to create")(
|
||||||
|
"epochProcessingInterval",
|
||||||
|
po::value<std::uint32_t>()->default_value(
|
||||||
|
CommandLineOptions::c_defaultEpochProcessingIntervalInMilli),
|
||||||
|
"epoch processing interval (ms)")(
|
||||||
|
"numActionsQueue",
|
||||||
|
po::value<std::uint8_t>()->default_value(
|
||||||
|
CommandLineOptions::c_defaultNumActionsQueue),
|
||||||
|
"number of actions queue")(
|
||||||
|
"recordTimeToLive",
|
||||||
|
po::value<std::uint32_t>()->default_value(
|
||||||
|
CommandLineOptions::c_defaultRecordTimeToLiveInSeconds),
|
||||||
|
"record time to live (s)")(
|
||||||
|
"cacheSize",
|
||||||
|
po::value<std::uint64_t>()->default_value(
|
||||||
|
CommandLineOptions::c_defaultCacheSizeInBytes),
|
||||||
|
"cache size in bytes")(
|
||||||
|
"forceTimeBasedEviction",
|
||||||
|
po::value<bool>()->default_value(
|
||||||
|
CommandLineOptions::c_defaultForceTimeBasedEviction),
|
||||||
|
"force time based eviction");
|
||||||
|
|
||||||
po::options_description all("Allowed options");
|
po::options_description all("Allowed options");
|
||||||
all.add(general).add(benchmarkOptions);
|
all.add(general).add(benchmarkOptions);
|
||||||
|
@ -610,78 +584,62 @@ CommandLineOptions Parse(int argc, char** argv)
|
||||||
|
|
||||||
CommandLineOptions options;
|
CommandLineOptions options;
|
||||||
|
|
||||||
if (vm.count("help"))
|
if (vm.count("help")) {
|
||||||
{
|
|
||||||
std::cout << all;
|
std::cout << all;
|
||||||
}
|
} else if (vm.count("module")) {
|
||||||
else if (vm.count("module"))
|
|
||||||
{
|
|
||||||
options.m_module = vm["module"].as<std::string>();
|
options.m_module = vm["module"].as<std::string>();
|
||||||
|
|
||||||
if (vm.count("dataSetSize"))
|
if (vm.count("dataSetSize")) {
|
||||||
{
|
|
||||||
options.m_dataSetSize = vm["dataSetSize"].as<std::size_t>();
|
options.m_dataSetSize = vm["dataSetSize"].as<std::size_t>();
|
||||||
}
|
}
|
||||||
if (vm.count("numBuckets"))
|
if (vm.count("numBuckets")) {
|
||||||
{
|
|
||||||
options.m_numBuckets = vm["numBuckets"].as<std::uint32_t>();
|
options.m_numBuckets = vm["numBuckets"].as<std::uint32_t>();
|
||||||
}
|
}
|
||||||
if (vm.count("keySize"))
|
if (vm.count("keySize")) {
|
||||||
{
|
|
||||||
options.m_keySize = vm["keySize"].as<std::uint16_t>();
|
options.m_keySize = vm["keySize"].as<std::uint16_t>();
|
||||||
}
|
}
|
||||||
if (vm.count("valueSize"))
|
if (vm.count("valueSize")) {
|
||||||
{
|
|
||||||
options.m_valueSize = vm["valueSize"].as<std::uint32_t>();
|
options.m_valueSize = vm["valueSize"].as<std::uint32_t>();
|
||||||
}
|
}
|
||||||
if (vm.count("randomizeValueSize"))
|
if (vm.count("randomizeValueSize")) {
|
||||||
{
|
|
||||||
options.m_randomizeValueSize = true;
|
options.m_randomizeValueSize = true;
|
||||||
}
|
}
|
||||||
if (vm.count("numIterationsPerGetContext"))
|
if (vm.count("numIterationsPerGetContext")) {
|
||||||
{
|
options.m_numIterationsPerGetContext =
|
||||||
options.m_numIterationsPerGetContext = vm["numIterationsPerGetContext"].as<std::uint32_t>();
|
vm["numIterationsPerGetContext"].as<std::uint32_t>();
|
||||||
}
|
}
|
||||||
if (vm.count("numThreads"))
|
if (vm.count("numThreads")) {
|
||||||
{
|
|
||||||
options.m_numThreads = vm["numThreads"].as<std::uint16_t>();
|
options.m_numThreads = vm["numThreads"].as<std::uint16_t>();
|
||||||
}
|
}
|
||||||
if (vm.count("epochProcessingInterval"))
|
if (vm.count("epochProcessingInterval")) {
|
||||||
{
|
options.m_epochProcessingIntervalInMilli =
|
||||||
options.m_epochProcessingIntervalInMilli = vm["epochProcessingInterval"].as<std::uint32_t>();
|
vm["epochProcessingInterval"].as<std::uint32_t>();
|
||||||
}
|
}
|
||||||
if (vm.count("numActionsQueue"))
|
if (vm.count("numActionsQueue")) {
|
||||||
{
|
|
||||||
options.m_numActionsQueue = vm["numActionsQueue"].as<std::uint8_t>();
|
options.m_numActionsQueue = vm["numActionsQueue"].as<std::uint8_t>();
|
||||||
}
|
}
|
||||||
if (vm.count("recordTimeToLive"))
|
if (vm.count("recordTimeToLive")) {
|
||||||
{
|
options.m_recordTimeToLiveInSeconds =
|
||||||
options.m_recordTimeToLiveInSeconds = vm["recordTimeToLive"].as<std::uint32_t>();
|
vm["recordTimeToLive"].as<std::uint32_t>();
|
||||||
}
|
}
|
||||||
if (vm.count("cacheSize"))
|
if (vm.count("cacheSize")) {
|
||||||
{
|
|
||||||
options.m_cacheSizeInBytes = vm["cacheSize"].as<std::uint64_t>();
|
options.m_cacheSizeInBytes = vm["cacheSize"].as<std::uint64_t>();
|
||||||
}
|
}
|
||||||
if (vm.count("forceTimeBasedEviction"))
|
if (vm.count("forceTimeBasedEviction")) {
|
||||||
{
|
options.m_forceTimeBasedEviction =
|
||||||
options.m_forceTimeBasedEviction = vm["forceTimeBasedEviction"].as<bool>();
|
vm["forceTimeBasedEviction"].as<bool>();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << all;
|
std::cout << all;
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
int main(int argc, char** argv)
|
|
||||||
{
|
|
||||||
auto options = Parse(argc, argv);
|
auto options = Parse(argc, argv);
|
||||||
|
|
||||||
if (options.m_module.empty())
|
if (options.m_module.empty()) {
|
||||||
{
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -689,22 +647,16 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
PrintHardwareInfo();
|
PrintHardwareInfo();
|
||||||
|
|
||||||
if (options.m_module == "write-perf"
|
if (options.m_module == "write-perf" ||
|
||||||
|| options.m_module == "overwrite-perf"
|
options.m_module == "overwrite-perf" ||
|
||||||
|| options.m_module == "cache-write-perf")
|
options.m_module == "cache-write-perf") {
|
||||||
{
|
|
||||||
WritePerfTest(options);
|
WritePerfTest(options);
|
||||||
}
|
} else if (options.m_module == "read-perf" ||
|
||||||
else if (options.m_module == "read-perf"
|
options.m_module == "cache-read-perf") {
|
||||||
|| options.m_module == "cache-read-perf")
|
|
||||||
{
|
|
||||||
ReadPerfTest(options);
|
ReadPerfTest(options);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << "Unknown module: " << options.m_module << std::endl;
|
std::cout << "Unknown module: " << options.m_module << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,21 +6,16 @@
|
||||||
|
|
||||||
using namespace L4;
|
using namespace L4;
|
||||||
|
|
||||||
void SimpleExample()
|
void SimpleExample() {
|
||||||
{
|
EpochManagerConfig epochConfig{1000, std::chrono::milliseconds(100), 1};
|
||||||
EpochManagerConfig epochConfig{ 1000, std::chrono::milliseconds(100), 1 };
|
LocalMemory::HashTableService service{epochConfig};
|
||||||
LocalMemory::HashTableService service{ epochConfig };
|
|
||||||
|
|
||||||
auto hashTableIndex = service.AddHashTable(
|
auto hashTableIndex = service.AddHashTable(
|
||||||
HashTableConfig("Table1", HashTableConfig::Setting{ 1000000 }));
|
HashTableConfig("Table1", HashTableConfig::Setting{1000000}));
|
||||||
|
|
||||||
std::vector<std::pair<std::string, std::string>> keyValuePairs =
|
std::vector<std::pair<std::string, std::string>> keyValuePairs = {
|
||||||
{
|
{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"},
|
||||||
{ "key1", "value1" },
|
{"key4", "value4"}, {"key5", "value5"},
|
||||||
{ "key2", "value2" },
|
|
||||||
{ "key3", "value3" },
|
|
||||||
{ "key4", "value4" },
|
|
||||||
{ "key5", "value5" },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Write data.
|
// Write data.
|
||||||
|
@ -28,8 +23,7 @@ void SimpleExample()
|
||||||
auto context = service.GetContext();
|
auto context = service.GetContext();
|
||||||
auto& hashTable = context[hashTableIndex];
|
auto& hashTable = context[hashTableIndex];
|
||||||
|
|
||||||
for (const auto& keyValuePair : keyValuePairs)
|
for (const auto& keyValuePair : keyValuePairs) {
|
||||||
{
|
|
||||||
const auto& keyStr = keyValuePair.first;
|
const auto& keyStr = keyValuePair.first;
|
||||||
const auto& valStr = keyValuePair.second;
|
const auto& valStr = keyValuePair.second;
|
||||||
|
|
||||||
|
@ -53,8 +47,7 @@ void SimpleExample()
|
||||||
// operator[] on the context and Get() are lock-free.
|
// operator[] on the context and Get() are lock-free.
|
||||||
auto& hashTable = context[hashTableIndex];
|
auto& hashTable = context[hashTableIndex];
|
||||||
|
|
||||||
for (const auto& keyValuePair : keyValuePairs)
|
for (const auto& keyValuePair : keyValuePairs) {
|
||||||
{
|
|
||||||
const auto& keyStr = keyValuePair.first;
|
const auto& keyStr = keyValuePair.first;
|
||||||
|
|
||||||
IWritableHashTable::Key key;
|
IWritableHashTable::Key key;
|
||||||
|
@ -64,13 +57,14 @@ void SimpleExample()
|
||||||
IWritableHashTable::Value val;
|
IWritableHashTable::Value val;
|
||||||
hashTable.Get(key, val);
|
hashTable.Get(key, val);
|
||||||
|
|
||||||
std::cout << std::string(reinterpret_cast<const char*>(val.m_data), val.m_size) << std::endl;
|
std::cout << std::string(reinterpret_cast<const char*>(val.m_data),
|
||||||
|
val.m_size)
|
||||||
|
<< std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CacheHashTableExample()
|
void CacheHashTableExample() {
|
||||||
{
|
|
||||||
LocalMemory::HashTableService service;
|
LocalMemory::HashTableService service;
|
||||||
|
|
||||||
HashTableConfig::Cache cacheConfig{
|
HashTableConfig::Cache cacheConfig{
|
||||||
|
@ -79,22 +73,17 @@ void CacheHashTableExample()
|
||||||
true // Remove any expired records during eviction.
|
true // Remove any expired records during eviction.
|
||||||
};
|
};
|
||||||
|
|
||||||
auto hashTableIndex = service.AddHashTable(
|
auto hashTableIndex = service.AddHashTable(HashTableConfig(
|
||||||
HashTableConfig(
|
"Table1", HashTableConfig::Setting{1000000}, cacheConfig));
|
||||||
"Table1",
|
|
||||||
HashTableConfig::Setting{ 1000000 },
|
|
||||||
cacheConfig));
|
|
||||||
|
|
||||||
(void)hashTableIndex;
|
(void)hashTableIndex;
|
||||||
// Use hash table similar to SimpleExample().
|
// Use hash table similar to SimpleExample().
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
int main() {
|
||||||
{
|
|
||||||
SimpleExample();
|
SimpleExample();
|
||||||
|
|
||||||
CacheHashTableExample();
|
CacheHashTableExample();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,109 +1,98 @@
|
||||||
#include <boost/test/unit_test.hpp>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include "Utils.h"
|
|
||||||
#include "Mocks.h"
|
|
||||||
#include "CheckedAllocator.h"
|
#include "CheckedAllocator.h"
|
||||||
#include "L4/HashTable/Common/Record.h"
|
|
||||||
#include "L4/HashTable/Cache/Metadata.h"
|
|
||||||
#include "L4/HashTable/Cache/HashTable.h"
|
#include "L4/HashTable/Cache/HashTable.h"
|
||||||
|
#include "L4/HashTable/Cache/Metadata.h"
|
||||||
|
#include "L4/HashTable/Common/Record.h"
|
||||||
|
#include "Mocks.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
using namespace HashTable::Cache;
|
using namespace HashTable::Cache;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
class MockClock
|
class MockClock {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
MockClock() = default;
|
MockClock() = default;
|
||||||
|
|
||||||
seconds GetCurrentEpochTime() const
|
seconds GetCurrentEpochTime() const { return s_currentEpochTime; }
|
||||||
{
|
|
||||||
return s_currentEpochTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void SetEpochTime(seconds time)
|
static void SetEpochTime(seconds time) { s_currentEpochTime = time; }
|
||||||
{
|
|
||||||
s_currentEpochTime = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void IncrementEpochTime(seconds increment)
|
static void IncrementEpochTime(seconds increment) {
|
||||||
{
|
|
||||||
s_currentEpochTime += increment;
|
s_currentEpochTime += increment;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static seconds s_currentEpochTime;
|
static seconds s_currentEpochTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
seconds MockClock::s_currentEpochTime{ 0U };
|
seconds MockClock::s_currentEpochTime{0U};
|
||||||
|
|
||||||
|
class CacheHashTableTestFixture {
|
||||||
class CacheHashTableTestFixture
|
public:
|
||||||
{
|
|
||||||
public:
|
|
||||||
using Allocator = CheckedAllocator<>;
|
using Allocator = CheckedAllocator<>;
|
||||||
using CacheHashTable = WritableHashTable<Allocator, MockClock>;
|
using CacheHashTable = WritableHashTable<Allocator, MockClock>;
|
||||||
using ReadOnlyCacheHashTable = ReadOnlyHashTable<Allocator, MockClock>;
|
using ReadOnlyCacheHashTable = ReadOnlyHashTable<Allocator, MockClock>;
|
||||||
using HashTable = CacheHashTable::HashTable;
|
using HashTable = CacheHashTable::HashTable;
|
||||||
|
|
||||||
CacheHashTableTestFixture()
|
CacheHashTableTestFixture()
|
||||||
: m_allocator{}
|
: m_allocator{},
|
||||||
, m_hashTable { HashTable::Setting{ 100U }, m_allocator }
|
m_hashTable{HashTable::Setting{100U}, m_allocator},
|
||||||
, m_epochManager{}
|
m_epochManager{} {
|
||||||
{
|
MockClock::SetEpochTime(seconds{0U});
|
||||||
MockClock::SetEpochTime(seconds{ 0U });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheHashTableTestFixture(const CacheHashTableTestFixture&) = delete;
|
CacheHashTableTestFixture(const CacheHashTableTestFixture&) = delete;
|
||||||
CacheHashTableTestFixture& operator=(const CacheHashTableTestFixture&) = delete;
|
CacheHashTableTestFixture& operator=(const CacheHashTableTestFixture&) =
|
||||||
|
delete;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
template <typename TCacheHashTable>
|
template <typename TCacheHashTable>
|
||||||
bool Get(TCacheHashTable& hashTable, const std::string& key, IReadOnlyHashTable::Value& value)
|
bool Get(TCacheHashTable& hashTable,
|
||||||
{
|
const std::string& key,
|
||||||
|
IReadOnlyHashTable::Value& value) {
|
||||||
return hashTable.Get(
|
return hashTable.Get(
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()),
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()), value);
|
||||||
value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Add(CacheHashTable& hashTable, const std::string& key, const std::string& value)
|
void Add(CacheHashTable& hashTable,
|
||||||
{
|
const std::string& key,
|
||||||
|
const std::string& value) {
|
||||||
hashTable.Add(
|
hashTable.Add(
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()),
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()),
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Value>(value.c_str()));
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(value.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Remove(CacheHashTable& hashTable, const std::string& key)
|
void Remove(CacheHashTable& hashTable, const std::string& key) {
|
||||||
{
|
hashTable.Remove(
|
||||||
hashTable.Remove(Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()));
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename TCacheHashTable>
|
template <typename TCacheHashTable>
|
||||||
bool CheckRecord(TCacheHashTable& hashTable, const std::string& key, const std::string& expectedValue)
|
bool CheckRecord(TCacheHashTable& hashTable,
|
||||||
{
|
const std::string& key,
|
||||||
|
const std::string& expectedValue) {
|
||||||
IReadOnlyHashTable::Value value;
|
IReadOnlyHashTable::Value value;
|
||||||
return Get(hashTable, key, value) && AreTheSame(value, expectedValue);
|
return Get(hashTable, key, value) && AreTheSame(value, expectedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AreTheSame(const IReadOnlyHashTable::Value& actual, const std::string& expected)
|
bool AreTheSame(const IReadOnlyHashTable::Value& actual,
|
||||||
{
|
const std::string& expected) {
|
||||||
return (actual.m_size == expected.size())
|
return (actual.m_size == expected.size()) &&
|
||||||
&& !memcmp(actual.m_data, expected.c_str(), actual.m_size);
|
!memcmp(actual.m_data, expected.c_str(), actual.m_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Blob>
|
template <typename Blob>
|
||||||
bool Exist(const Blob& actual, const std::vector<std::string>& expectedSet)
|
bool Exist(const Blob& actual, const std::vector<std::string>& expectedSet) {
|
||||||
{
|
const std::string actualStr(reinterpret_cast<const char*>(actual.m_data),
|
||||||
const std::string actualStr(
|
|
||||||
reinterpret_cast<const char*>(actual.m_data),
|
|
||||||
actual.m_size);
|
actual.m_size);
|
||||||
|
|
||||||
return std::find(expectedSet.cbegin(), expectedSet.cend(), actualStr) != expectedSet.cend();
|
return std::find(expectedSet.cbegin(), expectedSet.cend(), actualStr) !=
|
||||||
|
expectedSet.cend();
|
||||||
}
|
}
|
||||||
|
|
||||||
Allocator m_allocator;
|
Allocator m_allocator;
|
||||||
|
@ -112,31 +101,28 @@ protected:
|
||||||
MockClock m_clock;
|
MockClock m_clock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(CacheHashTableTests)
|
BOOST_AUTO_TEST_SUITE(CacheHashTableTests)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(MetadataTest) {
|
||||||
BOOST_AUTO_TEST_CASE(MetadataTest)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> buffer(20);
|
std::vector<std::uint8_t> buffer(20);
|
||||||
|
|
||||||
// The following will test with 1..8 byte alignments.
|
// The following will test with 1..8 byte alignments.
|
||||||
for (std::uint16_t i = 0U; i < 8U; ++i)
|
for (std::uint16_t i = 0U; i < 8U; ++i) {
|
||||||
{
|
std::uint32_t* metadataBuffer =
|
||||||
std::uint32_t* metadataBuffer = reinterpret_cast<std::uint32_t*>(buffer.data() + i);
|
reinterpret_cast<std::uint32_t*>(buffer.data() + i);
|
||||||
seconds currentEpochTime{ 0x7FABCDEF };
|
seconds currentEpochTime{0x7FABCDEF};
|
||||||
|
|
||||||
Metadata metadata{ metadataBuffer, currentEpochTime };
|
Metadata metadata{metadataBuffer, currentEpochTime};
|
||||||
|
|
||||||
BOOST_CHECK(currentEpochTime == metadata.GetEpochTime());
|
BOOST_CHECK(currentEpochTime == metadata.GetEpochTime());
|
||||||
|
|
||||||
// 10 seconds have elapsed.
|
// 10 seconds have elapsed.
|
||||||
currentEpochTime += seconds{ 10U };
|
currentEpochTime += seconds{10U};
|
||||||
|
|
||||||
// Check the expiration based on the time to live value.
|
// Check the expiration based on the time to live value.
|
||||||
BOOST_CHECK(!metadata.IsExpired(currentEpochTime, seconds{ 15 }));
|
BOOST_CHECK(!metadata.IsExpired(currentEpochTime, seconds{15}));
|
||||||
BOOST_CHECK(!metadata.IsExpired(currentEpochTime, seconds{ 10 }));
|
BOOST_CHECK(!metadata.IsExpired(currentEpochTime, seconds{10}));
|
||||||
BOOST_CHECK(metadata.IsExpired(currentEpochTime, seconds{ 5U }));
|
BOOST_CHECK(metadata.IsExpired(currentEpochTime, seconds{5U}));
|
||||||
|
|
||||||
// Test access state.
|
// Test access state.
|
||||||
BOOST_CHECK(!metadata.IsAccessed());
|
BOOST_CHECK(!metadata.IsAccessed());
|
||||||
|
@ -149,46 +135,33 @@ BOOST_AUTO_TEST_CASE(MetadataTest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(ExpirationTest, CacheHashTableTestFixture) {
|
||||||
BOOST_FIXTURE_TEST_CASE(ExpirationTest, CacheHashTableTestFixture)
|
|
||||||
{
|
|
||||||
// Don't care about evict in this test case, so make the cache size big.
|
// Don't care about evict in this test case, so make the cache size big.
|
||||||
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
|
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
|
||||||
constexpr seconds c_recordTimeToLive{ 20U };
|
constexpr seconds c_recordTimeToLive{20U};
|
||||||
|
|
||||||
CacheHashTable hashTable(
|
CacheHashTable hashTable(m_hashTable, m_epochManager, c_maxCacheSizeInBytes,
|
||||||
m_hashTable,
|
c_recordTimeToLive, false);
|
||||||
m_epochManager,
|
|
||||||
c_maxCacheSizeInBytes,
|
|
||||||
c_recordTimeToLive,
|
|
||||||
false);
|
|
||||||
|
|
||||||
const std::vector<std::pair<std::string, std::string>> c_keyValuePairs =
|
const std::vector<std::pair<std::string, std::string>> c_keyValuePairs = {
|
||||||
{
|
{"key1", "value1"},
|
||||||
{ "key1", "value1" },
|
{"key2", "value2"},
|
||||||
{ "key2", "value2" },
|
{"key3", "value3"},
|
||||||
{ "key3", "value3" },
|
{"key4", "value4"},
|
||||||
{ "key4", "value4" },
|
{"key5", "value5"}};
|
||||||
{ "key5", "value5" }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add 5 records at a different epoch time (10 seconds increment).
|
// Add 5 records at a different epoch time (10 seconds increment).
|
||||||
for (const auto& pair : c_keyValuePairs)
|
for (const auto& pair : c_keyValuePairs) {
|
||||||
{
|
MockClock::IncrementEpochTime(seconds{10});
|
||||||
MockClock::IncrementEpochTime(seconds{ 10 });
|
|
||||||
Add(hashTable, pair.first, pair.second);
|
Add(hashTable, pair.first, pair.second);
|
||||||
|
|
||||||
// Make sure the records can be retrieved right away. The record has not been
|
// Make sure the records can be retrieved right away. The record has not
|
||||||
// expired since the clock hasn't moved yet.
|
// been expired since the clock hasn't moved yet.
|
||||||
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
|
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& perfData = hashTable.GetPerfData();
|
const auto& perfData = hashTable.GetPerfData();
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData, {{HashTablePerfCounter::CacheHitCount, 5}});
|
||||||
perfData,
|
|
||||||
{
|
|
||||||
{ HashTablePerfCounter::CacheHitCount, 5 }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now we have the following data sets:
|
// Now we have the following data sets:
|
||||||
// | Key | Value | Creation time |
|
// | Key | Value | Creation time |
|
||||||
|
@ -200,65 +173,49 @@ BOOST_FIXTURE_TEST_CASE(ExpirationTest, CacheHashTableTestFixture)
|
||||||
// And the current clock is at 50.
|
// And the current clock is at 50.
|
||||||
|
|
||||||
// Do look ups and check expired records.
|
// Do look ups and check expired records.
|
||||||
for (const auto& pair : c_keyValuePairs)
|
for (const auto& pair : c_keyValuePairs) {
|
||||||
{
|
|
||||||
IReadOnlyHashTable::Value value;
|
IReadOnlyHashTable::Value value;
|
||||||
// Our time to live value is 20, so key0 and key0 records should be expired.
|
// Our time to live value is 20, so key0 and key0 records should be expired.
|
||||||
if (pair.first == "key1" || pair.first == "key2")
|
if (pair.first == "key1" || pair.first == "key2") {
|
||||||
{
|
|
||||||
BOOST_CHECK(!Get(hashTable, pair.first, value));
|
BOOST_CHECK(!Get(hashTable, pair.first, value));
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
|
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
{{HashTablePerfCounter::CacheHitCount, 8},
|
||||||
{
|
{HashTablePerfCounter::CacheMissCount, 2}});
|
||||||
{ HashTablePerfCounter::CacheHitCount, 8 },
|
|
||||||
{ HashTablePerfCounter::CacheMissCount, 2 }
|
|
||||||
});
|
|
||||||
|
|
||||||
MockClock::IncrementEpochTime(seconds{ 100 });
|
MockClock::IncrementEpochTime(seconds{100});
|
||||||
|
|
||||||
// All the records should be expired now.
|
// All the records should be expired now.
|
||||||
for (const auto& pair : c_keyValuePairs)
|
for (const auto& pair : c_keyValuePairs) {
|
||||||
{
|
|
||||||
IReadOnlyHashTable::Value value;
|
IReadOnlyHashTable::Value value;
|
||||||
BOOST_CHECK(!Get(hashTable, pair.first, value));
|
BOOST_CHECK(!Get(hashTable, pair.first, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
{{HashTablePerfCounter::CacheHitCount, 8},
|
||||||
{
|
{HashTablePerfCounter::CacheMissCount, 7}});
|
||||||
{ HashTablePerfCounter::CacheHitCount, 8 },
|
|
||||||
{ HashTablePerfCounter::CacheMissCount, 7 }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(CacheHashTableIteratorTest, CacheHashTableTestFixture) {
|
||||||
BOOST_FIXTURE_TEST_CASE(CacheHashTableIteratorTest, CacheHashTableTestFixture)
|
|
||||||
{
|
|
||||||
// Don't care about evict in this test case, so make the cache size big.
|
// Don't care about evict in this test case, so make the cache size big.
|
||||||
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
|
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
|
||||||
constexpr seconds c_recordTimeToLive{ 20U };
|
constexpr seconds c_recordTimeToLive{20U};
|
||||||
|
|
||||||
CacheHashTable hashTable(
|
CacheHashTable hashTable(m_hashTable, m_epochManager, c_maxCacheSizeInBytes,
|
||||||
m_hashTable,
|
c_recordTimeToLive, false);
|
||||||
m_epochManager,
|
|
||||||
c_maxCacheSizeInBytes,
|
|
||||||
c_recordTimeToLive,
|
|
||||||
false);
|
|
||||||
|
|
||||||
const std::vector<std::string> c_keys = { "key1", "key2", "key3", "key4", "key5" };
|
const std::vector<std::string> c_keys = {"key1", "key2", "key3", "key4",
|
||||||
const std::vector<std::string> c_vals = { "val1", "val2", "val3", "val4", "val5" };
|
"key5"};
|
||||||
|
const std::vector<std::string> c_vals = {"val1", "val2", "val3", "val4",
|
||||||
|
"val5"};
|
||||||
|
|
||||||
// Add 5 records at a different epoch time (3 seconds increment).
|
// Add 5 records at a different epoch time (3 seconds increment).
|
||||||
for (std::size_t i = 0; i < c_keys.size(); ++i)
|
for (std::size_t i = 0; i < c_keys.size(); ++i) {
|
||||||
{
|
MockClock::IncrementEpochTime(seconds{3});
|
||||||
MockClock::IncrementEpochTime(seconds{ 3 });
|
|
||||||
Add(hashTable, c_keys[i], c_vals[i]);
|
Add(hashTable, c_keys[i], c_vals[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,8 +230,7 @@ BOOST_FIXTURE_TEST_CASE(CacheHashTableIteratorTest, CacheHashTableTestFixture)
|
||||||
|
|
||||||
auto iterator = hashTable.GetIterator();
|
auto iterator = hashTable.GetIterator();
|
||||||
std::uint16_t numRecords = 0;
|
std::uint16_t numRecords = 0;
|
||||||
while (iterator->MoveNext())
|
while (iterator->MoveNext()) {
|
||||||
{
|
|
||||||
++numRecords;
|
++numRecords;
|
||||||
BOOST_CHECK(Exist(iterator->GetKey(), c_keys));
|
BOOST_CHECK(Exist(iterator->GetKey(), c_keys));
|
||||||
BOOST_CHECK(Exist(iterator->GetValue(), c_vals));
|
BOOST_CHECK(Exist(iterator->GetValue(), c_vals));
|
||||||
|
@ -283,169 +239,146 @@ BOOST_FIXTURE_TEST_CASE(CacheHashTableIteratorTest, CacheHashTableTestFixture)
|
||||||
BOOST_CHECK_EQUAL(numRecords, 5);
|
BOOST_CHECK_EQUAL(numRecords, 5);
|
||||||
|
|
||||||
// The clock becomes 30 and key1, key2 and key3 should expire.
|
// The clock becomes 30 and key1, key2 and key3 should expire.
|
||||||
MockClock::IncrementEpochTime(seconds{ 15 });
|
MockClock::IncrementEpochTime(seconds{15});
|
||||||
|
|
||||||
iterator = hashTable.GetIterator();
|
iterator = hashTable.GetIterator();
|
||||||
numRecords = 0;
|
numRecords = 0;
|
||||||
while (iterator->MoveNext())
|
while (iterator->MoveNext()) {
|
||||||
{
|
|
||||||
++numRecords;
|
++numRecords;
|
||||||
BOOST_CHECK(
|
BOOST_CHECK(
|
||||||
Exist(
|
Exist(iterator->GetKey(),
|
||||||
iterator->GetKey(),
|
std::vector<std::string>{c_keys.cbegin() + 2, c_keys.cend()}));
|
||||||
std::vector<std::string>{ c_keys.cbegin() + 2, c_keys.cend() }));
|
|
||||||
BOOST_CHECK(
|
BOOST_CHECK(
|
||||||
Exist(
|
Exist(iterator->GetValue(),
|
||||||
iterator->GetValue(),
|
std::vector<std::string>{c_vals.cbegin() + 2, c_vals.cend()}));
|
||||||
std::vector<std::string>{ c_vals.cbegin() + 2, c_vals.cend() }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_CHECK_EQUAL(numRecords, 2);
|
BOOST_CHECK_EQUAL(numRecords, 2);
|
||||||
|
|
||||||
// The clock becomes 40 and all records should be expired now.
|
// The clock becomes 40 and all records should be expired now.
|
||||||
MockClock::IncrementEpochTime(seconds{ 10 });
|
MockClock::IncrementEpochTime(seconds{10});
|
||||||
|
|
||||||
iterator = hashTable.GetIterator();
|
iterator = hashTable.GetIterator();
|
||||||
while (iterator->MoveNext())
|
while (iterator->MoveNext()) {
|
||||||
{
|
|
||||||
BOOST_CHECK(false);
|
BOOST_CHECK(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(TimeBasedEvictionTest, CacheHashTableTestFixture) {
|
||||||
BOOST_FIXTURE_TEST_CASE(TimeBasedEvictionTest, CacheHashTableTestFixture)
|
// We only care about time-based eviction in this test, so make the cache size
|
||||||
{
|
// big.
|
||||||
// We only care about time-based eviction in this test, so make the cache size big.
|
|
||||||
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
|
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
|
||||||
constexpr seconds c_recordTimeToLive{ 10U };
|
constexpr seconds c_recordTimeToLive{10U};
|
||||||
|
|
||||||
// Hash table with one bucket makes testing the time-based eviction easy.
|
// Hash table with one bucket makes testing the time-based eviction easy.
|
||||||
HashTable internalHashTable{ HashTable::Setting{ 1 }, m_allocator };
|
HashTable internalHashTable{HashTable::Setting{1}, m_allocator};
|
||||||
CacheHashTable hashTable(
|
CacheHashTable hashTable(internalHashTable, m_epochManager,
|
||||||
internalHashTable,
|
c_maxCacheSizeInBytes, c_recordTimeToLive, true);
|
||||||
m_epochManager,
|
|
||||||
c_maxCacheSizeInBytes,
|
|
||||||
c_recordTimeToLive,
|
|
||||||
true);
|
|
||||||
|
|
||||||
const std::vector<std::pair<std::string, std::string>> c_keyValuePairs =
|
const std::vector<std::pair<std::string, std::string>> c_keyValuePairs = {
|
||||||
{
|
{"key1", "value1"},
|
||||||
{ "key1", "value1" },
|
{"key2", "value2"},
|
||||||
{ "key2", "value2" },
|
{"key3", "value3"},
|
||||||
{ "key3", "value3" },
|
{"key4", "value4"},
|
||||||
{ "key4", "value4" },
|
{"key5", "value5"}};
|
||||||
{ "key5", "value5" }
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& pair : c_keyValuePairs)
|
for (const auto& pair : c_keyValuePairs) {
|
||||||
{
|
|
||||||
Add(hashTable, pair.first, pair.second);
|
Add(hashTable, pair.first, pair.second);
|
||||||
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
|
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& perfData = hashTable.GetPerfData();
|
const auto& perfData = hashTable.GetPerfData();
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
|
||||||
{
|
{
|
||||||
{ HashTablePerfCounter::CacheHitCount, 5 },
|
{HashTablePerfCounter::CacheHitCount, 5},
|
||||||
{ HashTablePerfCounter::RecordsCount, 5 },
|
{HashTablePerfCounter::RecordsCount, 5},
|
||||||
{ HashTablePerfCounter::EvictedRecordsCount, 0 },
|
{HashTablePerfCounter::EvictedRecordsCount, 0},
|
||||||
});
|
});
|
||||||
|
|
||||||
MockClock::IncrementEpochTime(seconds{ 20 });
|
MockClock::IncrementEpochTime(seconds{20});
|
||||||
|
|
||||||
// All the records should be expired now.
|
// All the records should be expired now.
|
||||||
for (const auto& pair : c_keyValuePairs)
|
for (const auto& pair : c_keyValuePairs) {
|
||||||
{
|
|
||||||
IReadOnlyHashTable::Value value;
|
IReadOnlyHashTable::Value value;
|
||||||
BOOST_CHECK(!Get(hashTable, pair.first, value));
|
BOOST_CHECK(!Get(hashTable, pair.first, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
|
||||||
{
|
{
|
||||||
{ HashTablePerfCounter::CacheHitCount, 5 },
|
{HashTablePerfCounter::CacheHitCount, 5},
|
||||||
{ HashTablePerfCounter::CacheMissCount, 5 },
|
{HashTablePerfCounter::CacheMissCount, 5},
|
||||||
{ HashTablePerfCounter::RecordsCount, 5 },
|
{HashTablePerfCounter::RecordsCount, 5},
|
||||||
{ HashTablePerfCounter::EvictedRecordsCount, 0 },
|
{HashTablePerfCounter::EvictedRecordsCount, 0},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Now try to add one record and all the expired records should be evicted.
|
// Now try to add one record and all the expired records should be evicted.
|
||||||
const auto& keyValuePair = c_keyValuePairs[0];
|
const auto& keyValuePair = c_keyValuePairs[0];
|
||||||
Add(hashTable, keyValuePair.first, keyValuePair.second);
|
Add(hashTable, keyValuePair.first, keyValuePair.second);
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
|
||||||
{
|
{
|
||||||
{ HashTablePerfCounter::RecordsCount, 1 },
|
{HashTablePerfCounter::RecordsCount, 1},
|
||||||
{ HashTablePerfCounter::EvictedRecordsCount, 5 },
|
{HashTablePerfCounter::EvictedRecordsCount, 5},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(EvcitAllRecordsTest, CacheHashTableTestFixture) {
|
||||||
BOOST_FIXTURE_TEST_CASE(EvcitAllRecordsTest, CacheHashTableTestFixture)
|
|
||||||
{
|
|
||||||
const auto& perfData = m_hashTable.m_perfData;
|
const auto& perfData = m_hashTable.m_perfData;
|
||||||
const auto initialTotalIndexSize = perfData.Get(HashTablePerfCounter::TotalIndexSize);
|
const auto initialTotalIndexSize =
|
||||||
|
perfData.Get(HashTablePerfCounter::TotalIndexSize);
|
||||||
const std::uint64_t c_maxCacheSizeInBytes = 500 + initialTotalIndexSize;
|
const std::uint64_t c_maxCacheSizeInBytes = 500 + initialTotalIndexSize;
|
||||||
constexpr seconds c_recordTimeToLive{ 5 };
|
constexpr seconds c_recordTimeToLive{5};
|
||||||
|
|
||||||
CacheHashTable hashTable{
|
CacheHashTable hashTable{m_hashTable, m_epochManager, c_maxCacheSizeInBytes,
|
||||||
m_hashTable,
|
c_recordTimeToLive, false};
|
||||||
m_epochManager,
|
|
||||||
c_maxCacheSizeInBytes,
|
|
||||||
c_recordTimeToLive,
|
|
||||||
false };
|
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
|
||||||
{
|
{
|
||||||
{ HashTablePerfCounter::EvictedRecordsCount, 0 },
|
{HashTablePerfCounter::EvictedRecordsCount, 0},
|
||||||
});
|
});
|
||||||
|
|
||||||
const std::vector<std::pair<std::string, std::string>> c_keyValuePairs =
|
const std::vector<std::pair<std::string, std::string>> c_keyValuePairs = {
|
||||||
{
|
{"key1", "value1"},
|
||||||
{ "key1", "value1" },
|
{"key2", "value2"},
|
||||||
{ "key2", "value2" },
|
{"key3", "value3"},
|
||||||
{ "key3", "value3" },
|
{"key4", "value4"},
|
||||||
{ "key4", "value4" },
|
{"key5", "value5"}};
|
||||||
{ "key5", "value5" }
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& pair : c_keyValuePairs)
|
for (const auto& pair : c_keyValuePairs) {
|
||||||
{
|
|
||||||
Add(hashTable, pair.first, pair.second);
|
Add(hashTable, pair.first, pair.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
using L4::HashTable::RecordSerializer;
|
using L4::HashTable::RecordSerializer;
|
||||||
|
|
||||||
// Variable key/value sizes.
|
// Variable key/value sizes.
|
||||||
const auto recordOverhead = RecordSerializer{ 0U, 0U }.CalculateRecordOverhead();
|
const auto recordOverhead =
|
||||||
|
RecordSerializer{0U, 0U}.CalculateRecordOverhead();
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(
|
||||||
perfData,
|
perfData,
|
||||||
{
|
{
|
||||||
{ HashTablePerfCounter::RecordsCount, c_keyValuePairs.size() },
|
{HashTablePerfCounter::RecordsCount, c_keyValuePairs.size()},
|
||||||
{ HashTablePerfCounter::TotalIndexSize, initialTotalIndexSize + (c_keyValuePairs.size() * recordOverhead) },
|
{HashTablePerfCounter::TotalIndexSize,
|
||||||
{ HashTablePerfCounter::EvictedRecordsCount, 0 },
|
initialTotalIndexSize + (c_keyValuePairs.size() * recordOverhead)},
|
||||||
|
{HashTablePerfCounter::EvictedRecordsCount, 0},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure all data records added are present and update the access status for each
|
// Make sure all data records added are present and update the access status
|
||||||
// record in order to test that accessed records are deleted when it's under memory constraint.
|
// for each record in order to test that accessed records are deleted when
|
||||||
for (const auto& pair : c_keyValuePairs)
|
// it's under memory constraint.
|
||||||
{
|
for (const auto& pair : c_keyValuePairs) {
|
||||||
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
|
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now insert a record that will force all the records to be evicted due to size.
|
// Now insert a record that will force all the records to be evicted due to
|
||||||
|
// size.
|
||||||
std::string bigRecordKeyStr(10, 'k');
|
std::string bigRecordKeyStr(10, 'k');
|
||||||
std::string bigRecordValStr(500, 'v');
|
std::string bigRecordValStr(500, 'v');
|
||||||
|
|
||||||
Add(hashTable, bigRecordKeyStr, bigRecordValStr);
|
Add(hashTable, bigRecordKeyStr, bigRecordValStr);
|
||||||
|
|
||||||
// Make sure all the previously inserted records are evicted.
|
// Make sure all the previously inserted records are evicted.
|
||||||
for (const auto& pair : c_keyValuePairs)
|
for (const auto& pair : c_keyValuePairs) {
|
||||||
{
|
|
||||||
IReadOnlyHashTable::Value value;
|
IReadOnlyHashTable::Value value;
|
||||||
BOOST_CHECK(!Get(hashTable, pair.first, value));
|
BOOST_CHECK(!Get(hashTable, pair.first, value));
|
||||||
}
|
}
|
||||||
|
@ -456,37 +389,32 @@ BOOST_FIXTURE_TEST_CASE(EvcitAllRecordsTest, CacheHashTableTestFixture)
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(
|
||||||
perfData,
|
perfData,
|
||||||
{
|
{
|
||||||
{ HashTablePerfCounter::RecordsCount, 1 },
|
{HashTablePerfCounter::RecordsCount, 1},
|
||||||
{ HashTablePerfCounter::TotalIndexSize, initialTotalIndexSize + (1 * recordOverhead) },
|
{HashTablePerfCounter::TotalIndexSize,
|
||||||
{ HashTablePerfCounter::EvictedRecordsCount, c_keyValuePairs.size() },
|
initialTotalIndexSize + (1 * recordOverhead)},
|
||||||
|
{HashTablePerfCounter::EvictedRecordsCount, c_keyValuePairs.size()},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(EvcitRecordsBasedOnAccessStatusTest,
|
||||||
|
CacheHashTableTestFixture) {
|
||||||
|
const std::uint64_t c_maxCacheSizeInBytes =
|
||||||
|
2000 + m_hashTable.m_perfData.Get(HashTablePerfCounter::TotalIndexSize);
|
||||||
|
const seconds c_recordTimeToLive{5};
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(EvcitRecordsBasedOnAccessStatusTest, CacheHashTableTestFixture)
|
CacheHashTable hashTable(m_hashTable, m_epochManager, c_maxCacheSizeInBytes,
|
||||||
{
|
c_recordTimeToLive, false);
|
||||||
const std::uint64_t c_maxCacheSizeInBytes
|
|
||||||
= 2000 + m_hashTable.m_perfData.Get(HashTablePerfCounter::TotalIndexSize);
|
|
||||||
const seconds c_recordTimeToLive{ 5 };
|
|
||||||
|
|
||||||
CacheHashTable hashTable(
|
|
||||||
m_hashTable,
|
|
||||||
m_epochManager,
|
|
||||||
c_maxCacheSizeInBytes,
|
|
||||||
c_recordTimeToLive,
|
|
||||||
false);
|
|
||||||
|
|
||||||
constexpr std::uint32_t c_valueSize = 100;
|
constexpr std::uint32_t c_valueSize = 100;
|
||||||
const std::string c_valStr(c_valueSize, 'v');
|
const std::string c_valStr(c_valueSize, 'v');
|
||||||
const auto& perfData = hashTable.GetPerfData();
|
const auto& perfData = hashTable.GetPerfData();
|
||||||
std::uint16_t key = 1;
|
std::uint16_t key = 1;
|
||||||
|
|
||||||
while ((static_cast<std::uint64_t>(perfData.Get(HashTablePerfCounter::TotalIndexSize))
|
while ((static_cast<std::uint64_t>(
|
||||||
+ perfData.Get(HashTablePerfCounter::TotalKeySize)
|
perfData.Get(HashTablePerfCounter::TotalIndexSize)) +
|
||||||
+ perfData.Get(HashTablePerfCounter::TotalValueSize)
|
perfData.Get(HashTablePerfCounter::TotalKeySize) +
|
||||||
+ c_valueSize)
|
perfData.Get(HashTablePerfCounter::TotalValueSize) + c_valueSize) <
|
||||||
< c_maxCacheSizeInBytes)
|
c_maxCacheSizeInBytes) {
|
||||||
{
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "key" << key;
|
ss << "key" << key;
|
||||||
Add(hashTable, ss.str(), c_valStr);
|
Add(hashTable, ss.str(), c_valStr);
|
||||||
|
@ -499,7 +427,8 @@ BOOST_FIXTURE_TEST_CASE(EvcitRecordsBasedOnAccessStatusTest, CacheHashTableTestF
|
||||||
// Look up with the "key1" key to update the access state.
|
// Look up with the "key1" key to update the access state.
|
||||||
BOOST_CHECK(CheckRecord(hashTable, "key1", c_valStr));
|
BOOST_CHECK(CheckRecord(hashTable, "key1", c_valStr));
|
||||||
|
|
||||||
// Now add a new key, which triggers an eviction, but deletes other records than the "key1" record.
|
// Now add a new key, which triggers an eviction, but deletes other records
|
||||||
|
// than the "key1" record.
|
||||||
Add(hashTable, "newkey", c_valStr);
|
Add(hashTable, "newkey", c_valStr);
|
||||||
|
|
||||||
// Now, eviction should have happened.
|
// Now, eviction should have happened.
|
||||||
|
@ -512,96 +441,77 @@ BOOST_FIXTURE_TEST_CASE(EvcitRecordsBasedOnAccessStatusTest, CacheHashTableTestF
|
||||||
BOOST_CHECK(CheckRecord(hashTable, "newkey", c_valStr));
|
BOOST_CHECK(CheckRecord(hashTable, "newkey", c_valStr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is similar to the one in ReadWriteHashTableTest, but necessary since
|
||||||
// This is similar to the one in ReadWriteHashTableTest, but necessary since cache store adds the meta values.
|
// cache store adds the meta values.
|
||||||
BOOST_FIXTURE_TEST_CASE(FixedKeyValueHashTableTest, CacheHashTableTestFixture)
|
BOOST_FIXTURE_TEST_CASE(FixedKeyValueHashTableTest, CacheHashTableTestFixture) {
|
||||||
{
|
|
||||||
// Fixed 4 byte keys and 6 byte values.
|
// Fixed 4 byte keys and 6 byte values.
|
||||||
std::vector<HashTable::Setting> settings =
|
std::vector<HashTable::Setting> settings = {
|
||||||
{
|
HashTable::Setting{100, 200, 4, 0}, HashTable::Setting{100, 200, 0, 6},
|
||||||
HashTable::Setting{ 100, 200, 4, 0 },
|
HashTable::Setting{100, 200, 4, 6}};
|
||||||
HashTable::Setting{ 100, 200, 0, 6 },
|
|
||||||
HashTable::Setting{ 100, 200, 4, 6 }
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& setting : settings)
|
for (const auto& setting : settings) {
|
||||||
{
|
|
||||||
// Don't care about evict in this test case, so make the cache size big.
|
// Don't care about evict in this test case, so make the cache size big.
|
||||||
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
|
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
|
||||||
constexpr seconds c_recordTimeToLive{ 20U };
|
constexpr seconds c_recordTimeToLive{20U};
|
||||||
|
|
||||||
HashTable hashTable{ setting, m_allocator };
|
HashTable hashTable{setting, m_allocator};
|
||||||
CacheHashTable writableHashTable{
|
CacheHashTable writableHashTable{hashTable, m_epochManager,
|
||||||
hashTable,
|
c_maxCacheSizeInBytes, c_recordTimeToLive,
|
||||||
m_epochManager,
|
false};
|
||||||
c_maxCacheSizeInBytes,
|
|
||||||
c_recordTimeToLive,
|
|
||||||
false };
|
|
||||||
|
|
||||||
ReadOnlyCacheHashTable readOnlyHashTable{ hashTable, c_recordTimeToLive };
|
ReadOnlyCacheHashTable readOnlyHashTable{hashTable, c_recordTimeToLive};
|
||||||
|
|
||||||
constexpr std::uint8_t c_numRecords = 10;
|
constexpr std::uint8_t c_numRecords = 10;
|
||||||
|
|
||||||
// Add records.
|
// Add records.
|
||||||
for (std::uint8_t i = 0; i < c_numRecords; ++i)
|
for (std::uint8_t i = 0; i < c_numRecords; ++i) {
|
||||||
{
|
Add(writableHashTable, "key" + std::to_string(i),
|
||||||
Add(writableHashTable, "key" + std::to_string(i), "value" + std::to_string(i));
|
"value" + std::to_string(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(writableHashTable.GetPerfData(),
|
||||||
writableHashTable.GetPerfData(),
|
{{HashTablePerfCounter::RecordsCount, 10},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 10 },
|
{HashTablePerfCounter::TotalKeySize, 40},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalValueSize, 100},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 40 },
|
{HashTablePerfCounter::MinKeySize, 4},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 100 },
|
{HashTablePerfCounter::MaxKeySize, 4},
|
||||||
{ HashTablePerfCounter::MinKeySize, 4 },
|
{HashTablePerfCounter::MinValueSize, 10},
|
||||||
{ HashTablePerfCounter::MaxKeySize, 4 },
|
{HashTablePerfCounter::MaxValueSize, 10}});
|
||||||
{ HashTablePerfCounter::MinValueSize, 10 },
|
|
||||||
{ HashTablePerfCounter::MaxValueSize, 10 }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Validate all the records added.
|
// Validate all the records added.
|
||||||
for (std::uint8_t i = 0; i < c_numRecords; ++i)
|
for (std::uint8_t i = 0; i < c_numRecords; ++i) {
|
||||||
{
|
CheckRecord(readOnlyHashTable, "key" + std::to_string(i),
|
||||||
CheckRecord(readOnlyHashTable, "key" + std::to_string(i), "value" + std::to_string(i));
|
"value" + std::to_string(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove first half of the records.
|
// Remove first half of the records.
|
||||||
for (std::uint8_t i = 0; i < c_numRecords / 2; ++i)
|
for (std::uint8_t i = 0; i < c_numRecords / 2; ++i) {
|
||||||
{
|
|
||||||
Remove(writableHashTable, "key" + std::to_string(i));
|
Remove(writableHashTable, "key" + std::to_string(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(writableHashTable.GetPerfData(),
|
||||||
writableHashTable.GetPerfData(),
|
{{HashTablePerfCounter::RecordsCount, 5},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 5 },
|
{HashTablePerfCounter::TotalKeySize, 20},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalValueSize, 50}});
|
||||||
{ HashTablePerfCounter::TotalKeySize, 20 },
|
|
||||||
{ HashTablePerfCounter::TotalValueSize, 50 }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify the records.
|
// Verify the records.
|
||||||
for (std::uint8_t i = 0; i < c_numRecords; ++i)
|
for (std::uint8_t i = 0; i < c_numRecords; ++i) {
|
||||||
{
|
if (i < (c_numRecords / 2)) {
|
||||||
if (i < (c_numRecords / 2))
|
|
||||||
{
|
|
||||||
IReadOnlyHashTable::Value value;
|
IReadOnlyHashTable::Value value;
|
||||||
BOOST_CHECK(!Get(readOnlyHashTable, "key" + std::to_string(i), value));
|
BOOST_CHECK(!Get(readOnlyHashTable, "key" + std::to_string(i), value));
|
||||||
}
|
} else {
|
||||||
else
|
CheckRecord(readOnlyHashTable, "key" + std::to_string(i),
|
||||||
{
|
"value" + std::to_string(i));
|
||||||
CheckRecord(readOnlyHashTable, "key" + std::to_string(i), "value" + std::to_string(i));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expire all the records.
|
// Expire all the records.
|
||||||
MockClock::IncrementEpochTime(seconds{ 100 });
|
MockClock::IncrementEpochTime(seconds{100});
|
||||||
|
|
||||||
// Verify the records.
|
// Verify the records.
|
||||||
for (std::uint8_t i = 0; i < c_numRecords; ++i)
|
for (std::uint8_t i = 0; i < c_numRecords; ++i) {
|
||||||
{
|
|
||||||
IReadOnlyHashTable::Value value;
|
IReadOnlyHashTable::Value value;
|
||||||
BOOST_CHECK(!Get(readOnlyHashTable, "key" + std::to_string(i), value));
|
BOOST_CHECK(!Get(readOnlyHashTable, "key" + std::to_string(i), value));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +1,50 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <boost/test/unit_test.hpp>
|
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
struct AllocationAddressHolder : public std::set<void*>
|
struct AllocationAddressHolder : public std::set<void*> {
|
||||||
{
|
~AllocationAddressHolder() { BOOST_REQUIRE(empty()); }
|
||||||
~AllocationAddressHolder()
|
|
||||||
{
|
|
||||||
BOOST_REQUIRE(empty());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T = void>
|
template <typename T = void>
|
||||||
class CheckedAllocator : public std::allocator<T>
|
class CheckedAllocator : public std::allocator<T> {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
using Base = std::allocator<T>;
|
using Base = std::allocator<T>;
|
||||||
using pointer = typename Base::pointer;
|
using pointer = typename Base::pointer;
|
||||||
|
|
||||||
template<class U>
|
template <class U>
|
||||||
struct rebind
|
struct rebind {
|
||||||
{
|
|
||||||
typedef CheckedAllocator<U> other;
|
typedef CheckedAllocator<U> other;
|
||||||
};
|
};
|
||||||
|
|
||||||
CheckedAllocator()
|
CheckedAllocator()
|
||||||
: m_allocationAddresses{ std::make_shared<AllocationAddressHolder>() }
|
: m_allocationAddresses{std::make_shared<AllocationAddressHolder>()} {}
|
||||||
{}
|
|
||||||
|
|
||||||
CheckedAllocator(const CheckedAllocator<T>&) = default;
|
CheckedAllocator(const CheckedAllocator<T>&) = default;
|
||||||
|
|
||||||
template<class U>
|
template <class U>
|
||||||
CheckedAllocator(const CheckedAllocator<U>& other)
|
CheckedAllocator(const CheckedAllocator<U>& other)
|
||||||
: m_allocationAddresses{ other.m_allocationAddresses }
|
: m_allocationAddresses{other.m_allocationAddresses} {}
|
||||||
{}
|
|
||||||
|
|
||||||
template<class U>
|
template <class U>
|
||||||
CheckedAllocator<T>& operator=(const CheckedAllocator<U>& other)
|
CheckedAllocator<T>& operator=(const CheckedAllocator<U>& other) {
|
||||||
{
|
|
||||||
m_allocationAddresses = other.m_allocationAddresses;
|
m_allocationAddresses = other.m_allocationAddresses;
|
||||||
return (*this);
|
return (*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer allocate(std::size_t count, std::allocator<void>::const_pointer hint = 0)
|
pointer allocate(std::size_t count,
|
||||||
{
|
std::allocator<void>::const_pointer hint = 0) {
|
||||||
auto address = Base::allocate(count, hint);
|
auto address = Base::allocate(count, hint);
|
||||||
BOOST_REQUIRE(m_allocationAddresses->insert(address).second);
|
BOOST_REQUIRE(m_allocationAddresses->insert(address).second);
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
void deallocate(pointer ptr, std::size_t count)
|
void deallocate(pointer ptr, std::size_t count) {
|
||||||
{
|
|
||||||
BOOST_REQUIRE(m_allocationAddresses->erase(ptr) == 1);
|
BOOST_REQUIRE(m_allocationAddresses->erase(ptr) == 1);
|
||||||
Base::deallocate(ptr, count);
|
Base::deallocate(ptr, count);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,48 @@
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
#include <mutex>
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include "Utils.h"
|
#include <mutex>
|
||||||
#include "L4/Interprocess/Connection/ConnectionMonitor.h"
|
#include "L4/Interprocess/Connection/ConnectionMonitor.h"
|
||||||
#include "L4/Interprocess/Connection/EndPointInfoUtils.h"
|
#include "L4/Interprocess/Connection/EndPointInfoUtils.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(ConnectionMonitorTests)
|
BOOST_AUTO_TEST_SUITE(ConnectionMonitorTests)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(ConnectionMonitorTest)
|
BOOST_AUTO_TEST_CASE(ConnectionMonitorTest) {
|
||||||
{
|
|
||||||
std::vector<Interprocess::Connection::EndPointInfo> endPointsDisconnected;
|
std::vector<Interprocess::Connection::EndPointInfo> endPointsDisconnected;
|
||||||
std::mutex lock;
|
std::mutex lock;
|
||||||
std::condition_variable cv;
|
std::condition_variable cv;
|
||||||
|
|
||||||
auto server = std::make_shared<Interprocess::Connection::ConnectionMonitor>();
|
auto server = std::make_shared<Interprocess::Connection::ConnectionMonitor>();
|
||||||
|
|
||||||
auto noOpCallback = [](const auto&) { throw std::runtime_error("This will not be called."); };
|
auto noOpCallback = [](const auto&) {
|
||||||
auto callback = [&](const auto& endPoint)
|
throw std::runtime_error("This will not be called.");
|
||||||
{
|
};
|
||||||
std::unique_lock<std::mutex> guard{ lock };
|
auto callback = [&](const auto& endPoint) {
|
||||||
|
std::unique_lock<std::mutex> guard{lock};
|
||||||
endPointsDisconnected.emplace_back(endPoint);
|
endPointsDisconnected.emplace_back(endPoint);
|
||||||
cv.notify_one();
|
cv.notify_one();
|
||||||
};
|
};
|
||||||
|
|
||||||
auto client1 = std::make_shared<Interprocess::Connection::ConnectionMonitor>();
|
auto client1 =
|
||||||
|
std::make_shared<Interprocess::Connection::ConnectionMonitor>();
|
||||||
client1->Register(server->GetLocalEndPointInfo(), noOpCallback);
|
client1->Register(server->GetLocalEndPointInfo(), noOpCallback);
|
||||||
server->Register(client1->GetLocalEndPointInfo(), callback);
|
server->Register(client1->GetLocalEndPointInfo(), callback);
|
||||||
|
|
||||||
// Registering the same end point is not allowed.
|
// Registering the same end point is not allowed.
|
||||||
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
||||||
server->Register(client1->GetLocalEndPointInfo(), noOpCallback); ,
|
server->Register(client1->GetLocalEndPointInfo(), noOpCallback);
|
||||||
"Duplicate end point found.");
|
, "Duplicate end point found.");
|
||||||
|
|
||||||
auto client2 = std::make_shared<Interprocess::Connection::ConnectionMonitor>();
|
auto client2 =
|
||||||
|
std::make_shared<Interprocess::Connection::ConnectionMonitor>();
|
||||||
client2->Register(server->GetLocalEndPointInfo(), callback);
|
client2->Register(server->GetLocalEndPointInfo(), callback);
|
||||||
server->Register(client2->GetLocalEndPointInfo(), noOpCallback);
|
server->Register(client2->GetLocalEndPointInfo(), noOpCallback);
|
||||||
|
|
||||||
auto client3 = std::make_shared<Interprocess::Connection::ConnectionMonitor>();
|
auto client3 =
|
||||||
|
std::make_shared<Interprocess::Connection::ConnectionMonitor>();
|
||||||
client3->Register(server->GetLocalEndPointInfo(), callback);
|
client3->Register(server->GetLocalEndPointInfo(), callback);
|
||||||
server->Register(client3->GetLocalEndPointInfo(), noOpCallback);
|
server->Register(client3->GetLocalEndPointInfo(), noOpCallback);
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ BOOST_AUTO_TEST_CASE(ConnectionMonitorTest)
|
||||||
auto client1EndPointInfo = client1->GetLocalEndPointInfo();
|
auto client1EndPointInfo = client1->GetLocalEndPointInfo();
|
||||||
client1.reset();
|
client1.reset();
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> guard{ lock };
|
std::unique_lock<std::mutex> guard{lock};
|
||||||
cv.wait(guard, [&] { return endPointsDisconnected.size() >= 1U; });
|
cv.wait(guard, [&] { return endPointsDisconnected.size() >= 1U; });
|
||||||
BOOST_REQUIRE_EQUAL(endPointsDisconnected.size(), 1U);
|
BOOST_REQUIRE_EQUAL(endPointsDisconnected.size(), 1U);
|
||||||
BOOST_CHECK(endPointsDisconnected[0] == client1EndPointInfo);
|
BOOST_CHECK(endPointsDisconnected[0] == client1EndPointInfo);
|
||||||
|
@ -59,11 +60,12 @@ BOOST_AUTO_TEST_CASE(ConnectionMonitorTest)
|
||||||
BOOST_CHECK_EQUAL(server->GetRemoteConnectionsCount(), 2U);
|
BOOST_CHECK_EQUAL(server->GetRemoteConnectionsCount(), 2U);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now kill server and check if both callbacks in client2 and client3 are called.
|
// Now kill server and check if both callbacks in client2 and client3 are
|
||||||
|
// called.
|
||||||
auto serverEndPointInfo = server->GetLocalEndPointInfo();
|
auto serverEndPointInfo = server->GetLocalEndPointInfo();
|
||||||
server.reset();
|
server.reset();
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> guard{ lock };
|
std::unique_lock<std::mutex> guard{lock};
|
||||||
cv.wait(guard, [&] { return endPointsDisconnected.size() >= 2U; });
|
cv.wait(guard, [&] { return endPointsDisconnected.size() >= 2U; });
|
||||||
BOOST_REQUIRE_EQUAL(endPointsDisconnected.size(), 2U);
|
BOOST_REQUIRE_EQUAL(endPointsDisconnected.size(), 2U);
|
||||||
BOOST_CHECK(endPointsDisconnected[0] == serverEndPointInfo);
|
BOOST_CHECK(endPointsDisconnected[0] == serverEndPointInfo);
|
||||||
|
|
|
@ -1,26 +1,23 @@
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
#include "Utils.h"
|
|
||||||
#include "L4/Epoch/EpochQueue.h"
|
|
||||||
#include "L4/Epoch/EpochActionManager.h"
|
#include "L4/Epoch/EpochActionManager.h"
|
||||||
|
#include "L4/Epoch/EpochQueue.h"
|
||||||
#include "L4/LocalMemory/EpochManager.h"
|
#include "L4/LocalMemory/EpochManager.h"
|
||||||
#include "L4/Log/PerfCounter.h"
|
#include "L4/Log/PerfCounter.h"
|
||||||
#include "L4/Utils/Lock.h"
|
#include "L4/Utils/Lock.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(EpochManagerTests)
|
BOOST_AUTO_TEST_SUITE(EpochManagerTests)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(EpochRefManagerTest)
|
BOOST_AUTO_TEST_CASE(EpochRefManagerTest) {
|
||||||
{
|
|
||||||
std::uint64_t currentEpochCounter = 5U;
|
std::uint64_t currentEpochCounter = 5U;
|
||||||
const std::uint32_t c_epochQueueSize = 100U;
|
const std::uint32_t c_epochQueueSize = 100U;
|
||||||
|
|
||||||
using EpochQueue = EpochQueue<
|
using EpochQueue =
|
||||||
boost::shared_lock_guard<L4::Utils::ReaderWriterLockSlim>,
|
EpochQueue<boost::shared_lock_guard<L4::Utils::ReaderWriterLockSlim>,
|
||||||
std::lock_guard<L4::Utils::ReaderWriterLockSlim>>;
|
std::lock_guard<L4::Utils::ReaderWriterLockSlim>>;
|
||||||
|
|
||||||
EpochQueue epochQueue(currentEpochCounter, c_epochQueueSize);
|
EpochQueue epochQueue(currentEpochCounter, c_epochQueueSize);
|
||||||
|
@ -32,7 +29,8 @@ BOOST_AUTO_TEST_CASE(EpochRefManagerTest)
|
||||||
|
|
||||||
BOOST_CHECK_EQUAL(epochManager.AddRef(), currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochManager.AddRef(), currentEpochCounter);
|
||||||
|
|
||||||
// Validate that a reference count is incremented at the current epoch counter.
|
// Validate that a reference count is incremented at the current epoch
|
||||||
|
// counter.
|
||||||
BOOST_CHECK_EQUAL(epochQueue.m_refCounts[currentEpochCounter], 1U);
|
BOOST_CHECK_EQUAL(epochQueue.m_refCounts[currentEpochCounter], 1U);
|
||||||
|
|
||||||
epochManager.RemoveRef(currentEpochCounter);
|
epochManager.RemoveRef(currentEpochCounter);
|
||||||
|
@ -40,29 +38,29 @@ BOOST_AUTO_TEST_CASE(EpochRefManagerTest)
|
||||||
// Validate that a reference count is back to 0.
|
// Validate that a reference count is back to 0.
|
||||||
BOOST_CHECK_EQUAL(epochQueue.m_refCounts[currentEpochCounter], 0U);
|
BOOST_CHECK_EQUAL(epochQueue.m_refCounts[currentEpochCounter], 0U);
|
||||||
|
|
||||||
// Decrementing a reference counter when it is already 0 will result in an exception.
|
// Decrementing a reference counter when it is already 0 will result in an
|
||||||
|
// exception.
|
||||||
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
||||||
epochManager.RemoveRef(currentEpochCounter);,
|
epochManager.RemoveRef(currentEpochCounter);
|
||||||
"Reference counter is invalid.");
|
, "Reference counter is invalid.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(EpochCounterManagerTest) {
|
||||||
BOOST_AUTO_TEST_CASE(EpochCounterManagerTest)
|
|
||||||
{
|
|
||||||
std::uint64_t currentEpochCounter = 0U;
|
std::uint64_t currentEpochCounter = 0U;
|
||||||
const std::uint32_t c_epochQueueSize = 100U;
|
const std::uint32_t c_epochQueueSize = 100U;
|
||||||
|
|
||||||
using EpochQueue = EpochQueue<
|
using EpochQueue =
|
||||||
boost::shared_lock_guard<L4::Utils::ReaderWriterLockSlim>,
|
EpochQueue<boost::shared_lock_guard<L4::Utils::ReaderWriterLockSlim>,
|
||||||
std::lock_guard<L4::Utils::ReaderWriterLockSlim>>;
|
std::lock_guard<L4::Utils::ReaderWriterLockSlim>>;
|
||||||
|
|
||||||
EpochQueue epochQueue(currentEpochCounter, c_epochQueueSize);
|
EpochQueue epochQueue(currentEpochCounter, c_epochQueueSize);
|
||||||
|
|
||||||
EpochCounterManager<EpochQueue> epochCounterManager(epochQueue);
|
EpochCounterManager<EpochQueue> epochCounterManager(epochQueue);
|
||||||
|
|
||||||
// If RemoveUnreferenceEpochCounters() is called when m_fonrtIndex and m_backIndex are
|
// If RemoveUnreferenceEpochCounters() is called when m_fonrtIndex and
|
||||||
// the same, it will just return either value.
|
// m_backIndex are the same, it will just return either value.
|
||||||
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(), currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(),
|
||||||
|
currentEpochCounter);
|
||||||
|
|
||||||
// Add two epoch counts.
|
// Add two epoch counts.
|
||||||
++currentEpochCounter;
|
++currentEpochCounter;
|
||||||
|
@ -76,7 +74,8 @@ BOOST_AUTO_TEST_CASE(EpochCounterManagerTest)
|
||||||
|
|
||||||
// Since the m_frontIndex's reference count was zero, it will be incremented
|
// Since the m_frontIndex's reference count was zero, it will be incremented
|
||||||
// all the way to currentEpochCounter.
|
// all the way to currentEpochCounter.
|
||||||
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(), currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(),
|
||||||
|
currentEpochCounter);
|
||||||
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter);
|
||||||
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
|
||||||
|
|
||||||
|
@ -86,9 +85,11 @@ BOOST_AUTO_TEST_CASE(EpochCounterManagerTest)
|
||||||
const auto epochCounterReferenced = epochRefManager.AddRef();
|
const auto epochCounterReferenced = epochRefManager.AddRef();
|
||||||
BOOST_CHECK_EQUAL(epochCounterReferenced, currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochCounterReferenced, currentEpochCounter);
|
||||||
|
|
||||||
// Calling RemoveUnreferenceEpochCounters() should just return currentEpochCounter
|
// Calling RemoveUnreferenceEpochCounters() should just return
|
||||||
// since m_frontIndex and m_backIndex is the same. (Not affected by adding a reference yet).
|
// currentEpochCounter since m_frontIndex and m_backIndex is the same. (Not
|
||||||
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(), currentEpochCounter);
|
// affected by adding a reference yet).
|
||||||
|
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(),
|
||||||
|
currentEpochCounter);
|
||||||
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter);
|
||||||
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
|
||||||
|
|
||||||
|
@ -96,25 +97,25 @@ BOOST_AUTO_TEST_CASE(EpochCounterManagerTest)
|
||||||
++currentEpochCounter;
|
++currentEpochCounter;
|
||||||
epochCounterManager.AddNewEpoch();
|
epochCounterManager.AddNewEpoch();
|
||||||
|
|
||||||
// Now RemoveUnreferenceEpochCounters() should return epochCounterReferenced because
|
// Now RemoveUnreferenceEpochCounters() should return epochCounterReferenced
|
||||||
// of the reference count.
|
// because of the reference count.
|
||||||
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(), epochCounterReferenced);
|
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(),
|
||||||
|
epochCounterReferenced);
|
||||||
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, epochCounterReferenced);
|
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, epochCounterReferenced);
|
||||||
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
|
||||||
|
|
||||||
// Remove the reference.
|
// Remove the reference.
|
||||||
epochRefManager.RemoveRef(epochCounterReferenced);
|
epochRefManager.RemoveRef(epochCounterReferenced);
|
||||||
|
|
||||||
// Now RemoveUnreferenceEpochCounters() should return currentEpochCounter and m_frontIndex
|
// Now RemoveUnreferenceEpochCounters() should return currentEpochCounter and
|
||||||
// should be in sync with m_backIndex.
|
// m_frontIndex should be in sync with m_backIndex.
|
||||||
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(), currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(),
|
||||||
|
currentEpochCounter);
|
||||||
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter);
|
||||||
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
|
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(EpochActionManagerTest) {
|
||||||
BOOST_AUTO_TEST_CASE(EpochActionManagerTest)
|
|
||||||
{
|
|
||||||
EpochActionManager actionManager(2U);
|
EpochActionManager actionManager(2U);
|
||||||
|
|
||||||
bool isAction1Called = false;
|
bool isAction1Called = false;
|
||||||
|
@ -142,15 +143,12 @@ BOOST_AUTO_TEST_CASE(EpochActionManagerTest)
|
||||||
BOOST_CHECK(isAction1Called && isAction2Called);
|
BOOST_CHECK(isAction1Called && isAction2Called);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(EpochManagerTest) {
|
||||||
BOOST_AUTO_TEST_CASE(EpochManagerTest)
|
|
||||||
{
|
|
||||||
ServerPerfData perfData;
|
ServerPerfData perfData;
|
||||||
LocalMemory::EpochManager epochManager(
|
LocalMemory::EpochManager epochManager(
|
||||||
EpochManagerConfig(100000U, std::chrono::milliseconds(5U), 1U),
|
EpochManagerConfig(100000U, std::chrono::milliseconds(5U), 1U), perfData);
|
||||||
perfData);
|
|
||||||
|
|
||||||
std::atomic<bool> isActionCalled{ false };
|
std::atomic<bool> isActionCalled{false};
|
||||||
auto action = [&]() { isActionCalled = true; };
|
auto action = [&]() { isActionCalled = true; };
|
||||||
|
|
||||||
auto epochCounterReferenced = epochManager.GetEpochRefManager().AddRef();
|
auto epochCounterReferenced = epochManager.GetEpochRefManager().AddRef();
|
||||||
|
@ -158,13 +156,16 @@ BOOST_AUTO_TEST_CASE(EpochManagerTest)
|
||||||
epochManager.RegisterAction(action);
|
epochManager.RegisterAction(action);
|
||||||
|
|
||||||
// Justification for using sleep_for in unit tests:
|
// Justification for using sleep_for in unit tests:
|
||||||
// - EpochManager already uses an internal thread which wakes up and perform a task
|
// - EpochManager already uses an internal thread which wakes up and perform a
|
||||||
// in a given interval and when the class is destroyed, there is a mechanism for
|
// task in a given interval and when the class is destroyed, there is a
|
||||||
// waiting for the thread anyway. It's more crucial to test the end to end scenario this way.
|
// mechanism for waiting for the thread anyway. It's more crucial to test the
|
||||||
|
// end to end scenario this way.
|
||||||
// - The overall execution time for this test is less than 50 milliseconds.
|
// - The overall execution time for this test is less than 50 milliseconds.
|
||||||
auto initialEpochCounter = perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue);
|
auto initialEpochCounter =
|
||||||
while (perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue) - initialEpochCounter < 2)
|
perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue);
|
||||||
{
|
while (perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue) -
|
||||||
|
initialEpochCounter <
|
||||||
|
2) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,9 +173,11 @@ BOOST_AUTO_TEST_CASE(EpochManagerTest)
|
||||||
|
|
||||||
epochManager.GetEpochRefManager().RemoveRef(epochCounterReferenced);
|
epochManager.GetEpochRefManager().RemoveRef(epochCounterReferenced);
|
||||||
|
|
||||||
initialEpochCounter = perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue);
|
initialEpochCounter =
|
||||||
while (perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue) - initialEpochCounter < 2)
|
perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue);
|
||||||
{
|
while (perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue) -
|
||||||
|
initialEpochCounter <
|
||||||
|
2) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
#include "Utils.h"
|
|
||||||
#include "Mocks.h"
|
|
||||||
#include "L4/HashTable/Config.h"
|
#include "L4/HashTable/Config.h"
|
||||||
#include "L4/HashTable/IHashTable.h"
|
#include "L4/HashTable/IHashTable.h"
|
||||||
#include "L4/LocalMemory/HashTableManager.h"
|
#include "L4/LocalMemory/HashTableManager.h"
|
||||||
|
#include "Mocks.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
class HashTableManagerTestsFixture
|
class HashTableManagerTestsFixture {
|
||||||
{
|
protected:
|
||||||
protected:
|
|
||||||
template <typename Store>
|
template <typename Store>
|
||||||
void ValidateRecord(
|
void ValidateRecord(const Store& store,
|
||||||
const Store& store,
|
|
||||||
const char* expectedKeyStr,
|
const char* expectedKeyStr,
|
||||||
const char* expectedValueStr)
|
const char* expectedValueStr) {
|
||||||
{
|
|
||||||
IReadOnlyHashTable::Value actualValue;
|
IReadOnlyHashTable::Value actualValue;
|
||||||
auto expectedValue = Utils::ConvertFromString<IReadOnlyHashTable::Value>(expectedValueStr);
|
auto expectedValue =
|
||||||
BOOST_CHECK(store.Get(Utils::ConvertFromString<IReadOnlyHashTable::Key>(expectedKeyStr), actualValue));
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(expectedValueStr);
|
||||||
|
BOOST_CHECK(store.Get(
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(expectedKeyStr),
|
||||||
|
actualValue));
|
||||||
BOOST_CHECK(actualValue.m_size == expectedValue.m_size);
|
BOOST_CHECK(actualValue.m_size == expectedValue.m_size);
|
||||||
BOOST_CHECK(!memcmp(actualValue.m_data, expectedValue.m_data, expectedValue.m_size));
|
BOOST_CHECK(!memcmp(actualValue.m_data, expectedValue.m_data,
|
||||||
|
expectedValue.m_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
MockEpochManager m_epochManager;
|
MockEpochManager m_epochManager;
|
||||||
|
@ -32,17 +31,14 @@ protected:
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_SUITE(HashTableManagerTests, HashTableManagerTestsFixture)
|
BOOST_FIXTURE_TEST_SUITE(HashTableManagerTests, HashTableManagerTestsFixture)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(HashTableManagerTest)
|
BOOST_AUTO_TEST_CASE(HashTableManagerTest) {
|
||||||
{
|
|
||||||
LocalMemory::HashTableManager htManager;
|
LocalMemory::HashTableManager htManager;
|
||||||
const auto ht1Index = htManager.Add(
|
const auto ht1Index = htManager.Add(
|
||||||
HashTableConfig("HashTable1", HashTableConfig::Setting(100U)),
|
HashTableConfig("HashTable1", HashTableConfig::Setting(100U)),
|
||||||
m_epochManager,
|
m_epochManager, m_allocator);
|
||||||
m_allocator);
|
|
||||||
const auto ht2Index = htManager.Add(
|
const auto ht2Index = htManager.Add(
|
||||||
HashTableConfig("HashTable2", HashTableConfig::Setting(200U)),
|
HashTableConfig("HashTable2", HashTableConfig::Setting(200U)),
|
||||||
m_epochManager,
|
m_epochManager, m_allocator);
|
||||||
m_allocator);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto& hashTable1 = htManager.GetHashTable("HashTable1");
|
auto& hashTable1 = htManager.GetHashTable("HashTable1");
|
||||||
|
@ -56,29 +52,20 @@ BOOST_AUTO_TEST_CASE(HashTableManagerTest)
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Value>("HashTable2Value"));
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>("HashTable2Value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateRecord(
|
ValidateRecord(htManager.GetHashTable(ht1Index), "HashTable1Key",
|
||||||
htManager.GetHashTable(ht1Index),
|
|
||||||
"HashTable1Key",
|
|
||||||
"HashTable1Value");
|
"HashTable1Value");
|
||||||
|
|
||||||
ValidateRecord(
|
ValidateRecord(htManager.GetHashTable(ht2Index), "HashTable2Key",
|
||||||
htManager.GetHashTable(ht2Index),
|
|
||||||
"HashTable2Key",
|
|
||||||
"HashTable2Value");
|
"HashTable2Value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(HashTableManagerTestForSerialzation) {
|
||||||
BOOST_AUTO_TEST_CASE(HashTableManagerTestForSerialzation)
|
HashTableConfig htConfig{"HashTable1", HashTableConfig::Setting(100U)};
|
||||||
{
|
|
||||||
HashTableConfig htConfig{ "HashTable1", HashTableConfig::Setting(100U) };
|
|
||||||
std::ostringstream outStream;
|
std::ostringstream outStream;
|
||||||
|
|
||||||
std::vector<std::pair<std::string, std::string>> testData;
|
std::vector<std::pair<std::string, std::string>> testData;
|
||||||
for (std::int32_t i = 0; i < 10; ++i)
|
for (std::int32_t i = 0; i < 10; ++i) {
|
||||||
{
|
testData.emplace_back("key" + std::to_string(i), "val" + std::to_string(i));
|
||||||
testData.emplace_back(
|
|
||||||
"key" + std::to_string(i),
|
|
||||||
"val" + std::to_string(i));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize a hash table.
|
// Serialize a hash table.
|
||||||
|
@ -88,11 +75,11 @@ BOOST_AUTO_TEST_CASE(HashTableManagerTestForSerialzation)
|
||||||
|
|
||||||
auto& hashTable1 = htManager.GetHashTable("HashTable1");
|
auto& hashTable1 = htManager.GetHashTable("HashTable1");
|
||||||
|
|
||||||
for (const auto& kvPair : testData)
|
for (const auto& kvPair : testData) {
|
||||||
{
|
hashTable1.Add(Utils::ConvertFromString<IReadOnlyHashTable::Key>(
|
||||||
hashTable1.Add(
|
kvPair.first.c_str()),
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Key>(kvPair.first.c_str()),
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Value>(kvPair.second.c_str()));
|
kvPair.second.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto serializer = hashTable1.GetSerializer();
|
auto serializer = hashTable1.GetSerializer();
|
||||||
|
@ -112,12 +99,8 @@ BOOST_AUTO_TEST_CASE(HashTableManagerTestForSerialzation)
|
||||||
hashTable1.GetPerfData().Get(HashTablePerfCounter::RecordsCount),
|
hashTable1.GetPerfData().Get(HashTablePerfCounter::RecordsCount),
|
||||||
testData.size());
|
testData.size());
|
||||||
|
|
||||||
for (const auto& kvPair : testData)
|
for (const auto& kvPair : testData) {
|
||||||
{
|
ValidateRecord(hashTable1, kvPair.first.c_str(), kvPair.second.c_str());
|
||||||
ValidateRecord(
|
|
||||||
hashTable1,
|
|
||||||
kvPair.first.c_str(),
|
|
||||||
kvPair.second.c_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +1,60 @@
|
||||||
#include <boost/test/unit_test.hpp>
|
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "L4/HashTable/Common/Record.h"
|
#include "L4/HashTable/Common/Record.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
using namespace HashTable;
|
using namespace HashTable;
|
||||||
|
|
||||||
class HashTableRecordTestFixture
|
class HashTableRecordTestFixture {
|
||||||
{
|
protected:
|
||||||
protected:
|
void Run(bool isFixedKey, bool isFixedValue, bool useMetaValue) {
|
||||||
void Run(bool isFixedKey, bool isFixedValue, bool useMetaValue)
|
BOOST_TEST_MESSAGE("Running with isFixedKey="
|
||||||
{
|
<< isFixedKey << ", isFixedValue=" << isFixedValue
|
||||||
BOOST_TEST_MESSAGE(
|
|
||||||
"Running with isFixedKey=" << isFixedKey
|
|
||||||
<< ", isFixedValue=" << isFixedValue
|
|
||||||
<< ", useMetatValue=" << useMetaValue);
|
<< ", useMetatValue=" << useMetaValue);
|
||||||
|
|
||||||
const std::string key = "TestKey";
|
const std::string key = "TestKey";
|
||||||
const std::string value = "TestValue";
|
const std::string value = "TestValue";
|
||||||
const std::string metaValue = "TestMetavalue";
|
const std::string metaValue = "TestMetavalue";
|
||||||
|
|
||||||
const auto recordOverhead = (isFixedKey ? 0U : c_keyTypeSize) + (isFixedValue ? 0U : c_valueTypeSize);
|
const auto recordOverhead = (isFixedKey ? 0U : c_keyTypeSize) +
|
||||||
|
(isFixedValue ? 0U : c_valueTypeSize);
|
||||||
|
|
||||||
Validate(
|
Validate(
|
||||||
RecordSerializer{
|
RecordSerializer{
|
||||||
isFixedKey ? static_cast<RecordSerializer::KeySize>(key.size()) : std::uint16_t(0),
|
isFixedKey ? static_cast<RecordSerializer::KeySize>(key.size())
|
||||||
isFixedValue ? static_cast<RecordSerializer::ValueSize>(value.size()) : 0U,
|
: std::uint16_t(0),
|
||||||
useMetaValue ? static_cast<RecordSerializer::ValueSize>(metaValue.size()) : 0U },
|
isFixedValue
|
||||||
key,
|
? static_cast<RecordSerializer::ValueSize>(value.size())
|
||||||
value,
|
: 0U,
|
||||||
recordOverhead + key.size() + value.size() + (useMetaValue ? metaValue.size() : 0U),
|
useMetaValue
|
||||||
|
? static_cast<RecordSerializer::ValueSize>(metaValue.size())
|
||||||
|
: 0U},
|
||||||
|
key, value,
|
||||||
|
recordOverhead + key.size() + value.size() +
|
||||||
|
(useMetaValue ? metaValue.size() : 0U),
|
||||||
recordOverhead,
|
recordOverhead,
|
||||||
useMetaValue ? boost::optional<const std::string&>{ metaValue } : boost::none);
|
useMetaValue ? boost::optional<const std::string&>{metaValue}
|
||||||
|
: boost::none);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Validate(
|
void Validate(const RecordSerializer& serializer,
|
||||||
const RecordSerializer& serializer,
|
|
||||||
const std::string& keyStr,
|
const std::string& keyStr,
|
||||||
const std::string& valueStr,
|
const std::string& valueStr,
|
||||||
std::size_t expectedBufferSize,
|
std::size_t expectedBufferSize,
|
||||||
std::size_t expectedRecordOverheadSize,
|
std::size_t expectedRecordOverheadSize,
|
||||||
boost::optional<const std::string&> metadataStr = boost::none)
|
boost::optional<const std::string&> metadataStr = boost::none) {
|
||||||
{
|
BOOST_CHECK_EQUAL(serializer.CalculateRecordOverhead(),
|
||||||
BOOST_CHECK_EQUAL(serializer.CalculateRecordOverhead(), expectedRecordOverheadSize);
|
expectedRecordOverheadSize);
|
||||||
|
|
||||||
const auto key = Utils::ConvertFromString<Record::Key>(keyStr.c_str());
|
const auto key = Utils::ConvertFromString<Record::Key>(keyStr.c_str());
|
||||||
const auto value = Utils::ConvertFromString<Record::Value>(valueStr.c_str());
|
const auto value =
|
||||||
|
Utils::ConvertFromString<Record::Value>(valueStr.c_str());
|
||||||
|
|
||||||
const auto bufferSize = serializer.CalculateBufferSize(key, value);
|
const auto bufferSize = serializer.CalculateBufferSize(key, value);
|
||||||
|
|
||||||
|
@ -61,14 +63,14 @@ private:
|
||||||
|
|
||||||
RecordBuffer* recordBuffer = nullptr;
|
RecordBuffer* recordBuffer = nullptr;
|
||||||
|
|
||||||
if (metadataStr)
|
if (metadataStr) {
|
||||||
{
|
auto metaValue =
|
||||||
auto metaValue = Utils::ConvertFromString<Record::Value>(metadataStr->c_str());
|
Utils::ConvertFromString<Record::Value>(metadataStr->c_str());
|
||||||
recordBuffer = serializer.Serialize(key, value, metaValue, buffer.data(), bufferSize);
|
recordBuffer = serializer.Serialize(key, value, metaValue, buffer.data(),
|
||||||
}
|
bufferSize);
|
||||||
else
|
} else {
|
||||||
{
|
recordBuffer =
|
||||||
recordBuffer = serializer.Serialize(key, value, buffer.data(), bufferSize);
|
serializer.Serialize(key, value, buffer.data(), bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto record = serializer.Deserialize(*recordBuffer);
|
const auto record = serializer.Deserialize(*recordBuffer);
|
||||||
|
@ -78,42 +80,34 @@ private:
|
||||||
BOOST_CHECK(record.m_value.m_data != value.m_data);
|
BOOST_CHECK(record.m_value.m_data != value.m_data);
|
||||||
|
|
||||||
BOOST_CHECK(record.m_key == key);
|
BOOST_CHECK(record.m_key == key);
|
||||||
if (metadataStr)
|
if (metadataStr) {
|
||||||
{
|
|
||||||
const std::string newValueStr = *metadataStr + valueStr;
|
const std::string newValueStr = *metadataStr + valueStr;
|
||||||
const auto newValue = Utils::ConvertFromString<Record::Value>(newValueStr.c_str());
|
const auto newValue =
|
||||||
|
Utils::ConvertFromString<Record::Value>(newValueStr.c_str());
|
||||||
BOOST_CHECK(record.m_value == newValue);
|
BOOST_CHECK(record.m_value == newValue);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
BOOST_CHECK(record.m_value == value);
|
BOOST_CHECK(record.m_value == value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr std::size_t c_keyTypeSize = sizeof(Record::Key::size_type);
|
static constexpr std::size_t c_keyTypeSize = sizeof(Record::Key::size_type);
|
||||||
static constexpr std::size_t c_valueTypeSize = sizeof(Record::Value::size_type);
|
static constexpr std::size_t c_valueTypeSize =
|
||||||
|
sizeof(Record::Value::size_type);
|
||||||
};
|
};
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_SUITE(HashTableRecordTests, HashTableRecordTestFixture)
|
BOOST_FIXTURE_TEST_SUITE(HashTableRecordTests, HashTableRecordTestFixture)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(RunAll)
|
BOOST_AUTO_TEST_CASE(RunAll) {
|
||||||
{
|
|
||||||
// Run all permutations for Run(), which takes three booleans.
|
// Run all permutations for Run(), which takes three booleans.
|
||||||
for (int i = 0; i < 8; ++i)
|
for (int i = 0; i < 8; ++i) {
|
||||||
{
|
Run(!!((i >> 2) & 1), !!((i >> 1) & 1), !!((i)&1));
|
||||||
Run(
|
|
||||||
!!((i >> 2) & 1),
|
|
||||||
!!((i >> 1) & 1),
|
|
||||||
!!((i) & 1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(InvalidSizeTest) {
|
||||||
BOOST_AUTO_TEST_CASE(InvalidSizeTest)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> buffer(100U);
|
std::vector<std::uint8_t> buffer(100U);
|
||||||
|
|
||||||
RecordSerializer serializer{ 4, 5 };
|
RecordSerializer serializer{4, 5};
|
||||||
|
|
||||||
const std::string keyStr = "1234";
|
const std::string keyStr = "1234";
|
||||||
const std::string invalidStr = "999999";
|
const std::string invalidStr = "999999";
|
||||||
|
@ -122,8 +116,10 @@ BOOST_AUTO_TEST_CASE(InvalidSizeTest)
|
||||||
const auto key = Utils::ConvertFromString<Record::Key>(keyStr.c_str());
|
const auto key = Utils::ConvertFromString<Record::Key>(keyStr.c_str());
|
||||||
const auto value = Utils::ConvertFromString<Record::Value>(valueStr.c_str());
|
const auto value = Utils::ConvertFromString<Record::Value>(valueStr.c_str());
|
||||||
|
|
||||||
const auto invalidKey = Utils::ConvertFromString<Record::Key>(invalidStr.c_str());
|
const auto invalidKey =
|
||||||
const auto invalidValue = Utils::ConvertFromString<Record::Value>(invalidStr.c_str());
|
Utils::ConvertFromString<Record::Key>(invalidStr.c_str());
|
||||||
|
const auto invalidValue =
|
||||||
|
Utils::ConvertFromString<Record::Value>(invalidStr.c_str());
|
||||||
|
|
||||||
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
||||||
serializer.Serialize(invalidKey, value, buffer.data(), buffer.size()),
|
serializer.Serialize(invalidKey, value, buffer.data(), buffer.size()),
|
||||||
|
@ -134,26 +130,28 @@ BOOST_AUTO_TEST_CASE(InvalidSizeTest)
|
||||||
"Invalid key or value sizes are given.");
|
"Invalid key or value sizes are given.");
|
||||||
|
|
||||||
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
||||||
serializer.Serialize(invalidKey, invalidValue, buffer.data(), buffer.size()),
|
serializer.Serialize(invalidKey, invalidValue, buffer.data(),
|
||||||
|
buffer.size()),
|
||||||
"Invalid key or value sizes are given.");
|
"Invalid key or value sizes are given.");
|
||||||
|
|
||||||
// Normal case shouldn't thrown an exception.
|
// Normal case shouldn't thrown an exception.
|
||||||
serializer.Serialize(key, value, buffer.data(), buffer.size());
|
serializer.Serialize(key, value, buffer.data(), buffer.size());
|
||||||
|
|
||||||
RecordSerializer serializerWithMetaValue{ 4, 5, 2 };
|
RecordSerializer serializerWithMetaValue{4, 5, 2};
|
||||||
std::uint16_t metadata = 0;
|
std::uint16_t metadata = 0;
|
||||||
|
|
||||||
Record::Value metaValue{
|
Record::Value metaValue{reinterpret_cast<std::uint8_t*>(&metadata),
|
||||||
reinterpret_cast<std::uint8_t*>(&metadata),
|
sizeof(metadata)};
|
||||||
sizeof(metadata) };
|
|
||||||
|
|
||||||
// Normal case shouldn't thrown an exception.
|
// Normal case shouldn't thrown an exception.
|
||||||
serializerWithMetaValue.Serialize(key, value, metaValue, buffer.data(), buffer.size());
|
serializerWithMetaValue.Serialize(key, value, metaValue, buffer.data(),
|
||||||
|
buffer.size());
|
||||||
|
|
||||||
// Mismatching size is given.
|
// Mismatching size is given.
|
||||||
metaValue.m_size = 1;
|
metaValue.m_size = 1;
|
||||||
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
||||||
serializerWithMetaValue.Serialize(key, value, metaValue, buffer.data(), buffer.size()),
|
serializerWithMetaValue.Serialize(key, value, metaValue, buffer.data(),
|
||||||
|
buffer.size()),
|
||||||
"Invalid meta value size is given.");
|
"Invalid meta value size is given.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,44 +1,38 @@
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "L4/LocalMemory/HashTableService.h"
|
||||||
#include "Mocks.h"
|
#include "Mocks.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "L4/LocalMemory/HashTableService.h"
|
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(HashTableServiceTest)
|
BOOST_AUTO_TEST_CASE(HashTableServiceTest) {
|
||||||
{
|
|
||||||
std::vector<std::pair<std::string, std::string>> dataSet;
|
std::vector<std::pair<std::string, std::string>> dataSet;
|
||||||
for (std::uint16_t i = 0U; i < 100; ++i)
|
for (std::uint16_t i = 0U; i < 100; ++i) {
|
||||||
{
|
dataSet.emplace_back("key" + std::to_string(i),
|
||||||
dataSet.emplace_back("key" + std::to_string(i), "value" + std::to_string(i));
|
"value" + std::to_string(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalMemory::HashTableService htService;
|
LocalMemory::HashTableService htService;
|
||||||
htService.AddHashTable(
|
htService.AddHashTable(
|
||||||
HashTableConfig("Table1", HashTableConfig::Setting{ 100U }));
|
HashTableConfig("Table1", HashTableConfig::Setting{100U}));
|
||||||
htService.AddHashTable(
|
htService.AddHashTable(HashTableConfig(
|
||||||
HashTableConfig(
|
"Table2", HashTableConfig::Setting{1000U},
|
||||||
"Table2",
|
HashTableConfig::Cache{1024, std::chrono::seconds{1U}, false}));
|
||||||
HashTableConfig::Setting{ 1000U },
|
|
||||||
HashTableConfig::Cache{ 1024, std::chrono::seconds{ 1U }, false }));
|
|
||||||
|
|
||||||
for (const auto& data : dataSet)
|
for (const auto& data : dataSet) {
|
||||||
{
|
|
||||||
htService.GetContext()["Table1"].Add(
|
htService.GetContext()["Table1"].Add(
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Key>(data.first.c_str()),
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(data.first.c_str()),
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Value>(data.second.c_str()));
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(
|
||||||
|
data.second.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Smoke tests for looking up the data .
|
// Smoke tests for looking up the data .
|
||||||
{
|
{
|
||||||
auto context = htService.GetContext();
|
auto context = htService.GetContext();
|
||||||
for (const auto& data : dataSet)
|
for (const auto& data : dataSet) {
|
||||||
{
|
|
||||||
IReadOnlyHashTable::Value val;
|
IReadOnlyHashTable::Value val;
|
||||||
BOOST_CHECK(context["Table1"].Get(
|
BOOST_CHECK(context["Table1"].Get(
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Key>(data.first.c_str()),
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(data.first.c_str()),
|
||||||
|
|
|
@ -3,28 +3,17 @@
|
||||||
#include "L4/Epoch/IEpochActionManager.h"
|
#include "L4/Epoch/IEpochActionManager.h"
|
||||||
#include "L4/Log/PerfLogger.h"
|
#include "L4/Log/PerfLogger.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
class MockPerfLogger : public IPerfLogger
|
class MockPerfLogger : public IPerfLogger {
|
||||||
{
|
virtual void Log(const IData& data) override { (void)data; }
|
||||||
virtual void Log(const IData& data) override
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MockEpochManager : public IEpochActionManager
|
struct MockEpochManager : public IEpochActionManager {
|
||||||
{
|
MockEpochManager() : m_numRegisterActionsCalled(0) {}
|
||||||
MockEpochManager()
|
|
||||||
: m_numRegisterActionsCalled(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void RegisterAction(Action&& action) override
|
virtual void RegisterAction(Action&& action) override {
|
||||||
{
|
|
||||||
++m_numRegisterActionsCalled;
|
++m_numRegisterActionsCalled;
|
||||||
action();
|
action();
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,26 +2,19 @@
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include "L4/Log/PerfLogger.h"
|
#include "L4/Log/PerfLogger.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
void CheckMinCounters(const HashTablePerfData& htPerfData)
|
void CheckMinCounters(const HashTablePerfData& htPerfData) {
|
||||||
{
|
|
||||||
const auto maxValue = (std::numeric_limits<std::int64_t>::max)();
|
const auto maxValue = (std::numeric_limits<std::int64_t>::max)();
|
||||||
/// Check if the min counter values are correctly initialized to max value.
|
/// Check if the min counter values are correctly initialized to max value.
|
||||||
BOOST_CHECK_EQUAL(htPerfData.Get(HashTablePerfCounter::MinValueSize), maxValue);
|
BOOST_CHECK_EQUAL(htPerfData.Get(HashTablePerfCounter::MinValueSize),
|
||||||
|
maxValue);
|
||||||
BOOST_CHECK_EQUAL(htPerfData.Get(HashTablePerfCounter::MinKeySize), maxValue);
|
BOOST_CHECK_EQUAL(htPerfData.Get(HashTablePerfCounter::MinKeySize), maxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(PerfCountersTest)
|
BOOST_AUTO_TEST_CASE(PerfCountersTest) {
|
||||||
{
|
enum class TestCounter { Counter = 0, Count };
|
||||||
enum class TestCounter
|
|
||||||
{
|
|
||||||
Counter = 0,
|
|
||||||
Count
|
|
||||||
};
|
|
||||||
|
|
||||||
PerfCounters<TestCounter> perfCounters;
|
PerfCounters<TestCounter> perfCounters;
|
||||||
|
|
||||||
|
@ -55,9 +48,7 @@ BOOST_AUTO_TEST_CASE(PerfCountersTest)
|
||||||
BOOST_CHECK_EQUAL(perfCounters.Get(TestCounter::Counter), 1);
|
BOOST_CHECK_EQUAL(perfCounters.Get(TestCounter::Counter), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(PerfDataTest) {
|
||||||
BOOST_AUTO_TEST_CASE(PerfDataTest)
|
|
||||||
{
|
|
||||||
PerfData testPerfData;
|
PerfData testPerfData;
|
||||||
|
|
||||||
BOOST_CHECK(testPerfData.GetHashTablesPerfData().empty());
|
BOOST_CHECK(testPerfData.GetHashTablesPerfData().empty());
|
||||||
|
@ -86,17 +77,20 @@ BOOST_AUTO_TEST_CASE(PerfDataTest)
|
||||||
{
|
{
|
||||||
auto htPerfDataIt = hashTablesPerfData.find("HT1");
|
auto htPerfDataIt = hashTablesPerfData.find("HT1");
|
||||||
BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end());
|
BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end());
|
||||||
BOOST_CHECK_EQUAL(htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 10);
|
BOOST_CHECK_EQUAL(
|
||||||
|
htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 10);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto htPerfDataIt = hashTablesPerfData.find("HT2");
|
auto htPerfDataIt = hashTablesPerfData.find("HT2");
|
||||||
BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end());
|
BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end());
|
||||||
BOOST_CHECK_EQUAL(htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 20);
|
BOOST_CHECK_EQUAL(
|
||||||
|
htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 20);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto htPerfDataIt = hashTablesPerfData.find("HT3");
|
auto htPerfDataIt = hashTablesPerfData.find("HT3");
|
||||||
BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end());
|
BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end());
|
||||||
BOOST_CHECK_EQUAL(htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 30);
|
BOOST_CHECK_EQUAL(
|
||||||
|
htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
#include <string>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "Utils.h"
|
|
||||||
#include "Mocks.h"
|
|
||||||
#include "L4/HashTable/ReadWrite/HashTable.h"
|
#include "L4/HashTable/ReadWrite/HashTable.h"
|
||||||
#include "L4/HashTable/ReadWrite/Serializer.h"
|
#include "L4/HashTable/ReadWrite/Serializer.h"
|
||||||
#include "L4/Log/PerfCounter.h"
|
|
||||||
#include "L4/LocalMemory/Memory.h"
|
#include "L4/LocalMemory/Memory.h"
|
||||||
|
#include "L4/Log/PerfCounter.h"
|
||||||
|
#include "Mocks.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
using namespace HashTable::ReadWrite;
|
using namespace HashTable::ReadWrite;
|
||||||
|
|
||||||
|
@ -32,23 +30,24 @@ void ValidateSerializer(
|
||||||
const KeyValuePairs& keyValuePairs,
|
const KeyValuePairs& keyValuePairs,
|
||||||
const Utils::ExpectedCounterValues& expectedCounterValuesAfterLoad,
|
const Utils::ExpectedCounterValues& expectedCounterValuesAfterLoad,
|
||||||
const Utils::ExpectedCounterValues& expectedCounterValuesAfterSerialization,
|
const Utils::ExpectedCounterValues& expectedCounterValuesAfterSerialization,
|
||||||
const Utils::ExpectedCounterValues& expectedCounterValuesAfterDeserialization)
|
const Utils::ExpectedCounterValues&
|
||||||
{
|
expectedCounterValuesAfterDeserialization) {
|
||||||
Memory memory;
|
Memory memory;
|
||||||
MockEpochManager epochManager;
|
MockEpochManager epochManager;
|
||||||
|
|
||||||
auto hashTableHolder{
|
auto hashTableHolder{memory.MakeUnique<HashTable>(HashTable::Setting{5},
|
||||||
memory.MakeUnique<HashTable>(
|
memory.GetAllocator())};
|
||||||
HashTable::Setting{ 5 }, memory.GetAllocator()) };
|
|
||||||
BOOST_CHECK(hashTableHolder != nullptr);
|
BOOST_CHECK(hashTableHolder != nullptr);
|
||||||
|
|
||||||
WritableHashTable<Allocator> writableHashTable(*hashTableHolder, epochManager);
|
WritableHashTable<Allocator> writableHashTable(*hashTableHolder,
|
||||||
|
epochManager);
|
||||||
|
|
||||||
// Insert the given key/value pairs to the hash table.
|
// Insert the given key/value pairs to the hash table.
|
||||||
for (const auto& pair : keyValuePairs)
|
for (const auto& pair : keyValuePairs) {
|
||||||
{
|
auto key =
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(pair.first.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(pair.first.c_str());
|
||||||
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(pair.second.c_str());
|
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(
|
||||||
|
pair.second.c_str());
|
||||||
|
|
||||||
writableHashTable.Add(key, val);
|
writableHashTable.Add(key, val);
|
||||||
}
|
}
|
||||||
|
@ -65,112 +64,90 @@ void ValidateSerializer(
|
||||||
// Read in the hash table from the stream and validate it.
|
// Read in the hash table from the stream and validate it.
|
||||||
std::istringstream inStream(outStream.str());
|
std::istringstream inStream(outStream.str());
|
||||||
|
|
||||||
// version == 0 means that it's run through the HashTableSerializer, thus the following can be skipped.
|
// version == 0 means that it's run through the HashTableSerializer, thus the
|
||||||
if (serializerVersion != 0)
|
// following can be skipped.
|
||||||
{
|
if (serializerVersion != 0) {
|
||||||
std::uint8_t actualSerializerVersion = 0;
|
std::uint8_t actualSerializerVersion = 0;
|
||||||
DeserializerHelper(inStream).Deserialize(actualSerializerVersion);
|
DeserializerHelper(inStream).Deserialize(actualSerializerVersion);
|
||||||
BOOST_CHECK(actualSerializerVersion == serializerVersion);
|
BOOST_CHECK(actualSerializerVersion == serializerVersion);
|
||||||
}
|
} else {
|
||||||
else
|
BOOST_REQUIRE(typeid(L4::HashTable::ReadWrite::Serializer<
|
||||||
{
|
HashTable, ReadOnlyHashTable>) == typeid(Serializer));
|
||||||
BOOST_REQUIRE(typeid(L4::HashTable::ReadWrite::Serializer<HashTable, ReadOnlyHashTable>) == typeid(Serializer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto newHashTableHolder = deserializer.Deserialize(memory, inStream);
|
auto newHashTableHolder = deserializer.Deserialize(memory, inStream);
|
||||||
BOOST_CHECK(newHashTableHolder != nullptr);
|
BOOST_CHECK(newHashTableHolder != nullptr);
|
||||||
|
|
||||||
WritableHashTable<Allocator> newWritableHashTable(*newHashTableHolder, epochManager);
|
WritableHashTable<Allocator> newWritableHashTable(*newHashTableHolder,
|
||||||
|
epochManager);
|
||||||
|
|
||||||
const auto& newPerfData = newWritableHashTable.GetPerfData();
|
const auto& newPerfData = newWritableHashTable.GetPerfData();
|
||||||
|
|
||||||
Utils::ValidateCounters(newPerfData, expectedCounterValuesAfterDeserialization);
|
Utils::ValidateCounters(newPerfData,
|
||||||
|
expectedCounterValuesAfterDeserialization);
|
||||||
|
|
||||||
// Make sure all the key/value pairs exist after deserialization.
|
// Make sure all the key/value pairs exist after deserialization.
|
||||||
for (const auto& pair : keyValuePairs)
|
for (const auto& pair : keyValuePairs) {
|
||||||
{
|
auto key =
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(pair.first.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(pair.first.c_str());
|
||||||
IReadOnlyHashTable::Value val;
|
IReadOnlyHashTable::Value val;
|
||||||
BOOST_CHECK(newWritableHashTable.Get(key, val));
|
BOOST_CHECK(newWritableHashTable.Get(key, val));
|
||||||
BOOST_CHECK(Utils::ConvertToString(val) == pair.second);
|
BOOST_CHECK(Utils::ConvertToString(val) == pair.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(CurrentSerializerTest) {
|
||||||
BOOST_AUTO_TEST_CASE(CurrentSerializerTest)
|
|
||||||
{
|
|
||||||
ValidateSerializer(
|
ValidateSerializer(
|
||||||
Current::Serializer<HashTable, ReadOnlyHashTable>{},
|
Current::Serializer<HashTable, ReadOnlyHashTable>{},
|
||||||
Current::Deserializer<Memory, HashTable, WritableHashTable>{ L4::Utils::Properties{} },
|
Current::Deserializer<Memory, HashTable, WritableHashTable>{
|
||||||
|
L4::Utils::Properties{}},
|
||||||
Current::c_version,
|
Current::c_version,
|
||||||
{
|
{{"hello1", " world1"}, {"hello2", " world2"}, {"hello3", " world3"}},
|
||||||
{ "hello1", " world1" },
|
{{HashTablePerfCounter::RecordsCount, 3},
|
||||||
{ "hello2", " world2" },
|
{HashTablePerfCounter::BucketsCount, 5},
|
||||||
{ "hello3", " world3" }
|
{HashTablePerfCounter::TotalKeySize, 18},
|
||||||
},
|
{HashTablePerfCounter::TotalValueSize, 21},
|
||||||
{
|
{HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0},
|
||||||
{ HashTablePerfCounter::RecordsCount, 3 },
|
{HashTablePerfCounter::RecordsCountSavedFromSerializer, 0}},
|
||||||
{ HashTablePerfCounter::BucketsCount, 5 },
|
{{HashTablePerfCounter::RecordsCount, 3},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 18 },
|
{HashTablePerfCounter::BucketsCount, 5},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 21 },
|
{HashTablePerfCounter::TotalKeySize, 18},
|
||||||
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0 },
|
{HashTablePerfCounter::TotalValueSize, 21},
|
||||||
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 0 }
|
{HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0},
|
||||||
},
|
{HashTablePerfCounter::RecordsCountSavedFromSerializer, 3}},
|
||||||
{
|
{{HashTablePerfCounter::RecordsCount, 3},
|
||||||
{ HashTablePerfCounter::RecordsCount, 3 },
|
{HashTablePerfCounter::BucketsCount, 5},
|
||||||
{ HashTablePerfCounter::BucketsCount, 5 },
|
{HashTablePerfCounter::TotalKeySize, 18},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 18 },
|
{HashTablePerfCounter::TotalValueSize, 21},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 21 },
|
{HashTablePerfCounter::RecordsCountLoadedFromSerializer, 3},
|
||||||
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0 },
|
{HashTablePerfCounter::RecordsCountSavedFromSerializer, 0}});
|
||||||
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 3 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{ HashTablePerfCounter::RecordsCount, 3 },
|
|
||||||
{ HashTablePerfCounter::BucketsCount, 5 },
|
|
||||||
{ HashTablePerfCounter::TotalKeySize, 18 },
|
|
||||||
{ HashTablePerfCounter::TotalValueSize, 21 },
|
|
||||||
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 3 },
|
|
||||||
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 0 }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(HashTableSerializeTest) {
|
||||||
BOOST_AUTO_TEST_CASE(HashTableSerializeTest)
|
|
||||||
{
|
|
||||||
// This test case tests end to end scenario using the HashTableSerializer.
|
// This test case tests end to end scenario using the HashTableSerializer.
|
||||||
ValidateSerializer(
|
ValidateSerializer(
|
||||||
Serializer<HashTable, ReadOnlyHashTable>{},
|
Serializer<HashTable, ReadOnlyHashTable>{},
|
||||||
Deserializer<Memory, HashTable, WritableHashTable>{ L4::Utils::Properties{} },
|
Deserializer<Memory, HashTable, WritableHashTable>{
|
||||||
0U,
|
L4::Utils::Properties{}},
|
||||||
{
|
0U, {{"hello1", " world1"}, {"hello2", " world2"}, {"hello3", " world3"}},
|
||||||
{ "hello1", " world1" },
|
{{HashTablePerfCounter::RecordsCount, 3},
|
||||||
{ "hello2", " world2" },
|
{HashTablePerfCounter::BucketsCount, 5},
|
||||||
{ "hello3", " world3" }
|
{HashTablePerfCounter::TotalKeySize, 18},
|
||||||
},
|
{HashTablePerfCounter::TotalValueSize, 21},
|
||||||
{
|
{HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0},
|
||||||
{ HashTablePerfCounter::RecordsCount, 3 },
|
{HashTablePerfCounter::RecordsCountSavedFromSerializer, 0}},
|
||||||
{ HashTablePerfCounter::BucketsCount, 5 },
|
{{HashTablePerfCounter::RecordsCount, 3},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 18 },
|
{HashTablePerfCounter::BucketsCount, 5},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 21 },
|
{HashTablePerfCounter::TotalKeySize, 18},
|
||||||
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0 },
|
{HashTablePerfCounter::TotalValueSize, 21},
|
||||||
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 0 }
|
{HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0},
|
||||||
},
|
{HashTablePerfCounter::RecordsCountSavedFromSerializer, 3}},
|
||||||
{
|
{{HashTablePerfCounter::RecordsCount, 3},
|
||||||
{ HashTablePerfCounter::RecordsCount, 3 },
|
{HashTablePerfCounter::BucketsCount, 5},
|
||||||
{ HashTablePerfCounter::BucketsCount, 5 },
|
{HashTablePerfCounter::TotalKeySize, 18},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 18 },
|
{HashTablePerfCounter::TotalValueSize, 21},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 21 },
|
{HashTablePerfCounter::RecordsCountLoadedFromSerializer, 3},
|
||||||
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0 },
|
{HashTablePerfCounter::RecordsCountSavedFromSerializer, 0}});
|
||||||
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 3 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{ HashTablePerfCounter::RecordsCount, 3 },
|
|
||||||
{ HashTablePerfCounter::BucketsCount, 5 },
|
|
||||||
{ HashTablePerfCounter::TotalKeySize, 18 },
|
|
||||||
{ HashTablePerfCounter::TotalValueSize, 21 },
|
|
||||||
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 3 },
|
|
||||||
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 0 }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
|
@ -1,39 +1,30 @@
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
#include "Utils.h"
|
|
||||||
#include "Mocks.h"
|
|
||||||
#include "CheckedAllocator.h"
|
#include "CheckedAllocator.h"
|
||||||
#include "L4/Log/PerfCounter.h"
|
|
||||||
#include "L4/HashTable/ReadWrite/HashTable.h"
|
#include "L4/HashTable/ReadWrite/HashTable.h"
|
||||||
|
#include "L4/Log/PerfCounter.h"
|
||||||
|
#include "Mocks.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
using namespace HashTable::ReadWrite;
|
using namespace HashTable::ReadWrite;
|
||||||
|
|
||||||
class ReadWriteHashTableTestFixture
|
class ReadWriteHashTableTestFixture {
|
||||||
{
|
protected:
|
||||||
protected:
|
|
||||||
using Allocator = CheckedAllocator<>;
|
using Allocator = CheckedAllocator<>;
|
||||||
using HashTable = WritableHashTable<Allocator>::HashTable;
|
using HashTable = WritableHashTable<Allocator>::HashTable;
|
||||||
|
|
||||||
ReadWriteHashTableTestFixture()
|
ReadWriteHashTableTestFixture() : m_allocator{}, m_epochManager{} {}
|
||||||
: m_allocator{}
|
|
||||||
, m_epochManager{}
|
|
||||||
{}
|
|
||||||
|
|
||||||
Allocator m_allocator;
|
Allocator m_allocator;
|
||||||
MockEpochManager m_epochManager;
|
MockEpochManager m_epochManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_SUITE(ReadWriteHashTableTests, ReadWriteHashTableTestFixture)
|
BOOST_FIXTURE_TEST_SUITE(ReadWriteHashTableTests, ReadWriteHashTableTestFixture)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(HashTableTest) {
|
||||||
BOOST_AUTO_TEST_CASE(HashTableTest)
|
HashTable hashTable{HashTable::Setting{100, 5}, m_allocator};
|
||||||
{
|
|
||||||
HashTable hashTable{ HashTable::Setting{ 100, 5 }, m_allocator };
|
|
||||||
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
|
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
|
||||||
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
|
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
|
||||||
|
|
||||||
|
@ -43,35 +34,35 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
|
||||||
// Check empty data.
|
// Check empty data.
|
||||||
|
|
||||||
std::string keyStr = "hello";
|
std::string keyStr = "hello";
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
|
||||||
IReadOnlyHashTable::Value data;
|
IReadOnlyHashTable::Value data;
|
||||||
BOOST_CHECK(!readOnlyHashTable.Get(key, data));
|
BOOST_CHECK(!readOnlyHashTable.Get(key, data));
|
||||||
|
|
||||||
const auto c_counterMaxValue = (std::numeric_limits<HashTablePerfData::TValue>::max)();
|
const auto c_counterMaxValue =
|
||||||
|
(std::numeric_limits<HashTablePerfData::TValue>::max)();
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(
|
||||||
perfData,
|
perfData, {{HashTablePerfCounter::RecordsCount, 0},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 0 },
|
{HashTablePerfCounter::ChainingEntriesCount, 0},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalKeySize, 0},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 0 },
|
{HashTablePerfCounter::TotalValueSize, 0},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 0 },
|
{HashTablePerfCounter::MinKeySize, c_counterMaxValue},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 0 },
|
{HashTablePerfCounter::MaxKeySize, 0},
|
||||||
{ HashTablePerfCounter::MinKeySize, c_counterMaxValue },
|
{HashTablePerfCounter::MinValueSize, c_counterMaxValue},
|
||||||
{ HashTablePerfCounter::MaxKeySize, 0 },
|
{HashTablePerfCounter::MaxValueSize, 0}});
|
||||||
{ HashTablePerfCounter::MinValueSize, c_counterMaxValue },
|
|
||||||
{ HashTablePerfCounter::MaxValueSize, 0 }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// First record added.
|
// First record added.
|
||||||
std::string keyStr = "hello";
|
std::string keyStr = "hello";
|
||||||
std::string valStr = "world";
|
std::string valStr = "world";
|
||||||
|
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
auto val =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
||||||
|
|
||||||
writableHashTable.Add(key, val);
|
writableHashTable.Add(key, val);
|
||||||
|
|
||||||
|
@ -80,19 +71,16 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
|
||||||
BOOST_CHECK(value.m_size == valStr.size());
|
BOOST_CHECK(value.m_size == valStr.size());
|
||||||
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
|
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
{{HashTablePerfCounter::RecordsCount, 1},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 1 },
|
{HashTablePerfCounter::ChainingEntriesCount, 0},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalKeySize, 5},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 0 },
|
{HashTablePerfCounter::TotalValueSize, 5},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 5 },
|
{HashTablePerfCounter::MinKeySize, 5},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 5 },
|
{HashTablePerfCounter::MaxKeySize, 5},
|
||||||
{ HashTablePerfCounter::MinKeySize, 5 },
|
{HashTablePerfCounter::MinValueSize, 5},
|
||||||
{ HashTablePerfCounter::MaxKeySize, 5 },
|
{HashTablePerfCounter::MaxValueSize, 5}});
|
||||||
{ HashTablePerfCounter::MinValueSize, 5 },
|
|
||||||
{ HashTablePerfCounter::MaxValueSize, 5 }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -100,8 +88,10 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
|
||||||
std::string keyStr = "hello2";
|
std::string keyStr = "hello2";
|
||||||
std::string valStr = "world2";
|
std::string valStr = "world2";
|
||||||
|
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
auto val =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
||||||
|
|
||||||
writableHashTable.Add(key, val);
|
writableHashTable.Add(key, val);
|
||||||
|
|
||||||
|
@ -110,19 +100,16 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
|
||||||
BOOST_CHECK(value.m_size == valStr.size());
|
BOOST_CHECK(value.m_size == valStr.size());
|
||||||
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
|
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
{{HashTablePerfCounter::RecordsCount, 2},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 2 },
|
{HashTablePerfCounter::ChainingEntriesCount, 0},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalKeySize, 11},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 0 },
|
{HashTablePerfCounter::TotalValueSize, 11},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 11 },
|
{HashTablePerfCounter::MinKeySize, 5},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 11 },
|
{HashTablePerfCounter::MaxKeySize, 6},
|
||||||
{ HashTablePerfCounter::MinKeySize, 5 },
|
{HashTablePerfCounter::MinValueSize, 5},
|
||||||
{ HashTablePerfCounter::MaxKeySize, 6 },
|
{HashTablePerfCounter::MaxValueSize, 6}});
|
||||||
{ HashTablePerfCounter::MinValueSize, 5 },
|
|
||||||
{ HashTablePerfCounter::MaxValueSize, 6 }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -130,8 +117,10 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
|
||||||
std::string keyStr = "hello";
|
std::string keyStr = "hello";
|
||||||
std::string valStr = "world long string";
|
std::string valStr = "world long string";
|
||||||
|
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
auto val =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
||||||
|
|
||||||
writableHashTable.Add(key, val);
|
writableHashTable.Add(key, val);
|
||||||
|
|
||||||
|
@ -141,19 +130,16 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
|
||||||
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
|
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
|
||||||
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 1);
|
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 1);
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
{{HashTablePerfCounter::RecordsCount, 2},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 2 },
|
{HashTablePerfCounter::ChainingEntriesCount, 0},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalKeySize, 11},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 0 },
|
{HashTablePerfCounter::TotalValueSize, 23},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 11 },
|
{HashTablePerfCounter::MinKeySize, 5},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 23 },
|
{HashTablePerfCounter::MaxKeySize, 6},
|
||||||
{ HashTablePerfCounter::MinKeySize, 5 },
|
{HashTablePerfCounter::MinValueSize, 5},
|
||||||
{ HashTablePerfCounter::MaxKeySize, 6 },
|
{HashTablePerfCounter::MaxValueSize, 17}});
|
||||||
{ HashTablePerfCounter::MinValueSize, 5 },
|
|
||||||
{ HashTablePerfCounter::MaxValueSize, 17 }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -161,8 +147,10 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
|
||||||
std::string keyStr = "hello2";
|
std::string keyStr = "hello2";
|
||||||
std::string valStr = "wo";
|
std::string valStr = "wo";
|
||||||
|
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
auto val =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
||||||
|
|
||||||
writableHashTable.Add(key, val);
|
writableHashTable.Add(key, val);
|
||||||
|
|
||||||
|
@ -172,19 +160,16 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
|
||||||
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
|
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
|
||||||
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 2);
|
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 2);
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
{{HashTablePerfCounter::RecordsCount, 2},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 2 },
|
{HashTablePerfCounter::ChainingEntriesCount, 0},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalKeySize, 11},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 0 },
|
{HashTablePerfCounter::TotalValueSize, 19},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 11 },
|
{HashTablePerfCounter::MinKeySize, 5},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 19 },
|
{HashTablePerfCounter::MaxKeySize, 6},
|
||||||
{ HashTablePerfCounter::MinKeySize, 5 },
|
{HashTablePerfCounter::MinValueSize, 2},
|
||||||
{ HashTablePerfCounter::MaxKeySize, 6 },
|
{HashTablePerfCounter::MaxValueSize, 17}});
|
||||||
{ HashTablePerfCounter::MinValueSize, 2 },
|
|
||||||
{ HashTablePerfCounter::MaxValueSize, 17 }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -192,26 +177,25 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
|
||||||
std::string keyStr = "hello";
|
std::string keyStr = "hello";
|
||||||
std::string valStr = "";
|
std::string valStr = "";
|
||||||
|
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
auto val =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
||||||
|
|
||||||
BOOST_CHECK(writableHashTable.Remove(key));
|
BOOST_CHECK(writableHashTable.Remove(key));
|
||||||
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 3);
|
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 3);
|
||||||
|
|
||||||
// Note that the Remove() doesn't change Min/Max counters by design.
|
// Note that the Remove() doesn't change Min/Max counters by design.
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
{{HashTablePerfCounter::RecordsCount, 1},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 1 },
|
{HashTablePerfCounter::ChainingEntriesCount, 0},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalKeySize, 6},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 0 },
|
{HashTablePerfCounter::TotalValueSize, 2},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 6 },
|
{HashTablePerfCounter::MinKeySize, 5},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 2 },
|
{HashTablePerfCounter::MaxKeySize, 6},
|
||||||
{ HashTablePerfCounter::MinKeySize, 5 },
|
{HashTablePerfCounter::MinValueSize, 2},
|
||||||
{ HashTablePerfCounter::MaxKeySize, 6 },
|
{HashTablePerfCounter::MaxValueSize, 17}});
|
||||||
{ HashTablePerfCounter::MinValueSize, 2 },
|
|
||||||
{ HashTablePerfCounter::MaxValueSize, 17 }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove the second key.
|
// Remove the second key.
|
||||||
keyStr = "hello2";
|
keyStr = "hello2";
|
||||||
|
@ -220,58 +204,51 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
|
||||||
BOOST_CHECK(writableHashTable.Remove(key));
|
BOOST_CHECK(writableHashTable.Remove(key));
|
||||||
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 4);
|
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 4);
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
{{HashTablePerfCounter::RecordsCount, 0},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 0 },
|
{HashTablePerfCounter::ChainingEntriesCount, 0},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalKeySize, 0},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 0 },
|
{HashTablePerfCounter::TotalValueSize, 0},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 0 },
|
{HashTablePerfCounter::MinKeySize, 5},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 0 },
|
{HashTablePerfCounter::MaxKeySize, 6},
|
||||||
{ HashTablePerfCounter::MinKeySize, 5 },
|
{HashTablePerfCounter::MinValueSize, 2},
|
||||||
{ HashTablePerfCounter::MaxKeySize, 6 },
|
{HashTablePerfCounter::MaxValueSize, 17}});
|
||||||
{ HashTablePerfCounter::MinValueSize, 2 },
|
|
||||||
{ HashTablePerfCounter::MaxValueSize, 17 }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Removing the key that doesn't exist.
|
// Removing the key that doesn't exist.
|
||||||
BOOST_CHECK(!writableHashTable.Remove(key));
|
BOOST_CHECK(!writableHashTable.Remove(key));
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
{{HashTablePerfCounter::RecordsCount, 0},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 0 },
|
{HashTablePerfCounter::ChainingEntriesCount, 0},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalKeySize, 0},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 0 },
|
{HashTablePerfCounter::TotalValueSize, 0},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 0 },
|
{HashTablePerfCounter::MinKeySize, 5},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 0 },
|
{HashTablePerfCounter::MaxKeySize, 6},
|
||||||
{ HashTablePerfCounter::MinKeySize, 5 },
|
{HashTablePerfCounter::MinValueSize, 2},
|
||||||
{ HashTablePerfCounter::MaxKeySize, 6 },
|
{HashTablePerfCounter::MaxValueSize, 17}});
|
||||||
{ HashTablePerfCounter::MinValueSize, 2 },
|
|
||||||
{ HashTablePerfCounter::MaxValueSize, 17 }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest) {
|
||||||
BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
|
|
||||||
{
|
|
||||||
Allocator allocator;
|
Allocator allocator;
|
||||||
HashTable hashTable{ HashTable::Setting{ 1 }, allocator };
|
HashTable hashTable{HashTable::Setting{1}, allocator};
|
||||||
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
|
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
|
||||||
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
|
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
|
||||||
|
|
||||||
const auto& perfData = writableHashTable.GetPerfData();
|
const auto& perfData = writableHashTable.GetPerfData();
|
||||||
|
|
||||||
Utils::ValidateCounters(perfData, { { HashTablePerfCounter::ChainingEntriesCount, 0 } });
|
Utils::ValidateCounters(perfData,
|
||||||
|
{{HashTablePerfCounter::ChainingEntriesCount, 0}});
|
||||||
|
|
||||||
const auto initialTotalIndexSize = perfData.Get(HashTablePerfCounter::TotalIndexSize);
|
const auto initialTotalIndexSize =
|
||||||
|
perfData.Get(HashTablePerfCounter::TotalIndexSize);
|
||||||
const std::size_t c_dataSetSize = HashTable::Entry::c_numDataPerEntry + 5U;
|
const std::size_t c_dataSetSize = HashTable::Entry::c_numDataPerEntry + 5U;
|
||||||
|
|
||||||
std::size_t expectedTotalKeySize = 0U;
|
std::size_t expectedTotalKeySize = 0U;
|
||||||
std::size_t expectedTotalValueSize = 0U;
|
std::size_t expectedTotalValueSize = 0U;
|
||||||
|
|
||||||
for (auto i = 0U; i < c_dataSetSize; ++i)
|
for (auto i = 0U; i < c_dataSetSize; ++i) {
|
||||||
{
|
|
||||||
std::stringstream keyStream;
|
std::stringstream keyStream;
|
||||||
keyStream << "key" << i;
|
keyStream << "key" << i;
|
||||||
|
|
||||||
|
@ -281,8 +258,10 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
|
||||||
std::string keyStr = keyStream.str();
|
std::string keyStr = keyStream.str();
|
||||||
std::string valStr = valStream.str();
|
std::string valStr = valStream.str();
|
||||||
|
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
auto val =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
||||||
|
|
||||||
expectedTotalKeySize += key.m_size;
|
expectedTotalKeySize += key.m_size;
|
||||||
expectedTotalValueSize += val.m_size;
|
expectedTotalValueSize += val.m_size;
|
||||||
|
@ -298,28 +277,24 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
|
||||||
using L4::HashTable::RecordSerializer;
|
using L4::HashTable::RecordSerializer;
|
||||||
|
|
||||||
// Variable key/value sizes.
|
// Variable key/value sizes.
|
||||||
const auto recordOverhead = RecordSerializer{ 0U, 0U }.CalculateRecordOverhead();
|
const auto recordOverhead =
|
||||||
|
RecordSerializer{0U, 0U}.CalculateRecordOverhead();
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(
|
||||||
perfData,
|
perfData, {{HashTablePerfCounter::RecordsCount, c_dataSetSize},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 1},
|
||||||
{ HashTablePerfCounter::RecordsCount, c_dataSetSize },
|
{HashTablePerfCounter::MaxBucketChainLength, 2},
|
||||||
{ HashTablePerfCounter::BucketsCount, 1 },
|
{HashTablePerfCounter::ChainingEntriesCount, 1},
|
||||||
{ HashTablePerfCounter::MaxBucketChainLength, 2 },
|
{HashTablePerfCounter::TotalKeySize, expectedTotalKeySize},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 1 },
|
{HashTablePerfCounter::TotalValueSize, expectedTotalValueSize},
|
||||||
{ HashTablePerfCounter::TotalKeySize, expectedTotalKeySize },
|
{HashTablePerfCounter::TotalIndexSize,
|
||||||
{ HashTablePerfCounter::TotalValueSize, expectedTotalValueSize },
|
initialTotalIndexSize + sizeof(HashTable::Entry) +
|
||||||
{
|
(c_dataSetSize * recordOverhead)}});
|
||||||
HashTablePerfCounter::TotalIndexSize,
|
|
||||||
initialTotalIndexSize + sizeof(HashTable::Entry) + (c_dataSetSize * recordOverhead)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now replace with new values.
|
// Now replace with new values.
|
||||||
expectedTotalValueSize = 0U;
|
expectedTotalValueSize = 0U;
|
||||||
|
|
||||||
for (auto i = 0U; i < c_dataSetSize; ++i)
|
for (auto i = 0U; i < c_dataSetSize; ++i) {
|
||||||
{
|
|
||||||
std::stringstream keyStream;
|
std::stringstream keyStream;
|
||||||
keyStream << "key" << i;
|
keyStream << "key" << i;
|
||||||
|
|
||||||
|
@ -329,8 +304,10 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
|
||||||
std::string keyStr = keyStream.str();
|
std::string keyStr = keyStream.str();
|
||||||
std::string valStr = valStream.str();
|
std::string valStr = valStream.str();
|
||||||
|
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
auto val =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
||||||
|
|
||||||
expectedTotalValueSize += val.m_size;
|
expectedTotalValueSize += val.m_size;
|
||||||
|
|
||||||
|
@ -343,28 +320,24 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(
|
||||||
perfData,
|
perfData, {{HashTablePerfCounter::RecordsCount, c_dataSetSize},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 1},
|
||||||
{ HashTablePerfCounter::RecordsCount, c_dataSetSize },
|
{HashTablePerfCounter::MaxBucketChainLength, 2},
|
||||||
{ HashTablePerfCounter::BucketsCount, 1 },
|
{HashTablePerfCounter::ChainingEntriesCount, 1},
|
||||||
{ HashTablePerfCounter::MaxBucketChainLength, 2 },
|
{HashTablePerfCounter::TotalKeySize, expectedTotalKeySize},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 1 },
|
{HashTablePerfCounter::TotalValueSize, expectedTotalValueSize},
|
||||||
{ HashTablePerfCounter::TotalKeySize, expectedTotalKeySize },
|
{HashTablePerfCounter::TotalIndexSize,
|
||||||
{ HashTablePerfCounter::TotalValueSize, expectedTotalValueSize },
|
initialTotalIndexSize + sizeof(HashTable::Entry) +
|
||||||
{
|
(c_dataSetSize * recordOverhead)}});
|
||||||
HashTablePerfCounter::TotalIndexSize,
|
|
||||||
initialTotalIndexSize + sizeof(HashTable::Entry) + (c_dataSetSize * recordOverhead)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now remove all key-value.
|
// Now remove all key-value.
|
||||||
for (auto i = 0U; i < c_dataSetSize; ++i)
|
for (auto i = 0U; i < c_dataSetSize; ++i) {
|
||||||
{
|
|
||||||
std::stringstream keyStream;
|
std::stringstream keyStream;
|
||||||
keyStream << "key" << i;
|
keyStream << "key" << i;
|
||||||
|
|
||||||
std::string keyStr = keyStream.str();
|
std::string keyStr = keyStream.str();
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
|
||||||
BOOST_CHECK(writableHashTable.Remove(key));
|
BOOST_CHECK(writableHashTable.Remove(key));
|
||||||
|
|
||||||
|
@ -372,27 +345,21 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
|
||||||
BOOST_CHECK(!readOnlyHashTable.Get(key, value));
|
BOOST_CHECK(!readOnlyHashTable.Get(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(perfData,
|
||||||
perfData,
|
{{HashTablePerfCounter::RecordsCount, 0},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 1},
|
||||||
{ HashTablePerfCounter::RecordsCount, 0 },
|
{HashTablePerfCounter::MaxBucketChainLength, 2},
|
||||||
{ HashTablePerfCounter::BucketsCount, 1 },
|
{HashTablePerfCounter::ChainingEntriesCount, 1},
|
||||||
{ HashTablePerfCounter::MaxBucketChainLength, 2 },
|
{HashTablePerfCounter::TotalKeySize, 0},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 1 },
|
{HashTablePerfCounter::TotalValueSize, 0},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 0 },
|
{HashTablePerfCounter::TotalIndexSize,
|
||||||
{ HashTablePerfCounter::TotalValueSize, 0 },
|
initialTotalIndexSize + sizeof(HashTable::Entry)}});
|
||||||
{
|
|
||||||
HashTablePerfCounter::TotalIndexSize,
|
|
||||||
initialTotalIndexSize + sizeof(HashTable::Entry)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Try to add back to the same bucket (reusing existing entries)
|
// Try to add back to the same bucket (reusing existing entries)
|
||||||
expectedTotalKeySize = 0U;
|
expectedTotalKeySize = 0U;
|
||||||
expectedTotalValueSize = 0U;
|
expectedTotalValueSize = 0U;
|
||||||
|
|
||||||
for (auto i = 0U; i < c_dataSetSize; ++i)
|
for (auto i = 0U; i < c_dataSetSize; ++i) {
|
||||||
{
|
|
||||||
std::stringstream keyStream;
|
std::stringstream keyStream;
|
||||||
keyStream << "key" << i;
|
keyStream << "key" << i;
|
||||||
|
|
||||||
|
@ -402,8 +369,10 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
|
||||||
std::string keyStr = keyStream.str();
|
std::string keyStr = keyStream.str();
|
||||||
std::string valStr = valStream.str();
|
std::string valStr = valStream.str();
|
||||||
|
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
auto val =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
||||||
|
|
||||||
expectedTotalKeySize += key.m_size;
|
expectedTotalKeySize += key.m_size;
|
||||||
expectedTotalValueSize += val.m_size;
|
expectedTotalValueSize += val.m_size;
|
||||||
|
@ -417,25 +386,19 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(
|
||||||
perfData,
|
perfData, {{HashTablePerfCounter::RecordsCount, c_dataSetSize},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 1},
|
||||||
{ HashTablePerfCounter::RecordsCount, c_dataSetSize },
|
{HashTablePerfCounter::MaxBucketChainLength, 2},
|
||||||
{ HashTablePerfCounter::BucketsCount, 1 },
|
{HashTablePerfCounter::ChainingEntriesCount, 1},
|
||||||
{ HashTablePerfCounter::MaxBucketChainLength, 2 },
|
{HashTablePerfCounter::TotalKeySize, expectedTotalKeySize},
|
||||||
{ HashTablePerfCounter::ChainingEntriesCount, 1 },
|
{HashTablePerfCounter::TotalValueSize, expectedTotalValueSize},
|
||||||
{ HashTablePerfCounter::TotalKeySize, expectedTotalKeySize },
|
{HashTablePerfCounter::TotalIndexSize,
|
||||||
{ HashTablePerfCounter::TotalValueSize, expectedTotalValueSize },
|
initialTotalIndexSize + sizeof(HashTable::Entry) +
|
||||||
{
|
(c_dataSetSize * recordOverhead)}});
|
||||||
HashTablePerfCounter::TotalIndexSize,
|
|
||||||
initialTotalIndexSize + sizeof(HashTable::Entry) + (c_dataSetSize * recordOverhead)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(AddRemoveSameKeyTest) {
|
||||||
BOOST_AUTO_TEST_CASE(AddRemoveSameKeyTest)
|
HashTable hashTable{HashTable::Setting{100, 5}, m_allocator};
|
||||||
{
|
|
||||||
HashTable hashTable{ HashTable::Setting{ 100, 5 }, m_allocator };
|
|
||||||
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
|
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
|
||||||
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
|
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
|
||||||
|
|
||||||
|
@ -459,13 +422,15 @@ BOOST_AUTO_TEST_CASE(AddRemoveSameKeyTest)
|
||||||
|
|
||||||
const auto& perfData = writableHashTable.GetPerfData();
|
const auto& perfData = writableHashTable.GetPerfData();
|
||||||
|
|
||||||
// Now remove the first record with key = "key1", which is at the head of the chain.
|
// Now remove the first record with key = "key1", which is at the head of the
|
||||||
|
// chain.
|
||||||
BOOST_CHECK(writableHashTable.Remove(key1));
|
BOOST_CHECK(writableHashTable.Remove(key1));
|
||||||
BOOST_CHECK(!readOnlyHashTable.Get(key1, valueRetrieved));
|
BOOST_CHECK(!readOnlyHashTable.Get(key1, valueRetrieved));
|
||||||
Utils::ValidateCounter(perfData, HashTablePerfCounter::RecordsCount, 1);
|
Utils::ValidateCounter(perfData, HashTablePerfCounter::RecordsCount, 1);
|
||||||
|
|
||||||
// Now try update the record with key = "key2". This should correctly update the existing record
|
// Now try update the record with key = "key2". This should correctly update
|
||||||
// instead of using the empty slot created by removing the record with key = "key1".
|
// the existing record instead of using the empty slot created by removing the
|
||||||
|
// record with key = "key1".
|
||||||
auto newVal2 = Utils::ConvertFromString<IReadOnlyHashTable::Value>("newVal2");
|
auto newVal2 = Utils::ConvertFromString<IReadOnlyHashTable::Value>("newVal2");
|
||||||
writableHashTable.Add(key2, newVal2);
|
writableHashTable.Add(key2, newVal2);
|
||||||
|
|
||||||
|
@ -480,53 +445,44 @@ BOOST_AUTO_TEST_CASE(AddRemoveSameKeyTest)
|
||||||
Utils::ValidateCounter(perfData, HashTablePerfCounter::RecordsCount, 0);
|
Utils::ValidateCounter(perfData, HashTablePerfCounter::RecordsCount, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(FixedKeyValueHashTableTest) {
|
||||||
BOOST_AUTO_TEST_CASE(FixedKeyValueHashTableTest)
|
|
||||||
{
|
|
||||||
// Fixed 4 byte keys and 6 byte values.
|
// Fixed 4 byte keys and 6 byte values.
|
||||||
std::vector<HashTable::Setting> settings =
|
std::vector<HashTable::Setting> settings = {
|
||||||
{
|
HashTable::Setting{100, 200, 4, 0}, HashTable::Setting{100, 200, 0, 6},
|
||||||
HashTable::Setting{ 100, 200, 4, 0 },
|
HashTable::Setting{100, 200, 4, 6}};
|
||||||
HashTable::Setting{ 100, 200, 0, 6 },
|
|
||||||
HashTable::Setting{ 100, 200, 4, 6 }
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& setting : settings)
|
for (const auto& setting : settings) {
|
||||||
{
|
HashTable hashTable{setting, m_allocator};
|
||||||
HashTable hashTable{ setting, m_allocator };
|
|
||||||
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
|
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
|
||||||
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
|
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
|
||||||
|
|
||||||
constexpr std::uint8_t c_numRecords = 10;
|
constexpr std::uint8_t c_numRecords = 10;
|
||||||
|
|
||||||
for (std::uint8_t i = 0; i < c_numRecords; ++i)
|
for (std::uint8_t i = 0; i < c_numRecords; ++i) {
|
||||||
{
|
|
||||||
const std::string keyStr = "key" + std::to_string(i);
|
const std::string keyStr = "key" + std::to_string(i);
|
||||||
const std::string valueStr = "value" + std::to_string(i);
|
const std::string valueStr = "value" + std::to_string(i);
|
||||||
|
|
||||||
writableHashTable.Add(
|
writableHashTable.Add(
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()),
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()),
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valueStr.c_str()));
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(
|
||||||
|
valueStr.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(writableHashTable.GetPerfData(),
|
||||||
writableHashTable.GetPerfData(),
|
{{HashTablePerfCounter::RecordsCount, 10},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 10 },
|
{HashTablePerfCounter::TotalKeySize, 40},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalValueSize, 60},
|
||||||
{ HashTablePerfCounter::TotalKeySize, 40 },
|
{HashTablePerfCounter::MinKeySize, 4},
|
||||||
{ HashTablePerfCounter::TotalValueSize, 60 },
|
{HashTablePerfCounter::MaxKeySize, 4},
|
||||||
{ HashTablePerfCounter::MinKeySize, 4 },
|
{HashTablePerfCounter::MinValueSize, 6},
|
||||||
{ HashTablePerfCounter::MaxKeySize, 4 },
|
{HashTablePerfCounter::MaxValueSize, 6}});
|
||||||
{ HashTablePerfCounter::MinValueSize, 6 },
|
|
||||||
{ HashTablePerfCounter::MaxValueSize, 6 }
|
|
||||||
});
|
|
||||||
|
|
||||||
for (std::uint8_t i = 0; i < c_numRecords; ++i)
|
for (std::uint8_t i = 0; i < c_numRecords; ++i) {
|
||||||
{
|
|
||||||
const std::string keyStr = "key" + std::to_string(i);
|
const std::string keyStr = "key" + std::to_string(i);
|
||||||
const std::string valueStr = "value" + std::to_string(i);
|
const std::string valueStr = "value" + std::to_string(i);
|
||||||
const auto expectedValue = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valueStr.c_str());
|
const auto expectedValue =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valueStr.c_str());
|
||||||
|
|
||||||
IReadOnlyHashTable::Value actualValue;
|
IReadOnlyHashTable::Value actualValue;
|
||||||
BOOST_CHECK(readOnlyHashTable.Get(
|
BOOST_CHECK(readOnlyHashTable.Get(
|
||||||
|
@ -535,30 +491,24 @@ BOOST_AUTO_TEST_CASE(FixedKeyValueHashTableTest)
|
||||||
BOOST_CHECK(expectedValue == actualValue);
|
BOOST_CHECK(expectedValue == actualValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::uint8_t i = 0; i < c_numRecords; ++i)
|
for (std::uint8_t i = 0; i < c_numRecords; ++i) {
|
||||||
{
|
|
||||||
const std::string keyStr = "key" + std::to_string(i);
|
const std::string keyStr = "key" + std::to_string(i);
|
||||||
writableHashTable.Remove(
|
writableHashTable.Remove(
|
||||||
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()));
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ValidateCounters(
|
Utils::ValidateCounters(writableHashTable.GetPerfData(),
|
||||||
writableHashTable.GetPerfData(),
|
{{HashTablePerfCounter::RecordsCount, 0},
|
||||||
{
|
{HashTablePerfCounter::BucketsCount, 100},
|
||||||
{ HashTablePerfCounter::RecordsCount, 0 },
|
{HashTablePerfCounter::TotalKeySize, 0},
|
||||||
{ HashTablePerfCounter::BucketsCount, 100 },
|
{HashTablePerfCounter::TotalValueSize, 0}});
|
||||||
{ HashTablePerfCounter::TotalKeySize, 0 },
|
|
||||||
{ HashTablePerfCounter::TotalValueSize, 0 }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(HashTableIteratorTest) {
|
||||||
BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
|
|
||||||
{
|
|
||||||
Allocator allocator;
|
Allocator allocator;
|
||||||
constexpr std::uint32_t c_numBuckets = 10;
|
constexpr std::uint32_t c_numBuckets = 10;
|
||||||
HashTable hashTable{ HashTable::Setting{ c_numBuckets }, allocator };
|
HashTable hashTable{HashTable::Setting{c_numBuckets}, allocator};
|
||||||
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
|
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -567,12 +517,10 @@ BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
|
||||||
BOOST_CHECK(!iter->MoveNext());
|
BOOST_CHECK(!iter->MoveNext());
|
||||||
|
|
||||||
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
||||||
iter->GetKey(),
|
iter->GetKey(), "HashTableIterator is not correctly used.");
|
||||||
"HashTableIterator is not correctly used.");
|
|
||||||
|
|
||||||
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
|
||||||
iter->GetValue(),
|
iter->GetValue(), "HashTableIterator is not correctly used.");
|
||||||
"HashTableIterator is not correctly used.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using Buffer = std::vector<std::uint8_t>;
|
using Buffer = std::vector<std::uint8_t>;
|
||||||
|
@ -581,11 +529,12 @@ BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
|
||||||
BufferMap keyValueMap;
|
BufferMap keyValueMap;
|
||||||
|
|
||||||
// The number of records should be such that it will create chained entries
|
// The number of records should be such that it will create chained entries
|
||||||
// for at least one bucket. So it should be greater than HashTable::Entry::c_numDataPerEntry * number of buckets.
|
// for at least one bucket. So it should be greater than
|
||||||
constexpr std::uint32_t c_numRecords = (HashTable::Entry::c_numDataPerEntry * c_numBuckets) + 1;
|
// HashTable::Entry::c_numDataPerEntry * number of buckets.
|
||||||
|
constexpr std::uint32_t c_numRecords =
|
||||||
|
(HashTable::Entry::c_numDataPerEntry * c_numBuckets) + 1;
|
||||||
|
|
||||||
for (auto i = 0U; i < c_numRecords; ++i)
|
for (auto i = 0U; i < c_numRecords; ++i) {
|
||||||
{
|
|
||||||
std::stringstream keyStream;
|
std::stringstream keyStream;
|
||||||
keyStream << "key" << i;
|
keyStream << "key" << i;
|
||||||
|
|
||||||
|
@ -595,15 +544,19 @@ BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
|
||||||
std::string keyStr = keyStream.str();
|
std::string keyStr = keyStream.str();
|
||||||
std::string valStr = valStream.str();
|
std::string valStr = valStream.str();
|
||||||
|
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
auto val =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
|
||||||
|
|
||||||
writableHashTable.Add(key, val);
|
writableHashTable.Add(key, val);
|
||||||
|
|
||||||
keyValueMap[Buffer(key.m_data, key.m_data + key.m_size)] = Buffer(val.m_data, val.m_data + val.m_size);
|
keyValueMap[Buffer(key.m_data, key.m_data + key.m_size)] =
|
||||||
|
Buffer(val.m_data, val.m_data + val.m_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_REQUIRE(writableHashTable.GetPerfData().Get(HashTablePerfCounter::MaxBucketChainLength) >= 2);
|
BOOST_REQUIRE(writableHashTable.GetPerfData().Get(
|
||||||
|
HashTablePerfCounter::MaxBucketChainLength) >= 2);
|
||||||
BOOST_CHECK_EQUAL(keyValueMap.size(), c_numRecords);
|
BOOST_CHECK_EQUAL(keyValueMap.size(), c_numRecords);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -611,37 +564,35 @@ BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
|
||||||
|
|
||||||
// Validate the data using the iterator.
|
// Validate the data using the iterator.
|
||||||
auto iter = writableHashTable.GetIterator();
|
auto iter = writableHashTable.GetIterator();
|
||||||
for (auto i = 0U; i < c_numRecords; ++i)
|
for (auto i = 0U; i < c_numRecords; ++i) {
|
||||||
{
|
|
||||||
BOOST_CHECK(iter->MoveNext());
|
BOOST_CHECK(iter->MoveNext());
|
||||||
|
|
||||||
const auto& key = iter->GetKey();
|
const auto& key = iter->GetKey();
|
||||||
const auto& val = iter->GetValue();
|
const auto& val = iter->GetValue();
|
||||||
|
|
||||||
keyValueMapFromIterator[Buffer(key.m_data, key.m_data + key.m_size)] = Buffer(val.m_data, val.m_data + val.m_size);
|
keyValueMapFromIterator[Buffer(key.m_data, key.m_data + key.m_size)] =
|
||||||
|
Buffer(val.m_data, val.m_data + val.m_size);
|
||||||
}
|
}
|
||||||
BOOST_CHECK(!iter->MoveNext());
|
BOOST_CHECK(!iter->MoveNext());
|
||||||
BOOST_CHECK(keyValueMap == keyValueMapFromIterator);
|
BOOST_CHECK(keyValueMap == keyValueMapFromIterator);
|
||||||
|
|
||||||
// Reset should move the iterator to the beginning.
|
// Reset should move the iterator to the beginning.
|
||||||
iter->Reset();
|
iter->Reset();
|
||||||
for (auto i = 0U; i < c_numRecords; ++i)
|
for (auto i = 0U; i < c_numRecords; ++i) {
|
||||||
{
|
|
||||||
BOOST_CHECK(iter->MoveNext());
|
BOOST_CHECK(iter->MoveNext());
|
||||||
}
|
}
|
||||||
BOOST_CHECK(!iter->MoveNext());
|
BOOST_CHECK(!iter->MoveNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove half of the key.
|
// Remove half of the key.
|
||||||
for (auto i = 0U; i < c_numRecords; ++i)
|
for (auto i = 0U; i < c_numRecords; ++i) {
|
||||||
{
|
if (i % 2 == 0U) {
|
||||||
if (i % 2 == 0U)
|
|
||||||
{
|
|
||||||
std::stringstream keyStream;
|
std::stringstream keyStream;
|
||||||
keyStream << "key" << i;
|
keyStream << "key" << i;
|
||||||
|
|
||||||
std::string keyStr = keyStream.str();
|
std::string keyStr = keyStream.str();
|
||||||
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
auto key =
|
||||||
|
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
|
||||||
|
|
||||||
BOOST_CHECK(writableHashTable.Remove(key));
|
BOOST_CHECK(writableHashTable.Remove(key));
|
||||||
|
|
||||||
|
@ -655,8 +606,7 @@ BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
|
||||||
{
|
{
|
||||||
BufferMap keyValueMapFromIterator;
|
BufferMap keyValueMapFromIterator;
|
||||||
auto iter = writableHashTable.GetIterator();
|
auto iter = writableHashTable.GetIterator();
|
||||||
for (auto i = 0U; i < c_numRecords / 2U; ++i)
|
for (auto i = 0U; i < c_numRecords / 2U; ++i) {
|
||||||
{
|
|
||||||
BOOST_CHECK(iter->MoveNext());
|
BOOST_CHECK(iter->MoveNext());
|
||||||
|
|
||||||
const auto& key = iter->GetKey();
|
const auto& key = iter->GetKey();
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
#include "L4/HashTable/Common/SettingAdapter.h"
|
|
||||||
#include "L4/HashTable/Common/Record.h"
|
|
||||||
#include "CheckedAllocator.h"
|
#include "CheckedAllocator.h"
|
||||||
|
#include "L4/HashTable/Common/Record.h"
|
||||||
|
#include "L4/HashTable/Common/SettingAdapter.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
using SharedHashTable = HashTable::SharedHashTable<HashTable::RecordBuffer, CheckedAllocator<>>;
|
using SharedHashTable =
|
||||||
|
HashTable::SharedHashTable<HashTable::RecordBuffer, CheckedAllocator<>>;
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(SettingAdapterTests)
|
BOOST_AUTO_TEST_SUITE(SettingAdapterTests)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(SettingAdapterTestWithDefaultValues)
|
BOOST_AUTO_TEST_CASE(SettingAdapterTestWithDefaultValues) {
|
||||||
{
|
HashTableConfig::Setting from{100U};
|
||||||
HashTableConfig::Setting from{ 100U };
|
|
||||||
const auto to = HashTable::SettingAdapter{}.Convert<SharedHashTable>(from);
|
const auto to = HashTable::SettingAdapter{}.Convert<SharedHashTable>(from);
|
||||||
|
|
||||||
BOOST_CHECK_EQUAL(to.m_numBuckets, 100U);
|
BOOST_CHECK_EQUAL(to.m_numBuckets, 100U);
|
||||||
|
@ -23,10 +21,8 @@ BOOST_AUTO_TEST_CASE(SettingAdapterTestWithDefaultValues)
|
||||||
BOOST_CHECK_EQUAL(to.m_fixedValueSize, 0U);
|
BOOST_CHECK_EQUAL(to.m_fixedValueSize, 0U);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(SettingAdapterTestWithNonDefaultValues) {
|
||||||
BOOST_AUTO_TEST_CASE(SettingAdapterTestWithNonDefaultValues)
|
HashTableConfig::Setting from{100U, 10U, 5U, 20U};
|
||||||
{
|
|
||||||
HashTableConfig::Setting from{ 100U, 10U, 5U, 20U };
|
|
||||||
const auto to = HashTable::SettingAdapter{}.Convert<SharedHashTable>(from);
|
const auto to = HashTable::SettingAdapter{}.Convert<SharedHashTable>(from);
|
||||||
|
|
||||||
BOOST_CHECK_EQUAL(to.m_numBuckets, 100U);
|
BOOST_CHECK_EQUAL(to.m_numBuckets, 100U);
|
||||||
|
|
|
@ -1,33 +1,23 @@
|
||||||
#include <boost/test/unit_test.hpp>
|
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
namespace Utils {
|
||||||
{
|
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
|
|
||||||
|
void ValidateCounter(const HashTablePerfData& actual,
|
||||||
void ValidateCounter(
|
|
||||||
const HashTablePerfData& actual,
|
|
||||||
HashTablePerfCounter perfCounter,
|
HashTablePerfCounter perfCounter,
|
||||||
PerfCounters<HashTablePerfCounter>::TValue expectedValue)
|
PerfCounters<HashTablePerfCounter>::TValue expectedValue) {
|
||||||
{
|
|
||||||
BOOST_CHECK_MESSAGE(
|
BOOST_CHECK_MESSAGE(
|
||||||
actual.Get(perfCounter) == expectedValue,
|
actual.Get(perfCounter) == expectedValue,
|
||||||
c_hashTablePerfCounterNames[static_cast<std::size_t>(perfCounter)]
|
c_hashTablePerfCounterNames[static_cast<std::size_t>(perfCounter)]
|
||||||
<< " counter: "
|
<< " counter: " << actual.Get(perfCounter)
|
||||||
<< actual.Get(perfCounter)
|
|
||||||
<< " (actual) != " << expectedValue << " (expected).");
|
<< " (actual) != " << expectedValue << " (expected).");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ValidateCounters(
|
void ValidateCounters(const HashTablePerfData& actual,
|
||||||
const HashTablePerfData& actual,
|
const ExpectedCounterValues& expected) {
|
||||||
const ExpectedCounterValues& expected)
|
for (const auto& expectedCounter : expected) {
|
||||||
{
|
|
||||||
for (const auto& expectedCounter : expected)
|
|
||||||
{
|
|
||||||
ValidateCounter(actual, expectedCounter.first, expectedCounter.second);
|
ValidateCounter(actual, expectedCounter.first, expectedCounter.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +1,86 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "L4/Log/PerfCounter.h"
|
#include "L4/Log/PerfCounter.h"
|
||||||
#include "L4/Utils/Exception.h"
|
#include "L4/Utils/Exception.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
// Macro CHECK_EXCEPTION_THROWN
|
// Macro CHECK_EXCEPTION_THROWN
|
||||||
|
|
||||||
#define CHECK_EXCEPTION_THROWN(statement) \
|
#define CHECK_EXCEPTION_THROWN(statement) \
|
||||||
do { \
|
do { \
|
||||||
bool isExceptionThrown = false;\
|
bool isExceptionThrown = false; \
|
||||||
try \
|
try { \
|
||||||
{ \
|
|
||||||
statement; \
|
statement; \
|
||||||
} \
|
} catch (const RuntimeException&) { \
|
||||||
catch (const RuntimeException&) \
|
|
||||||
{ \
|
|
||||||
isExceptionThrown = true; \
|
isExceptionThrown = true; \
|
||||||
} \
|
} \
|
||||||
BOOST_CHECK(isExceptionThrown); \
|
BOOST_CHECK(isExceptionThrown); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
#define CHECK_EXCEPTION_THROWN_WITH_MESSAGE(statement, message) \
|
#define CHECK_EXCEPTION_THROWN_WITH_MESSAGE(statement, message) \
|
||||||
do { \
|
do { \
|
||||||
bool isExceptionThrown = false; \
|
bool isExceptionThrown = false; \
|
||||||
std::string exceptionMsg; \
|
std::string exceptionMsg; \
|
||||||
try \
|
try { \
|
||||||
{ \
|
|
||||||
statement; \
|
statement; \
|
||||||
} \
|
} catch (const RuntimeException& ex) { \
|
||||||
catch (const RuntimeException& ex) \
|
|
||||||
{ \
|
|
||||||
isExceptionThrown = true; \
|
isExceptionThrown = true; \
|
||||||
exceptionMsg = ex.what(); \
|
exceptionMsg = ex.what(); \
|
||||||
BOOST_TEST_MESSAGE("Exception Message: " << exceptionMsg); \
|
BOOST_TEST_MESSAGE("Exception Message: " << exceptionMsg); \
|
||||||
} \
|
} \
|
||||||
BOOST_CHECK(isExceptionThrown); \
|
BOOST_CHECK(isExceptionThrown); \
|
||||||
BOOST_CHECK(strcmp((message), exceptionMsg.c_str()) == 0); \
|
BOOST_CHECK(strcmp((message), exceptionMsg.c_str()) == 0); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
// This will validate the given message is a prefix of the exception message.
|
// This will validate the given message is a prefix of the exception message.
|
||||||
#define CHECK_EXCEPTION_THROWN_WITH_PREFIX_MESSAGE(statement, message) \
|
#define CHECK_EXCEPTION_THROWN_WITH_PREFIX_MESSAGE(statement, message) \
|
||||||
do { \
|
do { \
|
||||||
bool isExceptionThrown = false; \
|
bool isExceptionThrown = false; \
|
||||||
std::string exceptionMsg; \
|
std::string exceptionMsg; \
|
||||||
try \
|
try { \
|
||||||
{ \
|
|
||||||
statement; \
|
statement; \
|
||||||
} \
|
} catch (const RuntimeException& ex) { \
|
||||||
catch (const RuntimeException& ex) \
|
|
||||||
{ \
|
|
||||||
isExceptionThrown = true; \
|
isExceptionThrown = true; \
|
||||||
exceptionMsg = ex.what(); \
|
exceptionMsg = ex.what(); \
|
||||||
BOOST_TEST_MESSAGE("Exception Message: " << exceptionMsg); \
|
BOOST_TEST_MESSAGE("Exception Message: " << exceptionMsg); \
|
||||||
} \
|
} \
|
||||||
BOOST_CHECK(isExceptionThrown); \
|
BOOST_CHECK(isExceptionThrown); \
|
||||||
BOOST_CHECK(exceptionMsg.compare(0, strlen(message), message) == 0); \
|
BOOST_CHECK(exceptionMsg.compare(0, strlen(message), message) == 0); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T ConvertFromString(const char* str)
|
T ConvertFromString(const char* str) {
|
||||||
{
|
return T(reinterpret_cast<const std::uint8_t*>(str),
|
||||||
return T(
|
|
||||||
reinterpret_cast<const std::uint8_t*>(str),
|
|
||||||
static_cast<typename T::size_type>(strlen(str)));
|
static_cast<typename T::size_type>(strlen(str)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::string ConvertToString(const T& t)
|
std::string ConvertToString(const T& t) {
|
||||||
{
|
|
||||||
return std::string(reinterpret_cast<const char*>(t.m_data), t.m_size);
|
return std::string(reinterpret_cast<const char*>(t.m_data), t.m_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Counter related validation util function.
|
// Counter related validation util function.
|
||||||
|
|
||||||
using ExpectedCounterValues
|
using ExpectedCounterValues =
|
||||||
= std::vector<
|
std::vector<std::pair<HashTablePerfCounter,
|
||||||
std::pair<
|
|
||||||
HashTablePerfCounter,
|
|
||||||
typename PerfCounters<HashTablePerfCounter>::TValue>>;
|
typename PerfCounters<HashTablePerfCounter>::TValue>>;
|
||||||
|
|
||||||
// Validate the given perfData against the expected counter value.
|
// Validate the given perfData against the expected counter value.
|
||||||
void ValidateCounter(
|
void ValidateCounter(const HashTablePerfData& actual,
|
||||||
const HashTablePerfData& actual,
|
|
||||||
HashTablePerfCounter perfCounter,
|
HashTablePerfCounter perfCounter,
|
||||||
PerfCounters<HashTablePerfCounter>::TValue expectedValue);
|
PerfCounters<HashTablePerfCounter>::TValue expectedValue);
|
||||||
|
|
||||||
// Validate the given perfData against the expected counter values.
|
// Validate the given perfData against the expected counter values.
|
||||||
void ValidateCounters(
|
void ValidateCounters(const HashTablePerfData& actual,
|
||||||
const HashTablePerfData& actual,
|
|
||||||
const ExpectedCounterValues& expected);
|
const ExpectedCounterValues& expected);
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
#include <boost/test/unit_test.hpp>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
#include "L4/Utils/Math.h"
|
#include "L4/Utils/Math.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace UnitTests {
|
||||||
namespace UnitTests
|
|
||||||
{
|
|
||||||
|
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(MathTest)
|
BOOST_AUTO_TEST_CASE(MathTest) {
|
||||||
{
|
|
||||||
// RoundUp tests.
|
// RoundUp tests.
|
||||||
BOOST_CHECK_EQUAL(Math::RoundUp(5, 10), 10);
|
BOOST_CHECK_EQUAL(Math::RoundUp(5, 10), 10);
|
||||||
BOOST_CHECK_EQUAL(Math::RoundUp(10, 10), 10);
|
BOOST_CHECK_EQUAL(Math::RoundUp(10, 10), 10);
|
||||||
|
@ -39,15 +36,17 @@ BOOST_AUTO_TEST_CASE(MathTest)
|
||||||
BOOST_CHECK_EQUAL(Math::NextHighestPowerOfTwo(200), 256U);
|
BOOST_CHECK_EQUAL(Math::NextHighestPowerOfTwo(200), 256U);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(PointerArithmeticTest) {
|
||||||
BOOST_AUTO_TEST_CASE(PointerArithmeticTest)
|
|
||||||
{
|
|
||||||
std::array<int, 3> elements;
|
std::array<int, 3> elements;
|
||||||
|
|
||||||
BOOST_CHECK(reinterpret_cast<int*>(Math::PointerArithmetic::Add(&elements[0], sizeof(int))) == &elements[1]);
|
BOOST_CHECK(reinterpret_cast<int*>(Math::PointerArithmetic::Add(
|
||||||
BOOST_CHECK(reinterpret_cast<int*>(Math::PointerArithmetic::Subtract(&elements[1], sizeof(int))) == &elements[0]);
|
&elements[0], sizeof(int))) == &elements[1]);
|
||||||
BOOST_CHECK(Math::PointerArithmetic::Distance(&elements[2], &elements[0]) == sizeof(int) * 2U);
|
BOOST_CHECK(reinterpret_cast<int*>(Math::PointerArithmetic::Subtract(
|
||||||
BOOST_CHECK(Math::PointerArithmetic::Distance(&elements[0], &elements[2]) == sizeof(int) * 2U);
|
&elements[1], sizeof(int))) == &elements[0]);
|
||||||
|
BOOST_CHECK(Math::PointerArithmetic::Distance(&elements[2], &elements[0]) ==
|
||||||
|
sizeof(int) * 2U);
|
||||||
|
BOOST_CHECK(Math::PointerArithmetic::Distance(&elements[0], &elements[2]) ==
|
||||||
|
sizeof(int) * 2U);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace UnitTests
|
} // namespace UnitTests
|
||||||
|
|
|
@ -1,28 +1,26 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
// EpochManagerConfig struct.
|
// EpochManagerConfig struct.
|
||||||
struct EpochManagerConfig
|
struct EpochManagerConfig {
|
||||||
{
|
// "numActionQueues" indicates how many action containers there will be in
|
||||||
// "numActionQueues" indicates how many action containers there will be in order to
|
// order to increase the throughput of registering an action.
|
||||||
// increase the throughput of registering an action.
|
// "performActionsInParallelThreshold" indicates the threshold value above
|
||||||
// "performActionsInParallelThreshold" indicates the threshold value above which
|
// which the actions are performed in parallel.
|
||||||
// the actions are performed in parallel.
|
// "maxNumThreadsToPerformActions" indicates how many threads will be used
|
||||||
// "maxNumThreadsToPerformActions" indicates how many threads will be used when
|
// when performing an action in parallel.
|
||||||
// performing an action in parallel.
|
|
||||||
explicit EpochManagerConfig(
|
explicit EpochManagerConfig(
|
||||||
std::uint32_t epochQueueSize = 1000,
|
std::uint32_t epochQueueSize = 1000,
|
||||||
std::chrono::milliseconds epochProcessingInterval = std::chrono::milliseconds{ 1000 },
|
std::chrono::milliseconds epochProcessingInterval =
|
||||||
|
std::chrono::milliseconds{1000},
|
||||||
std::uint8_t numActionQueues = 1)
|
std::uint8_t numActionQueues = 1)
|
||||||
: m_epochQueueSize{ epochQueueSize }
|
: m_epochQueueSize{epochQueueSize},
|
||||||
, m_epochProcessingInterval{ epochProcessingInterval }
|
m_epochProcessingInterval{epochProcessingInterval},
|
||||||
, m_numActionQueues{ numActionQueues }
|
m_numActionQueues{numActionQueues} {}
|
||||||
{}
|
|
||||||
|
|
||||||
std::uint32_t m_epochQueueSize;
|
std::uint32_t m_epochQueueSize;
|
||||||
std::chrono::milliseconds m_epochProcessingInterval;
|
std::chrono::milliseconds m_epochProcessingInterval;
|
||||||
|
|
|
@ -11,24 +11,22 @@
|
||||||
#include "IEpochActionManager.h"
|
#include "IEpochActionManager.h"
|
||||||
#include "Utils/Lock.h"
|
#include "Utils/Lock.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
|
// EpochActionManager provides functionalities to add actions at an epoch and to
|
||||||
// EpochActionManager provides functionalities to add actions at an epoch and to perform
|
// perform actions up to the given epoch.
|
||||||
// actions up to the given epoch.
|
class EpochActionManager {
|
||||||
class EpochActionManager
|
public:
|
||||||
{
|
// "numActionQueues" indicates how many action containers there will be in
|
||||||
public:
|
// order to increase the throughput of registering an action. This will be
|
||||||
// "numActionQueues" indicates how many action containers there will be in order to
|
// re-calculated to the next highest power of two so that the "&" operator can
|
||||||
// increase the throughput of registering an action. This will be re-calculated to
|
// be used for accessing the next queue.
|
||||||
// the next highest power of two so that the "&" operator can be used for accessing
|
|
||||||
// the next queue.
|
|
||||||
explicit EpochActionManager(std::uint8_t numActionQueues);
|
explicit EpochActionManager(std::uint8_t numActionQueues);
|
||||||
|
|
||||||
// Adds an action at a given epoch counter.
|
// Adds an action at a given epoch counter.
|
||||||
// This function is thread-safe.
|
// This function is thread-safe.
|
||||||
void RegisterAction(std::uint64_t epochCounter, IEpochActionManager::Action&& action);
|
void RegisterAction(std::uint64_t epochCounter,
|
||||||
|
IEpochActionManager::Action&& action);
|
||||||
|
|
||||||
// Perform actions whose associated epoch counter value is less than
|
// Perform actions whose associated epoch counter value is less than
|
||||||
// the given epoch counter value, and returns the number of actions performed.
|
// the given epoch counter value, and returns the number of actions performed.
|
||||||
|
@ -37,7 +35,7 @@ public:
|
||||||
EpochActionManager(const EpochActionManager&) = delete;
|
EpochActionManager(const EpochActionManager&) = delete;
|
||||||
EpochActionManager& operator=(const EpochActionManager&) = delete;
|
EpochActionManager& operator=(const EpochActionManager&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Mutex = Utils::CriticalSection;
|
using Mutex = Utils::CriticalSection;
|
||||||
using Lock = std::lock_guard<Mutex>;
|
using Lock = std::lock_guard<Mutex>;
|
||||||
|
|
||||||
|
@ -47,7 +45,8 @@ private:
|
||||||
// If the performance of using std::map becomes an issue, we can revisit this.
|
// If the performance of using std::map becomes an issue, we can revisit this.
|
||||||
using EpochToActions = std::map<std::uint64_t, Actions>;
|
using EpochToActions = std::map<std::uint64_t, Actions>;
|
||||||
|
|
||||||
using EpochToActionsWithLock = std::tuple<std::unique_ptr<Mutex>, EpochToActions>;
|
using EpochToActionsWithLock =
|
||||||
|
std::tuple<std::unique_ptr<Mutex>, EpochToActions>;
|
||||||
|
|
||||||
// Run actions based on the configuration.
|
// Run actions based on the configuration.
|
||||||
void ApplyActions(Actions& actions);
|
void ApplyActions(Actions& actions);
|
||||||
|
@ -59,5 +58,4 @@ private:
|
||||||
std::atomic<std::uint32_t> m_counter;
|
std::atomic<std::uint32_t> m_counter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace L4
|
} // namespace L4
|
|
@ -7,35 +7,30 @@
|
||||||
#include "Utils/Exception.h"
|
#include "Utils/Exception.h"
|
||||||
#include "Utils/Lock.h"
|
#include "Utils/Lock.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
// EpochQueue struct represents reference counts for each epoch.
|
// EpochQueue struct represents reference counts for each epoch.
|
||||||
// Each value of the queue (fixed-size array) is the reference counts at an index,
|
// Each value of the queue (fixed-size array) is the reference counts at an
|
||||||
// where an index represents an epoch (time).
|
// index, where an index represents an epoch (time).
|
||||||
template <
|
template <typename TSharableLock,
|
||||||
typename TSharableLock,
|
|
||||||
typename TExclusiveLock,
|
typename TExclusiveLock,
|
||||||
typename Allocator = std::allocator<void>
|
typename Allocator = std::allocator<void> >
|
||||||
>
|
struct EpochQueue {
|
||||||
struct EpochQueue
|
static_assert(std::is_same<typename TSharableLock::mutex_type,
|
||||||
{
|
typename TExclusiveLock::mutex_type>::value,
|
||||||
static_assert(
|
|
||||||
std::is_same<typename TSharableLock::mutex_type, typename TExclusiveLock::mutex_type>::value,
|
|
||||||
"mutex type should be the same");
|
"mutex type should be the same");
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EpochQueue(
|
EpochQueue(std::uint64_t epochCounter,
|
||||||
std::uint64_t epochCounter,
|
|
||||||
std::uint32_t queueSize,
|
std::uint32_t queueSize,
|
||||||
Allocator allocator = Allocator())
|
Allocator allocator = Allocator())
|
||||||
: m_frontIndex{ epochCounter }
|
: m_frontIndex{epochCounter},
|
||||||
, m_backIndex{ epochCounter }
|
m_backIndex{epochCounter},
|
||||||
, m_mutexForBackIndex{}
|
m_mutexForBackIndex{},
|
||||||
, m_refCounts{ queueSize, typename Allocator::template rebind<RefCount>::other(allocator) }
|
m_refCounts{
|
||||||
{
|
queueSize,
|
||||||
if (queueSize == 0U)
|
typename Allocator::template rebind<RefCount>::other(allocator)} {
|
||||||
{
|
if (queueSize == 0U) {
|
||||||
throw RuntimeException("Zero queue size is not allowed.");
|
throw RuntimeException("Zero queue size is not allowed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,9 +38,8 @@ public:
|
||||||
using SharableLock = TSharableLock;
|
using SharableLock = TSharableLock;
|
||||||
using ExclusiveLock = TExclusiveLock;
|
using ExclusiveLock = TExclusiveLock;
|
||||||
using RefCount = std::atomic<std::uint32_t>;
|
using RefCount = std::atomic<std::uint32_t>;
|
||||||
using RefCounts = Interprocess::Container::Vector<
|
using RefCounts = Interprocess::Container::
|
||||||
RefCount,
|
Vector<RefCount, typename Allocator::template rebind<RefCount>::other>;
|
||||||
typename Allocator::template rebind<RefCount>::other>;
|
|
||||||
|
|
||||||
// The followings (m_frontIndex and m_backIndex) are
|
// The followings (m_frontIndex and m_backIndex) are
|
||||||
// accessed/updated only by the owner thread (only one thread), thus
|
// accessed/updated only by the owner thread (only one thread), thus
|
||||||
|
@ -61,42 +55,38 @@ public:
|
||||||
typename SharableLock::mutex_type m_mutexForBackIndex;
|
typename SharableLock::mutex_type m_mutexForBackIndex;
|
||||||
|
|
||||||
// Reference counts per epoch count.
|
// Reference counts per epoch count.
|
||||||
// The index represents the epoch counter value and the value represents the reference counts.
|
// The index represents the epoch counter value and the value represents the
|
||||||
|
// reference counts.
|
||||||
RefCounts m_refCounts;
|
RefCounts m_refCounts;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// EpochRefManager provides functionality of adding/removing references
|
// EpochRefManager provides functionality of adding/removing references
|
||||||
// to the epoch counter.
|
// to the epoch counter.
|
||||||
template <typename EpochQueue>
|
template <typename EpochQueue>
|
||||||
class EpochRefManager
|
class EpochRefManager {
|
||||||
{
|
public:
|
||||||
public:
|
explicit EpochRefManager(EpochQueue& epochQueue) : m_epochQueue(epochQueue) {}
|
||||||
explicit EpochRefManager(EpochQueue& epochQueue)
|
|
||||||
: m_epochQueue(epochQueue)
|
|
||||||
{}
|
|
||||||
|
|
||||||
// Increment a reference to the current epoch counter.
|
// Increment a reference to the current epoch counter.
|
||||||
// This function is thread-safe.
|
// This function is thread-safe.
|
||||||
std::uint64_t AddRef()
|
std::uint64_t AddRef() {
|
||||||
{
|
|
||||||
// The synchronization is needed for EpochCounterManager::AddNewEpoch().
|
// The synchronization is needed for EpochCounterManager::AddNewEpoch().
|
||||||
typename EpochQueue::SharableLock lock(m_epochQueue.m_mutexForBackIndex);
|
typename EpochQueue::SharableLock lock(m_epochQueue.m_mutexForBackIndex);
|
||||||
|
|
||||||
++m_epochQueue.m_refCounts[m_epochQueue.m_backIndex % m_epochQueue.m_refCounts.size()];
|
++m_epochQueue.m_refCounts[m_epochQueue.m_backIndex %
|
||||||
|
m_epochQueue.m_refCounts.size()];
|
||||||
|
|
||||||
return m_epochQueue.m_backIndex;
|
return m_epochQueue.m_backIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Decrement a reference count for the given epoch counter.
|
// Decrement a reference count for the given epoch counter.
|
||||||
// This function is thread-safe.
|
// This function is thread-safe.
|
||||||
void RemoveRef(std::uint64_t epochCounter)
|
void RemoveRef(std::uint64_t epochCounter) {
|
||||||
{
|
auto& refCounter =
|
||||||
auto& refCounter = m_epochQueue.m_refCounts[epochCounter % m_epochQueue.m_refCounts.size()];
|
m_epochQueue
|
||||||
|
.m_refCounts[epochCounter % m_epochQueue.m_refCounts.size()];
|
||||||
|
|
||||||
if (refCounter == 0)
|
if (refCounter == 0) {
|
||||||
{
|
|
||||||
throw RuntimeException("Reference counter is invalid.");
|
throw RuntimeException("Reference counter is invalid.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,25 +96,21 @@ public:
|
||||||
EpochRefManager(const EpochRefManager&) = delete;
|
EpochRefManager(const EpochRefManager&) = delete;
|
||||||
EpochRefManager& operator=(const EpochRefManager&) = delete;
|
EpochRefManager& operator=(const EpochRefManager&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EpochQueue& m_epochQueue;
|
EpochQueue& m_epochQueue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// EpochCounterManager provides functionality of updating the current epoch
|
||||||
// EpochCounterManager provides functionality of updating the current epoch counter
|
// counter and getting the latest unreferenced epoch counter.
|
||||||
// and getting the latest unreferenced epoch counter.
|
|
||||||
template <typename EpochQueue>
|
template <typename EpochQueue>
|
||||||
class EpochCounterManager
|
class EpochCounterManager {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
explicit EpochCounterManager(EpochQueue& epochQueue)
|
explicit EpochCounterManager(EpochQueue& epochQueue)
|
||||||
: m_epochQueue(epochQueue)
|
: m_epochQueue(epochQueue) {}
|
||||||
{}
|
|
||||||
|
|
||||||
// Increments the current epoch count by one.
|
// Increments the current epoch count by one.
|
||||||
// This function is thread-safe.
|
// This function is thread-safe.
|
||||||
void AddNewEpoch()
|
void AddNewEpoch() {
|
||||||
{
|
|
||||||
// The synchronization is needed for EpochRefManager::AddRef().
|
// The synchronization is needed for EpochRefManager::AddRef().
|
||||||
typename EpochQueue::ExclusiveLock lock(m_epochQueue.m_mutexForBackIndex);
|
typename EpochQueue::ExclusiveLock lock(m_epochQueue.m_mutexForBackIndex);
|
||||||
|
|
||||||
|
@ -137,17 +123,14 @@ public:
|
||||||
// count such that all other epoch counts' references are zeros.
|
// count such that all other epoch counts' references are zeros.
|
||||||
// Note that this function is NOT thread safe, and should be run on the
|
// Note that this function is NOT thread safe, and should be run on the
|
||||||
// same thread as the one that calls AddNewEpoch().
|
// same thread as the one that calls AddNewEpoch().
|
||||||
std::uint64_t RemoveUnreferenceEpochCounters()
|
std::uint64_t RemoveUnreferenceEpochCounters() {
|
||||||
{
|
while (m_epochQueue.m_backIndex > m_epochQueue.m_frontIndex) {
|
||||||
while (m_epochQueue.m_backIndex > m_epochQueue.m_frontIndex)
|
if (m_epochQueue.m_refCounts[m_epochQueue.m_frontIndex %
|
||||||
{
|
m_epochQueue.m_refCounts.size()] == 0U) {
|
||||||
if (m_epochQueue.m_refCounts[m_epochQueue.m_frontIndex % m_epochQueue.m_refCounts.size()] == 0U)
|
|
||||||
{
|
|
||||||
++m_epochQueue.m_frontIndex;
|
++m_epochQueue.m_frontIndex;
|
||||||
}
|
} else {
|
||||||
else
|
// There are references to the front of the queue and will return this
|
||||||
{
|
// front index.
|
||||||
// There are references to the front of the queue and will return this front index.
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,7 +141,7 @@ public:
|
||||||
EpochCounterManager(const EpochCounterManager&) = delete;
|
EpochCounterManager(const EpochCounterManager&) = delete;
|
||||||
EpochCounterManager& operator=(const EpochCounterManager&) = delete;
|
EpochCounterManager& operator=(const EpochCounterManager&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EpochQueue& m_epochQueue;
|
EpochQueue& m_epochQueue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,27 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <boost/integer_traits.hpp>
|
#include <boost/integer_traits.hpp>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
// EpochRefPolicy class
|
// EpochRefPolicy class
|
||||||
template <typename EpochRefManager>
|
template <typename EpochRefManager>
|
||||||
class EpochRefPolicy
|
class EpochRefPolicy {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
explicit EpochRefPolicy(EpochRefManager& epochRefManager)
|
explicit EpochRefPolicy(EpochRefManager& epochRefManager)
|
||||||
: m_epochRefManager{ epochRefManager }
|
: m_epochRefManager{epochRefManager},
|
||||||
, m_epochCounter{ m_epochRefManager.AddRef() }
|
m_epochCounter{m_epochRefManager.AddRef()} {}
|
||||||
{}
|
|
||||||
|
|
||||||
EpochRefPolicy(EpochRefPolicy&& epochRefPolicy)
|
EpochRefPolicy(EpochRefPolicy&& epochRefPolicy)
|
||||||
: m_epochRefManager{ epochRefPolicy.m_epochRefManager }
|
: m_epochRefManager{epochRefPolicy.m_epochRefManager},
|
||||||
, m_epochCounter{ epochRefPolicy.m_epochCounter }
|
m_epochCounter{epochRefPolicy.m_epochCounter} {
|
||||||
{
|
epochRefPolicy.m_epochCounter =
|
||||||
epochRefPolicy.m_epochCounter = boost::integer_traits<std::uint64_t>::const_max;
|
boost::integer_traits<std::uint64_t>::const_max;
|
||||||
}
|
}
|
||||||
|
|
||||||
~EpochRefPolicy()
|
~EpochRefPolicy() {
|
||||||
{
|
if (m_epochCounter != boost::integer_traits<std::uint64_t>::const_max) {
|
||||||
if (m_epochCounter != boost::integer_traits<std::uint64_t>::const_max)
|
|
||||||
{
|
|
||||||
m_epochRefManager.RemoveRef(m_epochCounter);
|
m_epochRefManager.RemoveRef(m_epochCounter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +29,7 @@ public:
|
||||||
EpochRefPolicy(const EpochRefPolicy&) = delete;
|
EpochRefPolicy(const EpochRefPolicy&) = delete;
|
||||||
EpochRefPolicy& operator=(const EpochRefPolicy&) = delete;
|
EpochRefPolicy& operator=(const EpochRefPolicy&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EpochRefManager& m_epochRefManager;
|
EpochRefManager& m_epochRefManager;
|
||||||
std::uint64_t m_epochCounter;
|
std::uint64_t m_epochCounter;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,21 +2,17 @@
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
// IEpochActionManager interface exposes an API for registering an Action.
|
// IEpochActionManager interface exposes an API for registering an Action.
|
||||||
struct IEpochActionManager
|
struct IEpochActionManager {
|
||||||
{
|
|
||||||
using Action = std::function<void()>;
|
using Action = std::function<void()>;
|
||||||
|
|
||||||
virtual ~IEpochActionManager() {};
|
virtual ~IEpochActionManager(){};
|
||||||
|
|
||||||
// Register actions on the latest epoch in the queue and the action is
|
// Register actions on the latest epoch in the queue and the action is
|
||||||
// performed when the epoch is removed from the queue.
|
// performed when the epoch is removed from the queue.
|
||||||
virtual void RegisterAction(Action&& action) = 0;
|
virtual void RegisterAction(Action&& action) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace L4
|
} // namespace L4
|
|
@ -4,28 +4,24 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include "detail/ToRawPointer.h"
|
|
||||||
#include "Epoch/IEpochActionManager.h"
|
#include "Epoch/IEpochActionManager.h"
|
||||||
|
#include "HashTable/Cache/Metadata.h"
|
||||||
#include "HashTable/IHashTable.h"
|
#include "HashTable/IHashTable.h"
|
||||||
#include "HashTable/ReadWrite/HashTable.h"
|
#include "HashTable/ReadWrite/HashTable.h"
|
||||||
#include "HashTable/Cache/Metadata.h"
|
|
||||||
#include "Utils/Clock.h"
|
#include "Utils/Clock.h"
|
||||||
|
#include "detail/ToRawPointer.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace HashTable {
|
||||||
namespace HashTable
|
namespace Cache {
|
||||||
{
|
|
||||||
namespace Cache
|
|
||||||
{
|
|
||||||
|
|
||||||
// ReadOnlyHashTable class implements IReadOnlyHashTable interface and provides
|
// ReadOnlyHashTable class implements IReadOnlyHashTable interface and provides
|
||||||
// the functionality to read data given a key.
|
// the functionality to read data given a key.
|
||||||
template <typename Allocator, typename Clock = Utils::EpochClock>
|
template <typename Allocator, typename Clock = Utils::EpochClock>
|
||||||
class ReadOnlyHashTable
|
class ReadOnlyHashTable
|
||||||
: public virtual ReadWrite::ReadOnlyHashTable<Allocator>
|
: public virtual ReadWrite::ReadOnlyHashTable<Allocator>,
|
||||||
, protected Clock
|
protected Clock {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
using Base = ReadWrite::ReadOnlyHashTable<Allocator>;
|
using Base = ReadWrite::ReadOnlyHashTable<Allocator>;
|
||||||
using HashTable = typename Base::HashTable;
|
using HashTable = typename Base::HashTable;
|
||||||
|
|
||||||
|
@ -35,58 +31,48 @@ public:
|
||||||
|
|
||||||
class Iterator;
|
class Iterator;
|
||||||
|
|
||||||
ReadOnlyHashTable(
|
ReadOnlyHashTable(HashTable& hashTable, std::chrono::seconds recordTimeToLive)
|
||||||
HashTable& hashTable,
|
: Base(hashTable,
|
||||||
std::chrono::seconds recordTimeToLive)
|
RecordSerializer{hashTable.m_setting.m_fixedKeySize,
|
||||||
: Base(
|
|
||||||
hashTable,
|
|
||||||
RecordSerializer{
|
|
||||||
hashTable.m_setting.m_fixedKeySize,
|
|
||||||
hashTable.m_setting.m_fixedValueSize,
|
hashTable.m_setting.m_fixedValueSize,
|
||||||
Metadata::c_metaDataSize })
|
Metadata::c_metaDataSize}),
|
||||||
, m_recordTimeToLive{ recordTimeToLive }
|
m_recordTimeToLive{recordTimeToLive} {}
|
||||||
{}
|
|
||||||
|
|
||||||
virtual bool Get(const Key& key, Value& value) const override
|
virtual bool Get(const Key& key, Value& value) const override {
|
||||||
{
|
|
||||||
const auto status = GetInternal(key, value);
|
const auto status = GetInternal(key, value);
|
||||||
|
|
||||||
// Note that the following const_cast is safe and necessary to update cache hit information.
|
// Note that the following const_cast is safe and necessary to update cache
|
||||||
const_cast<HashTablePerfData&>(this->GetPerfData()).Increment(
|
// hit information.
|
||||||
status
|
const_cast<HashTablePerfData&>(this->GetPerfData())
|
||||||
? HashTablePerfCounter::CacheHitCount
|
.Increment(status ? HashTablePerfCounter::CacheHitCount
|
||||||
: HashTablePerfCounter::CacheMissCount);
|
: HashTablePerfCounter::CacheMissCount);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual IIteratorPtr GetIterator() const override
|
virtual IIteratorPtr GetIterator() const override {
|
||||||
{
|
|
||||||
return std::make_unique<Iterator>(
|
return std::make_unique<Iterator>(
|
||||||
this->m_hashTable,
|
this->m_hashTable, this->m_recordSerializer, m_recordTimeToLive,
|
||||||
this->m_recordSerializer,
|
|
||||||
m_recordTimeToLive,
|
|
||||||
this->GetCurrentEpochTime());
|
this->GetCurrentEpochTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlyHashTable(const ReadOnlyHashTable&) = delete;
|
ReadOnlyHashTable(const ReadOnlyHashTable&) = delete;
|
||||||
ReadOnlyHashTable& operator=(const ReadOnlyHashTable&) = delete;
|
ReadOnlyHashTable& operator=(const ReadOnlyHashTable&) = delete;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool GetInternal(const Key& key, Value& value) const
|
bool GetInternal(const Key& key, Value& value) const {
|
||||||
{
|
if (!Base::Get(key, value)) {
|
||||||
if (!Base::Get(key, value))
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(value.m_size > Metadata::c_metaDataSize);
|
assert(value.m_size > Metadata::c_metaDataSize);
|
||||||
|
|
||||||
// If the record with the given key is found, check if the record is expired or not.
|
// If the record with the given key is found, check if the record is expired
|
||||||
// Note that the following const_cast is safe and necessary to update the access status.
|
// or not. Note that the following const_cast is safe and necessary to
|
||||||
Metadata metaData{ const_cast<std::uint32_t*>(reinterpret_cast<const std::uint32_t*>(value.m_data)) };
|
// update the access status.
|
||||||
if (metaData.IsExpired(this->GetCurrentEpochTime(), m_recordTimeToLive))
|
Metadata metaData{const_cast<std::uint32_t*>(
|
||||||
{
|
reinterpret_cast<const std::uint32_t*>(value.m_data))};
|
||||||
|
if (metaData.IsExpired(this->GetCurrentEpochTime(), m_recordTimeToLive)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,45 +87,35 @@ protected:
|
||||||
std::chrono::seconds m_recordTimeToLive;
|
std::chrono::seconds m_recordTimeToLive;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
template <typename Allocator, typename Clock>
|
template <typename Allocator, typename Clock>
|
||||||
class ReadOnlyHashTable<Allocator, Clock>::Iterator : public Base::Iterator
|
class ReadOnlyHashTable<Allocator, Clock>::Iterator : public Base::Iterator {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
using BaseIterator = typename Base::Iterator;
|
using BaseIterator = typename Base::Iterator;
|
||||||
|
|
||||||
Iterator(
|
Iterator(const HashTable& hashTable,
|
||||||
const HashTable& hashTable,
|
|
||||||
const RecordSerializer& recordDeserializer,
|
const RecordSerializer& recordDeserializer,
|
||||||
std::chrono::seconds recordTimeToLive,
|
std::chrono::seconds recordTimeToLive,
|
||||||
std::chrono::seconds currentEpochTime)
|
std::chrono::seconds currentEpochTime)
|
||||||
: BaseIterator(hashTable, recordDeserializer)
|
: BaseIterator(hashTable, recordDeserializer),
|
||||||
, m_recordTimeToLive{ recordTimeToLive }
|
m_recordTimeToLive{recordTimeToLive},
|
||||||
, m_currentEpochTime{ currentEpochTime }
|
m_currentEpochTime{currentEpochTime} {}
|
||||||
{}
|
|
||||||
|
|
||||||
Iterator(Iterator&& other)
|
Iterator(Iterator&& other)
|
||||||
: BaseIterator(std::move(other))
|
: BaseIterator(std::move(other)),
|
||||||
, m_recordTimeToLive{ std::move(other.m_recordTimeToLive) }
|
m_recordTimeToLive{std::move(other.m_recordTimeToLive)},
|
||||||
, m_currentEpochTime{ std::move(other.m_currentEpochTime) }
|
m_currentEpochTime{std::move(other.m_currentEpochTime)} {}
|
||||||
{}
|
|
||||||
|
|
||||||
bool MoveNext() override
|
bool MoveNext() override {
|
||||||
{
|
if (!BaseIterator::MoveNext()) {
|
||||||
if (!BaseIterator::MoveNext())
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
do
|
do {
|
||||||
{
|
|
||||||
const Metadata metaData{
|
const Metadata metaData{
|
||||||
const_cast<std::uint32_t*>(
|
const_cast<std::uint32_t*>(reinterpret_cast<const std::uint32_t*>(
|
||||||
reinterpret_cast<const std::uint32_t*>(
|
BaseIterator::GetValue().m_data))};
|
||||||
BaseIterator::GetValue().m_data)) };
|
|
||||||
|
|
||||||
if (!metaData.IsExpired(m_currentEpochTime, m_recordTimeToLive))
|
if (!metaData.IsExpired(m_currentEpochTime, m_recordTimeToLive)) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} while (BaseIterator::MoveNext());
|
} while (BaseIterator::MoveNext());
|
||||||
|
@ -147,8 +123,7 @@ public:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value GetValue() const override
|
Value GetValue() const override {
|
||||||
{
|
|
||||||
auto value = BaseIterator::GetValue();
|
auto value = BaseIterator::GetValue();
|
||||||
value.m_data += Metadata::c_metaDataSize;
|
value.m_data += Metadata::c_metaDataSize;
|
||||||
value.m_size -= Metadata::c_metaDataSize;
|
value.m_size -= Metadata::c_metaDataSize;
|
||||||
|
@ -156,25 +131,22 @@ public:
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::chrono::seconds m_recordTimeToLive;
|
std::chrono::seconds m_recordTimeToLive;
|
||||||
std::chrono::seconds m_currentEpochTime;
|
std::chrono::seconds m_currentEpochTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The following warning is from the virtual inheritance and safe to disable in
|
||||||
// The following warning is from the virtual inheritance and safe to disable in this case.
|
// this case. https://msdn.microsoft.com/en-us/library/6b3sy7ae.aspx
|
||||||
// https://msdn.microsoft.com/en-us/library/6b3sy7ae.aspx
|
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable:4250)
|
#pragma warning(disable : 4250)
|
||||||
|
|
||||||
// WritableHashTable class implements IWritableHashTable interface and also provides
|
// WritableHashTable class implements IWritableHashTable interface and also
|
||||||
// the read only access (Get()) to the hash table.
|
// provides the read only access (Get()) to the hash table.
|
||||||
template <typename Allocator, typename Clock = Utils::EpochClock>
|
template <typename Allocator, typename Clock = Utils::EpochClock>
|
||||||
class WritableHashTable
|
class WritableHashTable : public ReadOnlyHashTable<Allocator, Clock>,
|
||||||
: public ReadOnlyHashTable<Allocator, Clock>
|
public ReadWrite::WritableHashTable<Allocator> {
|
||||||
, public ReadWrite::WritableHashTable<Allocator>
|
public:
|
||||||
{
|
|
||||||
public:
|
|
||||||
using ReadOnlyBase = ReadOnlyHashTable<Allocator, Clock>;
|
using ReadOnlyBase = ReadOnlyHashTable<Allocator, Clock>;
|
||||||
using WritableBase = typename ReadWrite::WritableHashTable<Allocator>;
|
using WritableBase = typename ReadWrite::WritableHashTable<Allocator>;
|
||||||
using HashTable = typename ReadOnlyBase::HashTable;
|
using HashTable = typename ReadOnlyBase::HashTable;
|
||||||
|
@ -183,32 +155,27 @@ public:
|
||||||
using Value = typename ReadOnlyBase::Value;
|
using Value = typename ReadOnlyBase::Value;
|
||||||
using ISerializerPtr = typename WritableBase::ISerializerPtr;
|
using ISerializerPtr = typename WritableBase::ISerializerPtr;
|
||||||
|
|
||||||
WritableHashTable(
|
WritableHashTable(HashTable& hashTable,
|
||||||
HashTable& hashTable,
|
|
||||||
IEpochActionManager& epochManager,
|
IEpochActionManager& epochManager,
|
||||||
std::uint64_t maxCacheSizeInBytes,
|
std::uint64_t maxCacheSizeInBytes,
|
||||||
std::chrono::seconds recordTimeToLive,
|
std::chrono::seconds recordTimeToLive,
|
||||||
bool forceTimeBasedEviction)
|
bool forceTimeBasedEviction)
|
||||||
: ReadOnlyBase::Base(
|
: ReadOnlyBase::Base(
|
||||||
hashTable,
|
hashTable,
|
||||||
RecordSerializer{
|
RecordSerializer{hashTable.m_setting.m_fixedKeySize,
|
||||||
hashTable.m_setting.m_fixedKeySize,
|
|
||||||
hashTable.m_setting.m_fixedValueSize,
|
hashTable.m_setting.m_fixedValueSize,
|
||||||
Metadata::c_metaDataSize })
|
Metadata::c_metaDataSize}),
|
||||||
, ReadOnlyBase(hashTable, recordTimeToLive)
|
ReadOnlyBase(hashTable, recordTimeToLive),
|
||||||
, WritableBase(hashTable, epochManager)
|
WritableBase(hashTable, epochManager),
|
||||||
, m_maxCacheSizeInBytes{ maxCacheSizeInBytes }
|
m_maxCacheSizeInBytes{maxCacheSizeInBytes},
|
||||||
, m_forceTimeBasedEviction{ forceTimeBasedEviction }
|
m_forceTimeBasedEviction{forceTimeBasedEviction},
|
||||||
, m_currentEvictBucketIndex{ 0U }
|
m_currentEvictBucketIndex{0U} {}
|
||||||
{}
|
|
||||||
|
|
||||||
using ReadOnlyBase::Get;
|
using ReadOnlyBase::Get;
|
||||||
using ReadOnlyBase::GetPerfData;
|
using ReadOnlyBase::GetPerfData;
|
||||||
|
|
||||||
virtual void Add(const Key& key, const Value& value) override
|
virtual void Add(const Key& key, const Value& value) override {
|
||||||
{
|
if (m_forceTimeBasedEviction) {
|
||||||
if (m_forceTimeBasedEviction)
|
|
||||||
{
|
|
||||||
EvictBasedOnTime(key);
|
EvictBasedOnTime(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,42 +184,36 @@ public:
|
||||||
WritableBase::Add(CreateRecordBuffer(key, value));
|
WritableBase::Add(CreateRecordBuffer(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ISerializerPtr GetSerializer() const override
|
virtual ISerializerPtr GetSerializer() const override {
|
||||||
{
|
|
||||||
throw std::runtime_error("Not implemented yet.");
|
throw std::runtime_error("Not implemented yet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Mutex = std::mutex;
|
using Mutex = std::mutex;
|
||||||
using Lock = std::lock_guard<Mutex>;
|
using Lock = std::lock_guard<Mutex>;
|
||||||
|
|
||||||
void EvictBasedOnTime(const Key& key)
|
void EvictBasedOnTime(const Key& key) {
|
||||||
{
|
|
||||||
const auto bucketIndex = this->GetBucketInfo(key).first;
|
const auto bucketIndex = this->GetBucketInfo(key).first;
|
||||||
|
|
||||||
auto* entry = &(this->m_hashTable.m_buckets[bucketIndex]);
|
auto* entry = &(this->m_hashTable.m_buckets[bucketIndex]);
|
||||||
|
|
||||||
const auto curEpochTime = this->GetCurrentEpochTime();
|
const auto curEpochTime = this->GetCurrentEpochTime();
|
||||||
|
|
||||||
typename HashTable::Lock lock{ this->m_hashTable.GetMutex(bucketIndex) };
|
typename HashTable::Lock lock{this->m_hashTable.GetMutex(bucketIndex)};
|
||||||
|
|
||||||
while (entry != nullptr)
|
while (entry != nullptr) {
|
||||||
{
|
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) {
|
||||||
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i)
|
|
||||||
{
|
|
||||||
const auto data = entry->m_dataList[i].Load(std::memory_order_relaxed);
|
const auto data = entry->m_dataList[i].Load(std::memory_order_relaxed);
|
||||||
|
|
||||||
if (data != nullptr)
|
if (data != nullptr) {
|
||||||
{
|
|
||||||
const Metadata metadata{
|
const Metadata metadata{
|
||||||
const_cast<std::uint32_t*>(
|
const_cast<std::uint32_t*>(reinterpret_cast<const std::uint32_t*>(
|
||||||
reinterpret_cast<const std::uint32_t*>(
|
this->m_recordSerializer.Deserialize(*data).m_value.m_data))};
|
||||||
this->m_recordSerializer.Deserialize(*data).m_value.m_data)) };
|
|
||||||
|
|
||||||
if (metadata.IsExpired(curEpochTime, this->m_recordTimeToLive))
|
if (metadata.IsExpired(curEpochTime, this->m_recordTimeToLive)) {
|
||||||
{
|
|
||||||
WritableBase::Remove(*entry, i);
|
WritableBase::Remove(*entry, i);
|
||||||
this->m_hashTable.m_perfData.Increment(HashTablePerfCounter::EvictedRecordsCount);
|
this->m_hashTable.m_perfData.Increment(
|
||||||
|
HashTablePerfCounter::EvictedRecordsCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,72 +222,73 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evict uses CLOCK algorithm to evict records based on expiration and access status
|
// Evict uses CLOCK algorithm to evict records based on expiration and access
|
||||||
// until the number of bytes freed match the given number of bytes needed.
|
// status until the number of bytes freed match the given number of bytes
|
||||||
void Evict(std::uint64_t bytesNeeded)
|
// needed.
|
||||||
{
|
void Evict(std::uint64_t bytesNeeded) {
|
||||||
std::uint64_t numBytesToFree = CalculateNumBytesToFree(bytesNeeded);
|
std::uint64_t numBytesToFree = CalculateNumBytesToFree(bytesNeeded);
|
||||||
if (numBytesToFree == 0U)
|
if (numBytesToFree == 0U) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start evicting records with a lock.
|
// Start evicting records with a lock.
|
||||||
Lock evictLock{ m_evictMutex };
|
Lock evictLock{m_evictMutex};
|
||||||
|
|
||||||
// Recalculate the number of bytes to free since other thread may have already evicted.
|
// Recalculate the number of bytes to free since other thread may have
|
||||||
|
// already evicted.
|
||||||
numBytesToFree = CalculateNumBytesToFree(bytesNeeded);
|
numBytesToFree = CalculateNumBytesToFree(bytesNeeded);
|
||||||
if (numBytesToFree == 0U)
|
if (numBytesToFree == 0U) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto curEpochTime = this->GetCurrentEpochTime();
|
const auto curEpochTime = this->GetCurrentEpochTime();
|
||||||
|
|
||||||
// The max number of iterations we are going through per eviction is twice the number
|
// The max number of iterations we are going through per eviction is twice
|
||||||
// of buckets so that it can clear the access status. Note that this is the worst
|
// the number of buckets so that it can clear the access status. Note that
|
||||||
// case scenario and the eviction process should exit much quicker in a normal case.
|
// this is the worst case scenario and the eviction process should exit much
|
||||||
|
// quicker in a normal case.
|
||||||
auto& buckets = this->m_hashTable.m_buckets;
|
auto& buckets = this->m_hashTable.m_buckets;
|
||||||
std::uint64_t numIterationsRemaining = buckets.size() * 2U;
|
std::uint64_t numIterationsRemaining = buckets.size() * 2U;
|
||||||
|
|
||||||
while (numBytesToFree > 0U && numIterationsRemaining-- > 0U)
|
while (numBytesToFree > 0U && numIterationsRemaining-- > 0U) {
|
||||||
{
|
const auto currentBucketIndex =
|
||||||
const auto currentBucketIndex = m_currentEvictBucketIndex++ % buckets.size();
|
m_currentEvictBucketIndex++ % buckets.size();
|
||||||
auto& bucket = buckets[currentBucketIndex];
|
auto& bucket = buckets[currentBucketIndex];
|
||||||
|
|
||||||
// Lock the bucket since another thread can bypass Evict() since TotalDataSize can
|
// Lock the bucket since another thread can bypass Evict() since
|
||||||
// be updated before the lock on m_evictMutex is released.
|
// TotalDataSize can be updated before the lock on m_evictMutex is
|
||||||
typename HashTable::UniqueLock lock{ this->m_hashTable.GetMutex(currentBucketIndex) };
|
// released.
|
||||||
|
typename HashTable::UniqueLock lock{
|
||||||
|
this->m_hashTable.GetMutex(currentBucketIndex)};
|
||||||
typename HashTable::Entry* entry = &bucket;
|
typename HashTable::Entry* entry = &bucket;
|
||||||
|
|
||||||
while (entry != nullptr)
|
while (entry != nullptr) {
|
||||||
{
|
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) {
|
||||||
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i)
|
const auto data =
|
||||||
{
|
entry->m_dataList[i].Load(std::memory_order_relaxed);
|
||||||
const auto data = entry->m_dataList[i].Load(std::memory_order_relaxed);
|
|
||||||
|
|
||||||
if (data != nullptr)
|
if (data != nullptr) {
|
||||||
{
|
|
||||||
const auto record = this->m_recordSerializer.Deserialize(*data);
|
const auto record = this->m_recordSerializer.Deserialize(*data);
|
||||||
const auto& value = record.m_value;
|
const auto& value = record.m_value;
|
||||||
|
|
||||||
Metadata metadata{
|
Metadata metadata{const_cast<std::uint32_t*>(
|
||||||
const_cast<std::uint32_t*>(
|
reinterpret_cast<const std::uint32_t*>(value.m_data))};
|
||||||
reinterpret_cast<const std::uint32_t*>(
|
|
||||||
value.m_data)) };
|
|
||||||
|
|
||||||
// Evict this record if
|
// Evict this record if
|
||||||
// 1: the record is expired, or
|
// 1: the record is expired, or
|
||||||
// 2: the entry is not recently accessed (and unset the access bit if set).
|
// 2: the entry is not recently accessed (and unset the access bit
|
||||||
if (metadata.IsExpired(curEpochTime, this->m_recordTimeToLive)
|
// if set).
|
||||||
|| !metadata.UpdateAccessStatus(false))
|
if (metadata.IsExpired(curEpochTime, this->m_recordTimeToLive) ||
|
||||||
{
|
!metadata.UpdateAccessStatus(false)) {
|
||||||
const auto numBytesFreed = record.m_key.m_size + value.m_size;
|
const auto numBytesFreed = record.m_key.m_size + value.m_size;
|
||||||
numBytesToFree = (numBytesFreed >= numBytesToFree) ? 0U : numBytesToFree - numBytesFreed;
|
numBytesToFree = (numBytesFreed >= numBytesToFree)
|
||||||
|
? 0U
|
||||||
|
: numBytesToFree - numBytesFreed;
|
||||||
|
|
||||||
WritableBase::Remove(*entry, i);
|
WritableBase::Remove(*entry, i);
|
||||||
|
|
||||||
this->m_hashTable.m_perfData.Increment(HashTablePerfCounter::EvictedRecordsCount);
|
this->m_hashTable.m_perfData.Increment(
|
||||||
|
HashTablePerfCounter::EvictedRecordsCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,18 +300,16 @@ private:
|
||||||
|
|
||||||
// Given the number of bytes needed, it calculates the number of bytes
|
// Given the number of bytes needed, it calculates the number of bytes
|
||||||
// to free based on the max cache size.
|
// to free based on the max cache size.
|
||||||
std::uint64_t CalculateNumBytesToFree(std::uint64_t bytesNeeded) const
|
std::uint64_t CalculateNumBytesToFree(std::uint64_t bytesNeeded) const {
|
||||||
{
|
|
||||||
const auto& perfData = GetPerfData();
|
const auto& perfData = GetPerfData();
|
||||||
|
|
||||||
const std::uint64_t totalDataSize =
|
const std::uint64_t totalDataSize =
|
||||||
perfData.Get(HashTablePerfCounter::TotalKeySize)
|
perfData.Get(HashTablePerfCounter::TotalKeySize) +
|
||||||
+ perfData.Get(HashTablePerfCounter::TotalValueSize)
|
perfData.Get(HashTablePerfCounter::TotalValueSize) +
|
||||||
+ perfData.Get(HashTablePerfCounter::TotalIndexSize);
|
perfData.Get(HashTablePerfCounter::TotalIndexSize);
|
||||||
|
|
||||||
if ((bytesNeeded < m_maxCacheSizeInBytes)
|
if ((bytesNeeded < m_maxCacheSizeInBytes) &&
|
||||||
&& (totalDataSize + bytesNeeded <= m_maxCacheSizeInBytes))
|
(totalDataSize + bytesNeeded <= m_maxCacheSizeInBytes)) {
|
||||||
{
|
|
||||||
// There are enough free bytes.
|
// There are enough free bytes.
|
||||||
return 0U;
|
return 0U;
|
||||||
}
|
}
|
||||||
|
@ -364,22 +324,22 @@ private:
|
||||||
: bytesNeeded;
|
: bytesNeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordBuffer* CreateRecordBuffer(const Key& key, const Value& value)
|
RecordBuffer* CreateRecordBuffer(const Key& key, const Value& value) {
|
||||||
{
|
const auto bufferSize =
|
||||||
const auto bufferSize = this->m_recordSerializer.CalculateBufferSize(key, value);
|
this->m_recordSerializer.CalculateBufferSize(key, value);
|
||||||
auto buffer = Detail::to_raw_pointer(
|
auto buffer = Detail::to_raw_pointer(
|
||||||
this->m_hashTable.template GetAllocator<std::uint8_t>().allocate(bufferSize));
|
this->m_hashTable.template GetAllocator<std::uint8_t>().allocate(
|
||||||
|
bufferSize));
|
||||||
|
|
||||||
std::uint32_t metaDataBuffer;
|
std::uint32_t metaDataBuffer;
|
||||||
Metadata{ &metaDataBuffer, this->GetCurrentEpochTime() };
|
Metadata{&metaDataBuffer, this->GetCurrentEpochTime()};
|
||||||
|
|
||||||
// 4-byte Metadata is inserted between key and value buffer.
|
// 4-byte Metadata is inserted between key and value buffer.
|
||||||
return this->m_recordSerializer.Serialize(
|
return this->m_recordSerializer.Serialize(
|
||||||
key,
|
key, value,
|
||||||
value,
|
Value{reinterpret_cast<std::uint8_t*>(&metaDataBuffer),
|
||||||
Value{ reinterpret_cast<std::uint8_t*>(&metaDataBuffer), sizeof(metaDataBuffer) },
|
sizeof(metaDataBuffer)},
|
||||||
buffer,
|
buffer, bufferSize);
|
||||||
bufferSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Mutex m_evictMutex;
|
Mutex m_evictMutex;
|
||||||
|
|
|
@ -4,75 +4,56 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace HashTable {
|
||||||
namespace HashTable
|
namespace Cache {
|
||||||
{
|
|
||||||
namespace Cache
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
// Metadata class that stores caching related data.
|
// Metadata class that stores caching related data.
|
||||||
// It stores access bit to indicate whether a record is recently accessed
|
// It stores access bit to indicate whether a record is recently accessed
|
||||||
// as well as the epoch time when a record is created.
|
// as well as the epoch time when a record is created.
|
||||||
// Note that this works regardless of the alignment of the metadata passed in.
|
// Note that this works regardless of the alignment of the metadata passed in.
|
||||||
class Metadata
|
class Metadata {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
// Constructs Metadata with the current epoch time.
|
// Constructs Metadata with the current epoch time.
|
||||||
Metadata(std::uint32_t* metadata, std::chrono::seconds curEpochTime)
|
Metadata(std::uint32_t* metadata, std::chrono::seconds curEpochTime)
|
||||||
: Metadata{ metadata }
|
: Metadata{metadata} {
|
||||||
{
|
|
||||||
*m_metadata = curEpochTime.count() & s_epochTimeMask;
|
*m_metadata = curEpochTime.count() & s_epochTimeMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit Metadata(std::uint32_t* metadata)
|
explicit Metadata(std::uint32_t* metadata) : m_metadata{metadata} {
|
||||||
: m_metadata{ metadata }
|
|
||||||
{
|
|
||||||
assert(m_metadata != nullptr);
|
assert(m_metadata != nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the stored epoch time.
|
// Returns the stored epoch time.
|
||||||
std::chrono::seconds GetEpochTime() const
|
std::chrono::seconds GetEpochTime() const {
|
||||||
{
|
|
||||||
// *m_metadata even on the not-aligned memory should be fine since
|
// *m_metadata even on the not-aligned memory should be fine since
|
||||||
// only the byte that contains the access bit is modified, and
|
// only the byte that contains the access bit is modified, and
|
||||||
// byte read is atomic.
|
// byte read is atomic.
|
||||||
return std::chrono::seconds{ *m_metadata & s_epochTimeMask };
|
return std::chrono::seconds{*m_metadata & s_epochTimeMask};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the stored epoch time is expired based
|
// Returns true if the stored epoch time is expired based
|
||||||
// on the given current epoch time and time-to-live value.
|
// on the given current epoch time and time-to-live value.
|
||||||
bool IsExpired(
|
bool IsExpired(std::chrono::seconds curEpochTime,
|
||||||
std::chrono::seconds curEpochTime,
|
std::chrono::seconds timeToLive) const {
|
||||||
std::chrono::seconds timeToLive) const
|
|
||||||
{
|
|
||||||
assert(curEpochTime >= GetEpochTime());
|
assert(curEpochTime >= GetEpochTime());
|
||||||
return (curEpochTime - GetEpochTime()) > timeToLive;
|
return (curEpochTime - GetEpochTime()) > timeToLive;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the access status is on.
|
// Returns true if the access status is on.
|
||||||
bool IsAccessed() const
|
bool IsAccessed() const { return !!(GetAccessByte() & s_accessSetMask); }
|
||||||
{
|
|
||||||
return !!(GetAccessByte() & s_accessSetMask);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If "set" is true, turn on the access bit in the given metadata and store it.
|
// If "set" is true, turn on the access bit in the given metadata and store
|
||||||
// If "set" is false, turn off the access bit.
|
// it. If "set" is false, turn off the access bit. Returns true if the given
|
||||||
// Returns true if the given metadata's access bit was originally on.
|
// metadata's access bit was originally on.
|
||||||
bool UpdateAccessStatus(bool set)
|
bool UpdateAccessStatus(bool set) {
|
||||||
{
|
|
||||||
const auto isAccessBitOn = IsAccessed();
|
const auto isAccessBitOn = IsAccessed();
|
||||||
|
|
||||||
// Set the bit only if the bit is not set, and vice versa.
|
// Set the bit only if the bit is not set, and vice versa.
|
||||||
if (set != isAccessBitOn)
|
if (set != isAccessBitOn) {
|
||||||
{
|
if (set) {
|
||||||
if (set)
|
|
||||||
{
|
|
||||||
GetAccessByte() |= s_accessSetMask;
|
GetAccessByte() |= s_accessSetMask;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
GetAccessByte() &= s_accessUnsetMask;
|
GetAccessByte() &= s_accessUnsetMask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,14 +63,12 @@ public:
|
||||||
|
|
||||||
static constexpr std::uint16_t c_metaDataSize = sizeof(std::uint32_t);
|
static constexpr std::uint16_t c_metaDataSize = sizeof(std::uint32_t);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::uint8_t GetAccessByte() const
|
std::uint8_t GetAccessByte() const {
|
||||||
{
|
|
||||||
return reinterpret_cast<std::uint8_t*>(m_metadata)[s_accessBitByte];
|
return reinterpret_cast<std::uint8_t*>(m_metadata)[s_accessBitByte];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint8_t& GetAccessByte()
|
std::uint8_t& GetAccessByte() {
|
||||||
{
|
|
||||||
return reinterpret_cast<std::uint8_t*>(m_metadata)[s_accessBitByte];
|
return reinterpret_cast<std::uint8_t*>(m_metadata)[s_accessBitByte];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +89,6 @@ private:
|
||||||
std::uint32_t* m_metadata = nullptr;
|
std::uint32_t* m_metadata = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace Cache
|
} // namespace Cache
|
||||||
} // namespace HashTable
|
} // namespace HashTable
|
||||||
} // namespace L4
|
} // namespace L4
|
||||||
|
|
|
@ -4,88 +4,68 @@
|
||||||
#include "HashTable/IHashTable.h"
|
#include "HashTable/IHashTable.h"
|
||||||
#include "Utils/Exception.h"
|
#include "Utils/Exception.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace HashTable {
|
||||||
namespace HashTable
|
|
||||||
{
|
|
||||||
|
|
||||||
// Record struct consists of key and value pair.
|
// Record struct consists of key and value pair.
|
||||||
struct Record
|
struct Record {
|
||||||
{
|
|
||||||
using Key = IReadOnlyHashTable::Key;
|
using Key = IReadOnlyHashTable::Key;
|
||||||
using Value = IReadOnlyHashTable::Value;
|
using Value = IReadOnlyHashTable::Value;
|
||||||
|
|
||||||
Record() = default;
|
Record() = default;
|
||||||
|
|
||||||
Record(
|
Record(const Key& key, const Value& value) : m_key{key}, m_value{value} {}
|
||||||
const Key& key,
|
|
||||||
const Value& value)
|
|
||||||
: m_key{ key }
|
|
||||||
, m_value{ value }
|
|
||||||
{}
|
|
||||||
|
|
||||||
Key m_key;
|
Key m_key;
|
||||||
Value m_value;
|
Value m_value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// RecordBuffer is a thin wrapper struct around a raw buffer array (pointer).
|
// RecordBuffer is a thin wrapper struct around a raw buffer array (pointer).
|
||||||
struct RecordBuffer
|
struct RecordBuffer {
|
||||||
{
|
|
||||||
std::uint8_t m_buffer[1];
|
std::uint8_t m_buffer[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(
|
static_assert(sizeof(RecordBuffer) == 1,
|
||||||
sizeof(RecordBuffer) == 1,
|
|
||||||
"RecordBuffer size should be 1 to be a thin wrapper.");
|
"RecordBuffer size should be 1 to be a thin wrapper.");
|
||||||
|
|
||||||
// RecordSerializer provides a functionality to serialize/deserialize a record information.
|
// RecordSerializer provides a functionality to serialize/deserialize a record
|
||||||
class RecordSerializer
|
// information.
|
||||||
{
|
class RecordSerializer {
|
||||||
public:
|
public:
|
||||||
using Key = Record::Key;
|
using Key = Record::Key;
|
||||||
using Value = Record::Value;
|
using Value = Record::Value;
|
||||||
using KeySize = Key::size_type;
|
using KeySize = Key::size_type;
|
||||||
using ValueSize = Value::size_type;
|
using ValueSize = Value::size_type;
|
||||||
|
|
||||||
RecordSerializer(
|
RecordSerializer(KeySize fixedKeySize,
|
||||||
KeySize fixedKeySize,
|
|
||||||
ValueSize fixedValueSize,
|
ValueSize fixedValueSize,
|
||||||
ValueSize metadataSize = 0U)
|
ValueSize metadataSize = 0U)
|
||||||
: m_fixedKeySize{ fixedKeySize }
|
: m_fixedKeySize{fixedKeySize},
|
||||||
, m_fixedValueSize{ fixedValueSize }
|
m_fixedValueSize{fixedValueSize},
|
||||||
, m_metadataSize{ metadataSize }
|
m_metadataSize{metadataSize} {}
|
||||||
{}
|
|
||||||
|
|
||||||
// Returns the number of bytes needed for serializing the given key and value.
|
// Returns the number of bytes needed for serializing the given key and value.
|
||||||
std::size_t CalculateBufferSize(const Key& key, const Value& value) const
|
std::size_t CalculateBufferSize(const Key& key, const Value& value) const {
|
||||||
{
|
return ((m_fixedKeySize != 0) ? m_fixedKeySize
|
||||||
return
|
: (key.m_size + sizeof(KeySize))) +
|
||||||
((m_fixedKeySize != 0)
|
((m_fixedValueSize != 0)
|
||||||
? m_fixedKeySize
|
|
||||||
: (key.m_size + sizeof(KeySize)))
|
|
||||||
+ ((m_fixedValueSize != 0)
|
|
||||||
? m_fixedValueSize + m_metadataSize
|
? m_fixedValueSize + m_metadataSize
|
||||||
: (value.m_size + sizeof(ValueSize) + m_metadataSize));
|
: (value.m_size + sizeof(ValueSize) + m_metadataSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the number bytes used for key and value sizes.
|
// Returns the number bytes used for key and value sizes.
|
||||||
std::size_t CalculateRecordOverhead() const
|
std::size_t CalculateRecordOverhead() const {
|
||||||
{
|
return (m_fixedKeySize != 0 ? 0U : sizeof(KeySize)) +
|
||||||
return
|
(m_fixedValueSize != 0 ? 0U : sizeof(ValueSize));
|
||||||
(m_fixedKeySize != 0 ? 0U : sizeof(KeySize))
|
|
||||||
+ (m_fixedValueSize != 0 ? 0U : sizeof(ValueSize));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serializes the given key and value to the given buffer.
|
// Serializes the given key and value to the given buffer.
|
||||||
// Note that the buffer size is at least as big as the number of bytes
|
// Note that the buffer size is at least as big as the number of bytes
|
||||||
// returned by CalculateBufferSize().
|
// returned by CalculateBufferSize().
|
||||||
RecordBuffer* Serialize(
|
RecordBuffer* Serialize(const Key& key,
|
||||||
const Key& key,
|
|
||||||
const Value& value,
|
const Value& value,
|
||||||
std::uint8_t* const buffer,
|
std::uint8_t* const buffer,
|
||||||
std::size_t bufferSize) const
|
std::size_t bufferSize) const {
|
||||||
{
|
|
||||||
Validate(key, value);
|
Validate(key, value);
|
||||||
|
|
||||||
assert(CalculateBufferSize(key, value) <= bufferSize);
|
assert(CalculateBufferSize(key, value) <= bufferSize);
|
||||||
|
@ -95,7 +75,8 @@ public:
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
memcpy_s(buffer + start, key.m_size, key.m_data, key.m_size);
|
memcpy_s(buffer + start, key.m_size, key.m_data, key.m_size);
|
||||||
memcpy_s(buffer + start + key.m_size, value.m_size, value.m_data, value.m_size);
|
memcpy_s(buffer + start + key.m_size, value.m_size, value.m_data,
|
||||||
|
value.m_size);
|
||||||
#else
|
#else
|
||||||
memcpy(buffer + start, key.m_data, key.m_size);
|
memcpy(buffer + start, key.m_data, key.m_size);
|
||||||
memcpy(buffer + start + key.m_size, value.m_data, value.m_size);
|
memcpy(buffer + start + key.m_size, value.m_data, value.m_size);
|
||||||
|
@ -107,57 +88,52 @@ public:
|
||||||
// The meta value is serialized between key and value.
|
// The meta value is serialized between key and value.
|
||||||
// Note that the buffer size is at least as big as the number of bytes
|
// Note that the buffer size is at least as big as the number of bytes
|
||||||
// returned by CalculateBufferSize().
|
// returned by CalculateBufferSize().
|
||||||
RecordBuffer* Serialize(
|
RecordBuffer* Serialize(const Key& key,
|
||||||
const Key& key,
|
|
||||||
const Value& value,
|
const Value& value,
|
||||||
const Value& metaValue,
|
const Value& metaValue,
|
||||||
std::uint8_t* const buffer,
|
std::uint8_t* const buffer,
|
||||||
std::size_t bufferSize) const
|
std::size_t bufferSize) const {
|
||||||
{
|
|
||||||
Validate(key, value, metaValue);
|
Validate(key, value, metaValue);
|
||||||
|
|
||||||
assert(CalculateBufferSize(key, value) <= bufferSize);
|
assert(CalculateBufferSize(key, value) <= bufferSize);
|
||||||
(void)bufferSize;
|
(void)bufferSize;
|
||||||
|
|
||||||
const auto start = SerializeSizes(buffer, key.m_size, value.m_size + metaValue.m_size);
|
const auto start =
|
||||||
|
SerializeSizes(buffer, key.m_size, value.m_size + metaValue.m_size);
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
memcpy_s(buffer + start, key.m_size, key.m_data, key.m_size);
|
memcpy_s(buffer + start, key.m_size, key.m_data, key.m_size);
|
||||||
memcpy_s(buffer + start + key.m_size, metaValue.m_size, metaValue.m_data, metaValue.m_size);
|
memcpy_s(buffer + start + key.m_size, metaValue.m_size, metaValue.m_data,
|
||||||
memcpy_s(buffer + start + key.m_size + metaValue.m_size, value.m_size, value.m_data, value.m_size);
|
metaValue.m_size);
|
||||||
|
memcpy_s(buffer + start + key.m_size + metaValue.m_size, value.m_size,
|
||||||
|
value.m_data, value.m_size);
|
||||||
#else
|
#else
|
||||||
memcpy(buffer + start, key.m_data, key.m_size);
|
memcpy(buffer + start, key.m_data, key.m_size);
|
||||||
memcpy(buffer + start + key.m_size, metaValue.m_data, metaValue.m_size);
|
memcpy(buffer + start + key.m_size, metaValue.m_data, metaValue.m_size);
|
||||||
memcpy(buffer + start + key.m_size + metaValue.m_size, value.m_data, value.m_size);
|
memcpy(buffer + start + key.m_size + metaValue.m_size, value.m_data,
|
||||||
|
value.m_size);
|
||||||
#endif
|
#endif
|
||||||
return reinterpret_cast<RecordBuffer*>(buffer);
|
return reinterpret_cast<RecordBuffer*>(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deserializes the given buffer and returns a Record object.
|
// Deserializes the given buffer and returns a Record object.
|
||||||
Record Deserialize(const RecordBuffer& buffer) const
|
Record Deserialize(const RecordBuffer& buffer) const {
|
||||||
{
|
|
||||||
Record record;
|
Record record;
|
||||||
|
|
||||||
const auto* dataBuffer = buffer.m_buffer;
|
const auto* dataBuffer = buffer.m_buffer;
|
||||||
|
|
||||||
auto& key = record.m_key;
|
auto& key = record.m_key;
|
||||||
if (m_fixedKeySize != 0)
|
if (m_fixedKeySize != 0) {
|
||||||
{
|
|
||||||
key.m_size = m_fixedKeySize;
|
key.m_size = m_fixedKeySize;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
key.m_size = *reinterpret_cast<const KeySize*>(dataBuffer);
|
key.m_size = *reinterpret_cast<const KeySize*>(dataBuffer);
|
||||||
dataBuffer += sizeof(KeySize);
|
dataBuffer += sizeof(KeySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& value = record.m_value;
|
auto& value = record.m_value;
|
||||||
if (m_fixedValueSize != 0)
|
if (m_fixedValueSize != 0) {
|
||||||
{
|
|
||||||
value.m_size = m_fixedValueSize + m_metadataSize;
|
value.m_size = m_fixedValueSize + m_metadataSize;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
value.m_size = *reinterpret_cast<const ValueSize*>(dataBuffer);
|
value.m_size = *reinterpret_cast<const ValueSize*>(dataBuffer);
|
||||||
dataBuffer += sizeof(ValueSize);
|
dataBuffer += sizeof(ValueSize);
|
||||||
}
|
}
|
||||||
|
@ -168,45 +144,39 @@ public:
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Validates key and value sizes when fixed sizes are set.
|
// Validates key and value sizes when fixed sizes are set.
|
||||||
// Throws an exception if invalid sizes are used.
|
// Throws an exception if invalid sizes are used.
|
||||||
void Validate(const Key& key, const Value& value) const
|
void Validate(const Key& key, const Value& value) const {
|
||||||
{
|
if ((m_fixedKeySize != 0 && key.m_size != m_fixedKeySize) ||
|
||||||
if ((m_fixedKeySize != 0 && key.m_size != m_fixedKeySize)
|
(m_fixedValueSize != 0 && value.m_size != m_fixedValueSize)) {
|
||||||
|| (m_fixedValueSize != 0 && value.m_size != m_fixedValueSize))
|
|
||||||
{
|
|
||||||
throw RuntimeException("Invalid key or value sizes are given.");
|
throw RuntimeException("Invalid key or value sizes are given.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates against the given meta value.
|
// Validates against the given meta value.
|
||||||
void Validate(const Key& key, const Value& value, const Value& metaValue) const
|
void Validate(const Key& key,
|
||||||
{
|
const Value& value,
|
||||||
|
const Value& metaValue) const {
|
||||||
Validate(key, value);
|
Validate(key, value);
|
||||||
|
|
||||||
if (m_metadataSize != metaValue.m_size)
|
if (m_metadataSize != metaValue.m_size) {
|
||||||
{
|
|
||||||
throw RuntimeException("Invalid meta value size is given.");
|
throw RuntimeException("Invalid meta value size is given.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serializes size information to the given buffer.
|
// Serializes size information to the given buffer.
|
||||||
// It assumes that buffer has enough size for serialization.
|
// It assumes that buffer has enough size for serialization.
|
||||||
std::size_t SerializeSizes(
|
std::size_t SerializeSizes(std::uint8_t* const buffer,
|
||||||
std::uint8_t* const buffer,
|
|
||||||
KeySize keySize,
|
KeySize keySize,
|
||||||
ValueSize valueSize) const
|
ValueSize valueSize) const {
|
||||||
{
|
|
||||||
auto curBuffer = buffer;
|
auto curBuffer = buffer;
|
||||||
if (m_fixedKeySize == 0)
|
if (m_fixedKeySize == 0) {
|
||||||
{
|
|
||||||
*reinterpret_cast<KeySize*>(curBuffer) = keySize;
|
*reinterpret_cast<KeySize*>(curBuffer) = keySize;
|
||||||
curBuffer += sizeof(keySize);
|
curBuffer += sizeof(keySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_fixedValueSize == 0)
|
if (m_fixedValueSize == 0) {
|
||||||
{
|
|
||||||
*reinterpret_cast<ValueSize*>(curBuffer) = valueSize;
|
*reinterpret_cast<ValueSize*>(curBuffer) = valueSize;
|
||||||
curBuffer += sizeof(valueSize);
|
curBuffer += sizeof(valueSize);
|
||||||
}
|
}
|
||||||
|
@ -219,6 +189,5 @@ private:
|
||||||
const ValueSize m_metadataSize;
|
const ValueSize m_metadataSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace HashTable
|
} // namespace HashTable
|
||||||
} // namespace L4
|
} // namespace L4
|
||||||
|
|
|
@ -4,23 +4,21 @@
|
||||||
#include "HashTable/Common/SharedHashTable.h"
|
#include "HashTable/Common/SharedHashTable.h"
|
||||||
#include "HashTable/Config.h"
|
#include "HashTable/Config.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace HashTable {
|
||||||
namespace HashTable
|
|
||||||
{
|
|
||||||
|
|
||||||
// SettingAdapter class provides a functionality to convert a HashTableConfig::Setting object
|
// SettingAdapter class provides a functionality to convert a
|
||||||
// to a SharedHashTable::Setting object.
|
// HashTableConfig::Setting object to a SharedHashTable::Setting object.
|
||||||
class SettingAdapter
|
class SettingAdapter {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
template <typename SharedHashTable>
|
template <typename SharedHashTable>
|
||||||
typename SharedHashTable::Setting Convert(const HashTableConfig::Setting& from) const
|
typename SharedHashTable::Setting Convert(
|
||||||
{
|
const HashTableConfig::Setting& from) const {
|
||||||
typename SharedHashTable::Setting to;
|
typename SharedHashTable::Setting to;
|
||||||
|
|
||||||
to.m_numBuckets = from.m_numBuckets;
|
to.m_numBuckets = from.m_numBuckets;
|
||||||
to.m_numBucketsPerMutex = (std::max)(from.m_numBucketsPerMutex.get_value_or(1U), 1U);
|
to.m_numBucketsPerMutex =
|
||||||
|
(std::max)(from.m_numBucketsPerMutex.get_value_or(1U), 1U);
|
||||||
to.m_fixedKeySize = from.m_fixedKeySize.get_value_or(0U);
|
to.m_fixedKeySize = from.m_fixedKeySize.get_value_or(0U);
|
||||||
to.m_fixedValueSize = from.m_fixedValueSize.get_value_or(0U);
|
to.m_fixedValueSize = from.m_fixedValueSize.get_value_or(0U);
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,12 @@
|
||||||
#include "Utils/Exception.h"
|
#include "Utils/Exception.h"
|
||||||
#include "Utils/Lock.h"
|
#include "Utils/Lock.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace HashTable {
|
||||||
namespace HashTable
|
|
||||||
{
|
|
||||||
|
|
||||||
// SharedHashTable struct represents the hash table structure.
|
// SharedHashTable struct represents the hash table structure.
|
||||||
template <typename TData, typename TAllocator>
|
template <typename TData, typename TAllocator>
|
||||||
struct SharedHashTable
|
struct SharedHashTable {
|
||||||
{
|
|
||||||
using Data = TData;
|
using Data = TData;
|
||||||
using Allocator = TAllocator;
|
using Allocator = TAllocator;
|
||||||
|
|
||||||
|
@ -45,72 +42,73 @@ struct SharedHashTable
|
||||||
// | Data16 pointer | 18
|
// | Data16 pointer | 18
|
||||||
// | Entry pointer to the next Entry | 19
|
// | Entry pointer to the next Entry | 19
|
||||||
// <----------------------8 bytes ---------------------------------->
|
// <----------------------8 bytes ---------------------------------->
|
||||||
// , where tag1 is a tag for Data1, tag2 for Data2, and so on. A tag value can be looked up
|
// , where tag1 is a tag for Data1, tag2 for Data2, and so on. A tag value can
|
||||||
// first before going to the corresponding Data for a quick check.
|
// be looked up first before going to the corresponding Data for a quick
|
||||||
// Also note that a byte read is atomic in modern processors so that tag is just
|
// check. Also note that a byte read is atomic in modern processors so that
|
||||||
// std::uint8_t instead of being atomic. Even in the case where the tag value read is a garbage ,
|
// tag is just std::uint8_t instead of being atomic. Even in the case where
|
||||||
// this is acceptable because of the followings:
|
// the tag value read is a garbage , this is acceptable because of the
|
||||||
// 1) if the garbage value was a hit where it should have been a miss: the actual key comparison will fail,
|
// followings:
|
||||||
// 2) if the garbage value was a miss where it should have been a hit: the key value must
|
// 1) if the garbage value was a hit where it should have been a miss: the
|
||||||
// have been changed since the tag was changed, so it will be looked up correctly
|
// actual key comparison will fail, 2) if the garbage value was a miss
|
||||||
// after the tag value written is visible correctly. Note that we don't need to guarantee the timing of
|
// where it should have been a hit: the key value must
|
||||||
// writing and reading (meaning the value written should be visible to the reader right away).
|
// have been changed since the tag was changed, so it will be looked up
|
||||||
|
// correctly after the tag value written is visible correctly. Note that
|
||||||
|
// we don't need to guarantee the timing of writing and reading (meaning
|
||||||
|
// the value written should be visible to the reader right away).
|
||||||
//
|
//
|
||||||
// Note about the CPU cache. In previous implementation, the Entry was 64 bytes to fit in the CPU cache.
|
// Note about the CPU cache. In previous implementation, the Entry was 64
|
||||||
// However, this resulted in lots of wasted space. For example, when the ratio of the number of expected records
|
// bytes to fit in the CPU cache. However, this resulted in lots of wasted
|
||||||
// to the number of buckets was 2:1, only 85% buckets were occupied. After experiments, if you have 10:1 ratio,
|
// space. For example, when the ratio of the number of expected records to the
|
||||||
// you will have 99.98% utilization of buckets. This required having more data per Entry, and the ideal number
|
// number of buckets was 2:1, only 85% buckets were occupied. After
|
||||||
// (after experiments) turned out to be 16 records per Entry. Also, because of how CPU fetches contiguous memory,
|
// experiments, if you have 10:1 ratio, you will have 99.98% utilization of
|
||||||
// this didn't have any impact on micro-benchmarking.
|
// buckets. This required having more data per Entry, and the ideal number
|
||||||
struct Entry
|
// (after experiments) turned out to be 16 records per Entry. Also, because of
|
||||||
{
|
// how CPU fetches contiguous memory, this didn't have any impact on
|
||||||
|
// micro-benchmarking.
|
||||||
|
struct Entry {
|
||||||
Entry() = default;
|
Entry() = default;
|
||||||
|
|
||||||
// Releases deallocates all the memories of the chained entries including
|
// Releases deallocates all the memories of the chained entries including
|
||||||
// the data list in the current Entry.
|
// the data list in the current Entry.
|
||||||
void Release(Allocator allocator)
|
void Release(Allocator allocator) {
|
||||||
{
|
auto dataDeleter = [allocator](auto& data) {
|
||||||
auto dataDeleter = [allocator](auto& data)
|
|
||||||
{
|
|
||||||
auto dataToDelete = data.Load();
|
auto dataToDelete = data.Load();
|
||||||
if (dataToDelete != nullptr)
|
if (dataToDelete != nullptr) {
|
||||||
{
|
|
||||||
dataToDelete->~Data();
|
dataToDelete->~Data();
|
||||||
typename Allocator::template rebind<Data>::other(allocator).deallocate(dataToDelete, 1U);
|
typename Allocator::template rebind<Data>::other(allocator)
|
||||||
|
.deallocate(dataToDelete, 1U);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete all the chained entries, not including itself.
|
// Delete all the chained entries, not including itself.
|
||||||
auto curEntry = m_next.Load();
|
auto curEntry = m_next.Load();
|
||||||
|
|
||||||
while (curEntry != nullptr)
|
while (curEntry != nullptr) {
|
||||||
{
|
|
||||||
auto entryToDelete = curEntry;
|
auto entryToDelete = curEntry;
|
||||||
|
|
||||||
// Copy m_next for the next iteration.
|
// Copy m_next for the next iteration.
|
||||||
curEntry = entryToDelete->m_next.Load();
|
curEntry = entryToDelete->m_next.Load();
|
||||||
|
|
||||||
// Delete all the data within this entry.
|
// Delete all the data within this entry.
|
||||||
for (auto& data : entryToDelete->m_dataList)
|
for (auto& data : entryToDelete->m_dataList) {
|
||||||
{
|
|
||||||
dataDeleter(data);
|
dataDeleter(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean the current entry itself.
|
// Clean the current entry itself.
|
||||||
entryToDelete->~Entry();
|
entryToDelete->~Entry();
|
||||||
typename Allocator::template rebind<Entry>::other(allocator).deallocate(entryToDelete, 1U);
|
typename Allocator::template rebind<Entry>::other(allocator).deallocate(
|
||||||
|
entryToDelete, 1U);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all the data from the head of chained entries.
|
// Delete all the data from the head of chained entries.
|
||||||
for (auto& data : m_dataList)
|
for (auto& data : m_dataList) {
|
||||||
{
|
|
||||||
dataDeleter(data);
|
dataDeleter(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr std::uint8_t c_numDataPerEntry = 16U;
|
static constexpr std::uint8_t c_numDataPerEntry = 16U;
|
||||||
|
|
||||||
std::array<std::uint8_t, c_numDataPerEntry> m_tags{ 0U };
|
std::array<std::uint8_t, c_numDataPerEntry> m_tags{0U};
|
||||||
|
|
||||||
std::array<Utils::AtomicOffsetPtr<Data>, c_numDataPerEntry> m_dataList{};
|
std::array<Utils::AtomicOffsetPtr<Data>, c_numDataPerEntry> m_dataList{};
|
||||||
|
|
||||||
|
@ -119,23 +117,20 @@ struct SharedHashTable
|
||||||
|
|
||||||
static_assert(sizeof(Entry) == 152, "Entry should be 152 bytes.");
|
static_assert(sizeof(Entry) == 152, "Entry should be 152 bytes.");
|
||||||
|
|
||||||
struct Setting
|
struct Setting {
|
||||||
{
|
|
||||||
using KeySize = IReadOnlyHashTable::Key::size_type;
|
using KeySize = IReadOnlyHashTable::Key::size_type;
|
||||||
using ValueSize = IReadOnlyHashTable::Value::size_type;
|
using ValueSize = IReadOnlyHashTable::Value::size_type;
|
||||||
|
|
||||||
Setting() = default;
|
Setting() = default;
|
||||||
|
|
||||||
explicit Setting(
|
explicit Setting(std::uint32_t numBuckets,
|
||||||
std::uint32_t numBuckets,
|
|
||||||
std::uint32_t numBucketsPerMutex = 1U,
|
std::uint32_t numBucketsPerMutex = 1U,
|
||||||
KeySize fixedKeySize = 0U,
|
KeySize fixedKeySize = 0U,
|
||||||
ValueSize fixedValueSize = 0U)
|
ValueSize fixedValueSize = 0U)
|
||||||
: m_numBuckets{ numBuckets }
|
: m_numBuckets{numBuckets},
|
||||||
, m_numBucketsPerMutex{ numBucketsPerMutex }
|
m_numBucketsPerMutex{numBucketsPerMutex},
|
||||||
, m_fixedKeySize{ fixedKeySize }
|
m_fixedKeySize{fixedKeySize},
|
||||||
, m_fixedValueSize{ fixedValueSize }
|
m_fixedValueSize{fixedValueSize} {}
|
||||||
{}
|
|
||||||
|
|
||||||
std::uint32_t m_numBuckets = 1U;
|
std::uint32_t m_numBuckets = 1U;
|
||||||
std::uint32_t m_numBucketsPerMutex = 1U;
|
std::uint32_t m_numBucketsPerMutex = 1U;
|
||||||
|
@ -143,29 +138,27 @@ struct SharedHashTable
|
||||||
ValueSize m_fixedValueSize = 0U;
|
ValueSize m_fixedValueSize = 0U;
|
||||||
};
|
};
|
||||||
|
|
||||||
SharedHashTable(
|
SharedHashTable(const Setting& setting, Allocator allocator)
|
||||||
const Setting& setting,
|
: m_allocator{allocator},
|
||||||
Allocator allocator)
|
m_setting{setting},
|
||||||
: m_allocator{ allocator }
|
m_buckets{
|
||||||
, m_setting{ setting }
|
setting.m_numBuckets,
|
||||||
, m_buckets{ setting.m_numBuckets, typename Allocator::template rebind<Entry>::other(m_allocator) }
|
typename Allocator::template rebind<Entry>::other(m_allocator)},
|
||||||
, m_mutexes{
|
m_mutexes{
|
||||||
(std::max)(setting.m_numBuckets / (std::max)(setting.m_numBucketsPerMutex, 1U), 1U),
|
(std::max)(setting.m_numBuckets /
|
||||||
typename Allocator::template rebind<Mutex>::other(m_allocator) }
|
(std::max)(setting.m_numBucketsPerMutex, 1U),
|
||||||
, m_perfData{}
|
1U),
|
||||||
{
|
typename Allocator::template rebind<Mutex>::other(m_allocator)},
|
||||||
|
m_perfData{} {
|
||||||
m_perfData.Set(HashTablePerfCounter::BucketsCount, m_buckets.size());
|
m_perfData.Set(HashTablePerfCounter::BucketsCount, m_buckets.size());
|
||||||
m_perfData.Set(
|
m_perfData.Set(HashTablePerfCounter::TotalIndexSize,
|
||||||
HashTablePerfCounter::TotalIndexSize,
|
(m_buckets.size() * sizeof(Entry)) +
|
||||||
(m_buckets.size() * sizeof(Entry))
|
(m_mutexes.size() * sizeof(Mutex)) +
|
||||||
+ (m_mutexes.size() * sizeof(Mutex))
|
sizeof(SharedHashTable));
|
||||||
+ sizeof(SharedHashTable));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~SharedHashTable()
|
~SharedHashTable() {
|
||||||
{
|
for (auto& bucket : m_buckets) {
|
||||||
for (auto& bucket : m_buckets)
|
|
||||||
{
|
|
||||||
bucket.Release(m_allocator);
|
bucket.Release(m_allocator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,17 +167,17 @@ struct SharedHashTable
|
||||||
using Lock = std::lock_guard<Mutex>;
|
using Lock = std::lock_guard<Mutex>;
|
||||||
using UniqueLock = std::unique_lock<Mutex>;
|
using UniqueLock = std::unique_lock<Mutex>;
|
||||||
|
|
||||||
using Buckets = Interprocess::Container::Vector<Entry, typename Allocator::template rebind<Entry>::other>;
|
using Buckets = Interprocess::Container::
|
||||||
using Mutexes = Interprocess::Container::Vector<Mutex, typename Allocator::template rebind<Mutex>::other>;
|
Vector<Entry, typename Allocator::template rebind<Entry>::other>;
|
||||||
|
using Mutexes = Interprocess::Container::
|
||||||
|
Vector<Mutex, typename Allocator::template rebind<Mutex>::other>;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
auto GetAllocator() const
|
auto GetAllocator() const {
|
||||||
{
|
|
||||||
return typename Allocator::template rebind<T>::other(m_allocator);
|
return typename Allocator::template rebind<T>::other(m_allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
Mutex& GetMutex(std::size_t index)
|
Mutex& GetMutex(std::size_t index) {
|
||||||
{
|
|
||||||
return m_mutexes[index % m_mutexes.size()];
|
return m_mutexes[index % m_mutexes.size()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,33 +2,28 @@
|
||||||
|
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdint>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "HashTable/IHashTable.h"
|
#include "HashTable/IHashTable.h"
|
||||||
#include "Utils/Properties.h"
|
#include "Utils/Properties.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
// HashTableConfig struct.
|
// HashTableConfig struct.
|
||||||
struct HashTableConfig
|
struct HashTableConfig {
|
||||||
{
|
struct Setting {
|
||||||
struct Setting
|
|
||||||
{
|
|
||||||
using KeySize = IReadOnlyHashTable::Key::size_type;
|
using KeySize = IReadOnlyHashTable::Key::size_type;
|
||||||
using ValueSize = IReadOnlyHashTable::Value::size_type;
|
using ValueSize = IReadOnlyHashTable::Value::size_type;
|
||||||
|
|
||||||
explicit Setting(
|
explicit Setting(std::uint32_t numBuckets,
|
||||||
std::uint32_t numBuckets,
|
|
||||||
boost::optional<std::uint32_t> numBucketsPerMutex = {},
|
boost::optional<std::uint32_t> numBucketsPerMutex = {},
|
||||||
boost::optional<KeySize> fixedKeySize = {},
|
boost::optional<KeySize> fixedKeySize = {},
|
||||||
boost::optional<ValueSize> fixedValueSize = {})
|
boost::optional<ValueSize> fixedValueSize = {})
|
||||||
: m_numBuckets{ numBuckets }
|
: m_numBuckets{numBuckets},
|
||||||
, m_numBucketsPerMutex{ numBucketsPerMutex }
|
m_numBucketsPerMutex{numBucketsPerMutex},
|
||||||
, m_fixedKeySize{ fixedKeySize }
|
m_fixedKeySize{fixedKeySize},
|
||||||
, m_fixedValueSize{ fixedValueSize }
|
m_fixedValueSize{fixedValueSize} {}
|
||||||
{}
|
|
||||||
|
|
||||||
std::uint32_t m_numBuckets;
|
std::uint32_t m_numBuckets;
|
||||||
boost::optional<std::uint32_t> m_numBucketsPerMutex;
|
boost::optional<std::uint32_t> m_numBucketsPerMutex;
|
||||||
|
@ -36,49 +31,40 @@ struct HashTableConfig
|
||||||
boost::optional<ValueSize> m_fixedValueSize;
|
boost::optional<ValueSize> m_fixedValueSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Cache
|
struct Cache {
|
||||||
{
|
Cache(std::uint64_t maxCacheSizeInBytes,
|
||||||
Cache(
|
|
||||||
std::uint64_t maxCacheSizeInBytes,
|
|
||||||
std::chrono::seconds recordTimeToLive,
|
std::chrono::seconds recordTimeToLive,
|
||||||
bool forceTimeBasedEviction)
|
bool forceTimeBasedEviction)
|
||||||
: m_maxCacheSizeInBytes{ maxCacheSizeInBytes }
|
: m_maxCacheSizeInBytes{maxCacheSizeInBytes},
|
||||||
, m_recordTimeToLive{ recordTimeToLive }
|
m_recordTimeToLive{recordTimeToLive},
|
||||||
, m_forceTimeBasedEviction{ forceTimeBasedEviction }
|
m_forceTimeBasedEviction{forceTimeBasedEviction} {}
|
||||||
{}
|
|
||||||
|
|
||||||
std::uint64_t m_maxCacheSizeInBytes;
|
std::uint64_t m_maxCacheSizeInBytes;
|
||||||
std::chrono::seconds m_recordTimeToLive;
|
std::chrono::seconds m_recordTimeToLive;
|
||||||
bool m_forceTimeBasedEviction;
|
bool m_forceTimeBasedEviction;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Serializer
|
struct Serializer {
|
||||||
{
|
|
||||||
using Properties = Utils::Properties;
|
using Properties = Utils::Properties;
|
||||||
|
|
||||||
Serializer(
|
Serializer(std::shared_ptr<std::istream> stream = {},
|
||||||
std::shared_ptr<std::istream> stream = {},
|
|
||||||
boost::optional<Properties> properties = {})
|
boost::optional<Properties> properties = {})
|
||||||
: m_stream{ stream }
|
: m_stream{stream}, m_properties{properties} {}
|
||||||
, m_properties{ properties }
|
|
||||||
{}
|
|
||||||
|
|
||||||
std::shared_ptr<std::istream> m_stream;
|
std::shared_ptr<std::istream> m_stream;
|
||||||
boost::optional<Properties> m_properties;
|
boost::optional<Properties> m_properties;
|
||||||
};
|
};
|
||||||
|
|
||||||
HashTableConfig(
|
HashTableConfig(std::string name,
|
||||||
std::string name,
|
|
||||||
Setting setting,
|
Setting setting,
|
||||||
boost::optional<Cache> cache = {},
|
boost::optional<Cache> cache = {},
|
||||||
boost::optional<Serializer> serializer = {})
|
boost::optional<Serializer> serializer = {})
|
||||||
: m_name{ std::move(name) }
|
: m_name{std::move(name)},
|
||||||
, m_setting{ std::move(setting) }
|
m_setting{std::move(setting)},
|
||||||
, m_cache{ cache }
|
m_cache{cache},
|
||||||
, m_serializer{ serializer }
|
m_serializer{serializer} {
|
||||||
{
|
assert(m_setting.m_numBuckets > 0U ||
|
||||||
assert(m_setting.m_numBuckets > 0U
|
(m_serializer && (serializer->m_stream != nullptr)));
|
||||||
|| (m_serializer && (serializer->m_stream != nullptr)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
|
|
|
@ -5,35 +5,26 @@
|
||||||
#include "Log/PerfCounter.h"
|
#include "Log/PerfCounter.h"
|
||||||
#include "Utils/Properties.h"
|
#include "Utils/Properties.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
// IReadOnlyHashTable interface for read-only access to the hash table.
|
// IReadOnlyHashTable interface for read-only access to the hash table.
|
||||||
struct IReadOnlyHashTable
|
struct IReadOnlyHashTable {
|
||||||
{
|
|
||||||
// Blob struct that represents a memory blob.
|
// Blob struct that represents a memory blob.
|
||||||
template <typename TSize>
|
template <typename TSize>
|
||||||
struct Blob
|
struct Blob {
|
||||||
{
|
|
||||||
using size_type = TSize;
|
using size_type = TSize;
|
||||||
|
|
||||||
explicit Blob(const std::uint8_t* data = nullptr, size_type size = 0U)
|
explicit Blob(const std::uint8_t* data = nullptr, size_type size = 0U)
|
||||||
: m_data{ data }
|
: m_data{data}, m_size{size} {
|
||||||
, m_size{ size }
|
static_assert(std::numeric_limits<size_type>::is_integer,
|
||||||
{
|
"size_type is not an integer.");
|
||||||
static_assert(std::numeric_limits<size_type>::is_integer, "size_type is not an integer.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const Blob& other) const
|
bool operator==(const Blob& other) const {
|
||||||
{
|
return (m_size == other.m_size) && !memcmp(m_data, other.m_data, m_size);
|
||||||
return (m_size == other.m_size)
|
|
||||||
&& !memcmp(m_data, other.m_data, m_size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const Blob& other) const
|
bool operator!=(const Blob& other) const { return !(*this == other); }
|
||||||
{
|
|
||||||
return !(*this == other);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::uint8_t* m_data;
|
const std::uint8_t* m_data;
|
||||||
size_type m_size;
|
size_type m_size;
|
||||||
|
@ -56,8 +47,7 @@ struct IReadOnlyHashTable
|
||||||
};
|
};
|
||||||
|
|
||||||
// IReadOnlyHashTable::IIterator interface for the hash table iterator.
|
// IReadOnlyHashTable::IIterator interface for the hash table iterator.
|
||||||
struct IReadOnlyHashTable::IIterator
|
struct IReadOnlyHashTable::IIterator {
|
||||||
{
|
|
||||||
virtual ~IIterator() = default;
|
virtual ~IIterator() = default;
|
||||||
|
|
||||||
virtual void Reset() = 0;
|
virtual void Reset() = 0;
|
||||||
|
@ -70,8 +60,7 @@ struct IReadOnlyHashTable::IIterator
|
||||||
};
|
};
|
||||||
|
|
||||||
// IWritableHashTable interface for write access to the hash table.
|
// IWritableHashTable interface for write access to the hash table.
|
||||||
struct IWritableHashTable : public virtual IReadOnlyHashTable
|
struct IWritableHashTable : public virtual IReadOnlyHashTable {
|
||||||
{
|
|
||||||
struct ISerializer;
|
struct ISerializer;
|
||||||
|
|
||||||
using ISerializerPtr = std::unique_ptr<ISerializer>;
|
using ISerializerPtr = std::unique_ptr<ISerializer>;
|
||||||
|
@ -84,12 +73,10 @@ struct IWritableHashTable : public virtual IReadOnlyHashTable
|
||||||
};
|
};
|
||||||
|
|
||||||
// IWritableHashTable::ISerializer interface for serializing hash table.
|
// IWritableHashTable::ISerializer interface for serializing hash table.
|
||||||
struct IWritableHashTable::ISerializer
|
struct IWritableHashTable::ISerializer {
|
||||||
{
|
|
||||||
virtual ~ISerializer() = default;
|
virtual ~ISerializer() = default;
|
||||||
|
|
||||||
virtual void Serialize(
|
virtual void Serialize(std::ostream& stream,
|
||||||
std::ostream& stream,
|
|
||||||
const Utils::Properties& properties) = 0;
|
const Utils::Properties& properties) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,32 +3,29 @@
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include "detail/ToRawPointer.h"
|
|
||||||
#include "Epoch/IEpochActionManager.h"
|
#include "Epoch/IEpochActionManager.h"
|
||||||
#include "HashTable/Common/SharedHashTable.h"
|
|
||||||
#include "HashTable/Common/Record.h"
|
#include "HashTable/Common/Record.h"
|
||||||
|
#include "HashTable/Common/SharedHashTable.h"
|
||||||
#include "HashTable/IHashTable.h"
|
#include "HashTable/IHashTable.h"
|
||||||
#include "HashTable/ReadWrite/Serializer.h"
|
#include "HashTable/ReadWrite/Serializer.h"
|
||||||
#include "Log/PerfCounter.h"
|
#include "Log/PerfCounter.h"
|
||||||
#include "Utils/Exception.h"
|
#include "Utils/Exception.h"
|
||||||
#include "Utils/MurmurHash3.h"
|
#include "Utils/MurmurHash3.h"
|
||||||
#include "Utils/Properties.h"
|
#include "Utils/Properties.h"
|
||||||
|
#include "detail/ToRawPointer.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
// ReadWriteHashTable is a general purpose hash table where the look up is look free.
|
// ReadWriteHashTable is a general purpose hash table where the look up is look
|
||||||
namespace HashTable
|
// free.
|
||||||
{
|
namespace HashTable {
|
||||||
namespace ReadWrite
|
namespace ReadWrite {
|
||||||
{
|
|
||||||
|
|
||||||
// ReadOnlyHashTable class implements IReadOnlyHashTable interface and provides
|
// ReadOnlyHashTable class implements IReadOnlyHashTable interface and provides
|
||||||
// the functionality to read data given a key.
|
// the functionality to read data given a key.
|
||||||
template <typename Allocator>
|
template <typename Allocator>
|
||||||
class ReadOnlyHashTable : public virtual IReadOnlyHashTable
|
class ReadOnlyHashTable : public virtual IReadOnlyHashTable {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
using HashTable = SharedHashTable<RecordBuffer, Allocator>;
|
using HashTable = SharedHashTable<RecordBuffer, Allocator>;
|
||||||
|
|
||||||
class Iterator;
|
class Iterator;
|
||||||
|
@ -36,36 +33,30 @@ public:
|
||||||
explicit ReadOnlyHashTable(
|
explicit ReadOnlyHashTable(
|
||||||
HashTable& hashTable,
|
HashTable& hashTable,
|
||||||
boost::optional<RecordSerializer> recordSerializer = boost::none)
|
boost::optional<RecordSerializer> recordSerializer = boost::none)
|
||||||
: m_hashTable{ hashTable }
|
: m_hashTable{hashTable},
|
||||||
, m_recordSerializer{
|
m_recordSerializer{
|
||||||
recordSerializer
|
recordSerializer
|
||||||
? *recordSerializer
|
? *recordSerializer
|
||||||
: RecordSerializer{
|
: RecordSerializer{m_hashTable.m_setting.m_fixedKeySize,
|
||||||
m_hashTable.m_setting.m_fixedKeySize,
|
m_hashTable.m_setting.m_fixedValueSize}} {}
|
||||||
m_hashTable.m_setting.m_fixedValueSize } }
|
|
||||||
{}
|
|
||||||
|
|
||||||
virtual bool Get(const Key& key, Value& value) const override
|
virtual bool Get(const Key& key, Value& value) const override {
|
||||||
{
|
|
||||||
const auto bucketInfo = GetBucketInfo(key);
|
const auto bucketInfo = GetBucketInfo(key);
|
||||||
const auto* entry = &m_hashTable.m_buckets[bucketInfo.first];
|
const auto* entry = &m_hashTable.m_buckets[bucketInfo.first];
|
||||||
|
|
||||||
while (entry != nullptr)
|
while (entry != nullptr) {
|
||||||
{
|
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) {
|
||||||
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i)
|
if (bucketInfo.second == entry->m_tags[i]) {
|
||||||
{
|
// There could be a race condition where m_dataList[i] is updated
|
||||||
if (bucketInfo.second == entry->m_tags[i])
|
// during access. Therefore, load it once and save it (it's safe to
|
||||||
{
|
// store it b/c the memory will not be deleted until ref count becomes
|
||||||
// There could be a race condition where m_dataList[i] is updated during access.
|
// 0).
|
||||||
// Therefore, load it once and save it (it's safe to store it b/c the memory
|
const auto data =
|
||||||
// will not be deleted until ref count becomes 0).
|
entry->m_dataList[i].Load(std::memory_order_acquire);
|
||||||
const auto data = entry->m_dataList[i].Load(std::memory_order_acquire);
|
|
||||||
|
|
||||||
if (data != nullptr)
|
if (data != nullptr) {
|
||||||
{
|
|
||||||
const auto record = m_recordSerializer.Deserialize(*data);
|
const auto record = m_recordSerializer.Deserialize(*data);
|
||||||
if (record.m_key == key)
|
if (record.m_key == key) {
|
||||||
{
|
|
||||||
value = record.m_value;
|
value = record.m_value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -79,15 +70,14 @@ public:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual IIteratorPtr GetIterator() const override
|
virtual IIteratorPtr GetIterator() const override {
|
||||||
{
|
|
||||||
return std::make_unique<Iterator>(m_hashTable, m_recordSerializer);
|
return std::make_unique<Iterator>(m_hashTable, m_recordSerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual const HashTablePerfData& GetPerfData() const override
|
virtual const HashTablePerfData& GetPerfData() const override {
|
||||||
{
|
|
||||||
// Synchronizes with any std::memory_order_release if there exists, so that
|
// Synchronizes with any std::memory_order_release if there exists, so that
|
||||||
// HashTablePerfData has the latest values at the moment when GetPerfData() is called.
|
// HashTablePerfData has the latest values at the moment when GetPerfData()
|
||||||
|
// is called.
|
||||||
std::atomic_thread_fence(std::memory_order_acquire);
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
return m_hashTable.m_perfData;
|
return m_hashTable.m_perfData;
|
||||||
}
|
}
|
||||||
|
@ -95,22 +85,21 @@ public:
|
||||||
ReadOnlyHashTable(const ReadOnlyHashTable&) = delete;
|
ReadOnlyHashTable(const ReadOnlyHashTable&) = delete;
|
||||||
ReadOnlyHashTable& operator=(const ReadOnlyHashTable&) = delete;
|
ReadOnlyHashTable& operator=(const ReadOnlyHashTable&) = delete;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// GetBucketInfo returns a pair, where the first is the index to the bucket
|
// GetBucketInfo returns a pair, where the first is the index to the bucket
|
||||||
// and the second is the tag value for the given key.
|
// and the second is the tag value for the given key.
|
||||||
// In this hash table, we treat tag value of 0 as empty (see WritableHashTable::Remove()),
|
// In this hash table, we treat tag value of 0 as empty (see
|
||||||
// so in the worst case scenario, where an entry has an empty data list and the tag
|
// WritableHashTable::Remove()), so in the worst case scenario, where an entry
|
||||||
// value returned for the key is 0, the look up cost is up to 6 checks. We can do something
|
// has an empty data list and the tag value returned for the key is 0, the
|
||||||
// smarter by using the unused two bytes per Entry, but since an Entry object fits into
|
// look up cost is up to 6 checks. We can do something smarter by using the
|
||||||
// CPU cache, the extra overhead should be minimal.
|
// unused two bytes per Entry, but since an Entry object fits into CPU cache,
|
||||||
std::pair<std::uint32_t, std::uint8_t> GetBucketInfo(const Key& key) const
|
// the extra overhead should be minimal.
|
||||||
{
|
std::pair<std::uint32_t, std::uint8_t> GetBucketInfo(const Key& key) const {
|
||||||
std::array<std::uint64_t, 2> hash;
|
std::array<std::uint64_t, 2> hash;
|
||||||
MurmurHash3_x64_128(key.m_data, key.m_size, 0U, hash.data());
|
MurmurHash3_x64_128(key.m_data, key.m_size, 0U, hash.data());
|
||||||
|
|
||||||
return {
|
return {static_cast<std::uint32_t>(hash[0] % m_hashTable.m_buckets.size()),
|
||||||
static_cast<std::uint32_t>(hash[0] % m_hashTable.m_buckets.size()),
|
static_cast<std::uint8_t>(hash[1])};
|
||||||
static_cast<std::uint8_t>(hash[1]) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HashTable& m_hashTable;
|
HashTable& m_hashTable;
|
||||||
|
@ -118,69 +107,57 @@ protected:
|
||||||
RecordSerializer m_recordSerializer;
|
RecordSerializer m_recordSerializer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ReadOnlyHashTable::Iterator class implements IIterator interface and provides
|
// ReadOnlyHashTable::Iterator class implements IIterator interface and provides
|
||||||
// read-only iterator for the ReadOnlyHashTable.
|
// read-only iterator for the ReadOnlyHashTable.
|
||||||
template <typename Allocator>
|
template <typename Allocator>
|
||||||
class ReadOnlyHashTable<Allocator>::Iterator : public IIterator
|
class ReadOnlyHashTable<Allocator>::Iterator : public IIterator {
|
||||||
{
|
public:
|
||||||
public:
|
Iterator(const HashTable& hashTable,
|
||||||
Iterator(
|
|
||||||
const HashTable& hashTable,
|
|
||||||
const RecordSerializer& recordDeserializer)
|
const RecordSerializer& recordDeserializer)
|
||||||
: m_hashTable{ hashTable }
|
: m_hashTable{hashTable},
|
||||||
, m_recordSerializer{ recordDeserializer }
|
m_recordSerializer{recordDeserializer},
|
||||||
, m_currentBucketIndex{ -1 }
|
m_currentBucketIndex{-1},
|
||||||
, m_currentRecordIndex{ 0U }
|
m_currentRecordIndex{0U},
|
||||||
, m_currentEntry{ nullptr }
|
m_currentEntry{nullptr} {}
|
||||||
{}
|
|
||||||
|
|
||||||
Iterator(Iterator&& iterator)
|
Iterator(Iterator&& iterator)
|
||||||
: m_hashTable{ std::move(iterator.m_hashTable) }
|
: m_hashTable{std::move(iterator.m_hashTable)},
|
||||||
, m_recordSerializer{ std::move(iterator.recordDeserializer) }
|
m_recordSerializer{std::move(iterator.recordDeserializer)},
|
||||||
, m_currentBucketIndex{ std::move(iterator.m_currentBucketIndex) }
|
m_currentBucketIndex{std::move(iterator.m_currentBucketIndex)},
|
||||||
, m_currentRecordIndex{ std::move(iterator.m_currentRecordIndex) }
|
m_currentRecordIndex{std::move(iterator.m_currentRecordIndex)},
|
||||||
, m_currentEntry{ std::move(iterator.m_currentEntry) }
|
m_currentEntry{std::move(iterator.m_currentEntry)} {}
|
||||||
{}
|
|
||||||
|
|
||||||
void Reset() override
|
void Reset() override {
|
||||||
{
|
|
||||||
m_currentBucketIndex = -1;
|
m_currentBucketIndex = -1;
|
||||||
m_currentRecordIndex = 0U;
|
m_currentRecordIndex = 0U;
|
||||||
m_currentEntry = nullptr;
|
m_currentEntry = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MoveNext() override
|
bool MoveNext() override {
|
||||||
{
|
if (IsEnd()) {
|
||||||
if (IsEnd())
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_currentEntry != nullptr)
|
if (m_currentEntry != nullptr) {
|
||||||
{
|
|
||||||
MoveToNextData();
|
MoveToNextData();
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(m_currentRecordIndex < HashTable::Entry::c_numDataPerEntry);
|
assert(m_currentRecordIndex < HashTable::Entry::c_numDataPerEntry);
|
||||||
|
|
||||||
while ((m_currentEntry == nullptr)
|
while ((m_currentEntry == nullptr) ||
|
||||||
|| (m_currentRecord = m_currentEntry->m_dataList[m_currentRecordIndex].Load()) == nullptr)
|
(m_currentRecord =
|
||||||
{
|
m_currentEntry->m_dataList[m_currentRecordIndex].Load()) ==
|
||||||
if (m_currentEntry == nullptr)
|
nullptr) {
|
||||||
{
|
if (m_currentEntry == nullptr) {
|
||||||
++m_currentBucketIndex;
|
++m_currentBucketIndex;
|
||||||
m_currentRecordIndex = 0U;
|
m_currentRecordIndex = 0U;
|
||||||
|
|
||||||
if (IsEnd())
|
if (IsEnd()) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentEntry = &m_hashTable.m_buckets[m_currentBucketIndex];
|
m_currentEntry = &m_hashTable.m_buckets[m_currentBucketIndex];
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
MoveToNextData();
|
MoveToNextData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,20 +168,16 @@ public:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Key GetKey() const override
|
Key GetKey() const override {
|
||||||
{
|
if (!IsValid()) {
|
||||||
if (!IsValid())
|
|
||||||
{
|
|
||||||
throw RuntimeException("HashTableIterator is not correctly used.");
|
throw RuntimeException("HashTableIterator is not correctly used.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_recordSerializer.Deserialize(*m_currentRecord).m_key;
|
return m_recordSerializer.Deserialize(*m_currentRecord).m_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value GetValue() const override
|
Value GetValue() const override {
|
||||||
{
|
if (!IsValid()) {
|
||||||
if (!IsValid())
|
|
||||||
{
|
|
||||||
throw RuntimeException("HashTableIterator is not correctly used.");
|
throw RuntimeException("HashTableIterator is not correctly used.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,23 +187,19 @@ public:
|
||||||
Iterator(const Iterator&) = delete;
|
Iterator(const Iterator&) = delete;
|
||||||
Iterator& operator=(const Iterator&) = delete;
|
Iterator& operator=(const Iterator&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool IsValid() const
|
bool IsValid() const {
|
||||||
{
|
return !IsEnd() && (m_currentEntry != nullptr) &&
|
||||||
return !IsEnd()
|
(m_currentRecord != nullptr);
|
||||||
&& (m_currentEntry != nullptr)
|
|
||||||
&& (m_currentRecord != nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsEnd() const
|
bool IsEnd() const {
|
||||||
{
|
return m_currentBucketIndex ==
|
||||||
return m_currentBucketIndex == static_cast<std::int64_t>(m_hashTable.m_buckets.size());
|
static_cast<std::int64_t>(m_hashTable.m_buckets.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoveToNextData()
|
void MoveToNextData() {
|
||||||
{
|
if (++m_currentRecordIndex >= HashTable::Entry::c_numDataPerEntry) {
|
||||||
if (++m_currentRecordIndex >= HashTable::Entry::c_numDataPerEntry)
|
|
||||||
{
|
|
||||||
m_currentRecordIndex = 0U;
|
m_currentRecordIndex = 0U;
|
||||||
m_currentEntry = m_currentEntry->m_next.Load();
|
m_currentEntry = m_currentEntry->m_next.Load();
|
||||||
}
|
}
|
||||||
|
@ -246,60 +215,48 @@ private:
|
||||||
const RecordBuffer* m_currentRecord;
|
const RecordBuffer* m_currentRecord;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The following warning is from the virtual inheritance and safe to disable in
|
||||||
// The following warning is from the virtual inheritance and safe to disable in this case.
|
// this case. https://msdn.microsoft.com/en-us/library/6b3sy7ae.aspx
|
||||||
// https://msdn.microsoft.com/en-us/library/6b3sy7ae.aspx
|
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable:4250)
|
#pragma warning(disable : 4250)
|
||||||
|
|
||||||
// WritableHashTable class implements IWritableHashTable interface and also provides
|
// WritableHashTable class implements IWritableHashTable interface and also
|
||||||
// the read only access (Get()) to the hash table.
|
// provides the read only access (Get()) to the hash table. Note the virtual
|
||||||
// Note the virtual inheritance on ReadOnlyHashTable<Allocator> so that any derived class
|
// inheritance on ReadOnlyHashTable<Allocator> so that any derived class can
|
||||||
// can have only one ReadOnlyHashTable base class instance.
|
// have only one ReadOnlyHashTable base class instance.
|
||||||
template <typename Allocator>
|
template <typename Allocator>
|
||||||
class WritableHashTable
|
class WritableHashTable : public virtual ReadOnlyHashTable<Allocator>,
|
||||||
: public virtual ReadOnlyHashTable<Allocator>
|
public IWritableHashTable {
|
||||||
, public IWritableHashTable
|
public:
|
||||||
{
|
|
||||||
public:
|
|
||||||
using Base = ReadOnlyHashTable<Allocator>;
|
using Base = ReadOnlyHashTable<Allocator>;
|
||||||
using HashTable = typename Base::HashTable;
|
using HashTable = typename Base::HashTable;
|
||||||
|
|
||||||
WritableHashTable(
|
WritableHashTable(HashTable& hashTable, IEpochActionManager& epochManager)
|
||||||
HashTable& hashTable,
|
: Base(hashTable), m_epochManager{epochManager} {}
|
||||||
IEpochActionManager& epochManager)
|
|
||||||
: Base(hashTable)
|
|
||||||
, m_epochManager{ epochManager }
|
|
||||||
{}
|
|
||||||
|
|
||||||
virtual void Add(const Key& key, const Value& value) override
|
virtual void Add(const Key& key, const Value& value) override {
|
||||||
{
|
|
||||||
Add(CreateRecordBuffer(key, value));
|
Add(CreateRecordBuffer(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool Remove(const Key& key) override
|
virtual bool Remove(const Key& key) override {
|
||||||
{
|
|
||||||
const auto bucketInfo = this->GetBucketInfo(key);
|
const auto bucketInfo = this->GetBucketInfo(key);
|
||||||
|
|
||||||
auto* entry = &(this->m_hashTable.m_buckets[bucketInfo.first]);
|
auto* entry = &(this->m_hashTable.m_buckets[bucketInfo.first]);
|
||||||
|
|
||||||
typename HashTable::Lock lock{ this->m_hashTable.GetMutex(bucketInfo.first) };
|
typename HashTable::Lock lock{this->m_hashTable.GetMutex(bucketInfo.first)};
|
||||||
|
|
||||||
// Note that similar to Add(), the following block is performed inside a critical section,
|
// Note that similar to Add(), the following block is performed inside a
|
||||||
// therefore, it is safe to do "Load"s with memory_order_relaxed.
|
// critical section, therefore, it is safe to do "Load"s with
|
||||||
while (entry != nullptr)
|
// memory_order_relaxed.
|
||||||
{
|
while (entry != nullptr) {
|
||||||
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i)
|
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) {
|
||||||
{
|
if (bucketInfo.second == entry->m_tags[i]) {
|
||||||
if (bucketInfo.second == entry->m_tags[i])
|
const auto data =
|
||||||
{
|
entry->m_dataList[i].Load(std::memory_order_relaxed);
|
||||||
const auto data = entry->m_dataList[i].Load(std::memory_order_relaxed);
|
|
||||||
|
|
||||||
if (data != nullptr)
|
if (data != nullptr) {
|
||||||
{
|
|
||||||
const auto record = this->m_recordSerializer.Deserialize(*data);
|
const auto record = this->m_recordSerializer.Deserialize(*data);
|
||||||
if (record.m_key == key)
|
if (record.m_key == key) {
|
||||||
{
|
|
||||||
Remove(*entry, i);
|
Remove(*entry, i);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -313,21 +270,19 @@ public:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ISerializerPtr GetSerializer() const override
|
virtual ISerializerPtr GetSerializer() const override {
|
||||||
{
|
|
||||||
return std::make_unique<WritableHashTable::Serializer>(this->m_hashTable);
|
return std::make_unique<WritableHashTable::Serializer>(this->m_hashTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void Add(RecordBuffer* recordToAdd)
|
void Add(RecordBuffer* recordToAdd) {
|
||||||
{
|
|
||||||
assert(recordToAdd != nullptr);
|
assert(recordToAdd != nullptr);
|
||||||
|
|
||||||
const auto newRecord = this->m_recordSerializer.Deserialize(*recordToAdd);
|
const auto newRecord = this->m_recordSerializer.Deserialize(*recordToAdd);
|
||||||
const auto& newKey = newRecord.m_key;
|
const auto& newKey = newRecord.m_key;
|
||||||
const auto& newValue = newRecord.m_value;
|
const auto& newValue = newRecord.m_value;
|
||||||
|
|
||||||
Stat stat{ newKey.m_size, newValue.m_size };
|
Stat stat{newKey.m_size, newValue.m_size};
|
||||||
|
|
||||||
const auto bucketInfo = this->GetBucketInfo(newKey);
|
const auto bucketInfo = this->GetBucketInfo(newKey);
|
||||||
|
|
||||||
|
@ -336,33 +291,28 @@ protected:
|
||||||
typename HashTable::Entry* entryToUpdate = nullptr;
|
typename HashTable::Entry* entryToUpdate = nullptr;
|
||||||
std::uint8_t curDataIndex = 0U;
|
std::uint8_t curDataIndex = 0U;
|
||||||
|
|
||||||
typename HashTable::UniqueLock lock{ this->m_hashTable.GetMutex(bucketInfo.first) };
|
typename HashTable::UniqueLock lock{
|
||||||
|
this->m_hashTable.GetMutex(bucketInfo.first)};
|
||||||
|
|
||||||
// Note that the following block is performed inside a critical section, therefore,
|
// Note that the following block is performed inside a critical section,
|
||||||
// it is safe to do "Load"s with memory_order_relaxed.
|
// therefore, it is safe to do "Load"s with memory_order_relaxed.
|
||||||
while (curEntry != nullptr)
|
while (curEntry != nullptr) {
|
||||||
{
|
|
||||||
++stat.m_chainIndex;
|
++stat.m_chainIndex;
|
||||||
|
|
||||||
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i)
|
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) {
|
||||||
{
|
const auto data =
|
||||||
const auto data = curEntry->m_dataList[i].Load(std::memory_order_relaxed);
|
curEntry->m_dataList[i].Load(std::memory_order_relaxed);
|
||||||
|
|
||||||
if (data == nullptr)
|
if (data == nullptr) {
|
||||||
{
|
if (entryToUpdate == nullptr) {
|
||||||
if (entryToUpdate == nullptr)
|
// Found an entry with no data set, but still need to go through the
|
||||||
{
|
// end of the list to see if an entry with the given key exists.
|
||||||
// Found an entry with no data set, but still need to go through the end of
|
|
||||||
// the list to see if an entry with the given key exists.
|
|
||||||
entryToUpdate = curEntry;
|
entryToUpdate = curEntry;
|
||||||
curDataIndex = i;
|
curDataIndex = i;
|
||||||
}
|
}
|
||||||
}
|
} else if (curEntry->m_tags[i] == bucketInfo.second) {
|
||||||
else if (curEntry->m_tags[i] == bucketInfo.second)
|
|
||||||
{
|
|
||||||
const auto oldRecord = this->m_recordSerializer.Deserialize(*data);
|
const auto oldRecord = this->m_recordSerializer.Deserialize(*data);
|
||||||
if (newKey == oldRecord.m_key)
|
if (newKey == oldRecord.m_key) {
|
||||||
{
|
|
||||||
// Will overwrite this entry data.
|
// Will overwrite this entry data.
|
||||||
entryToUpdate = curEntry;
|
entryToUpdate = curEntry;
|
||||||
curDataIndex = i;
|
curDataIndex = i;
|
||||||
|
@ -373,19 +323,19 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Found the entry data to replaces.
|
// Found the entry data to replaces.
|
||||||
if (stat.m_oldValueSize != 0U)
|
if (stat.m_oldValueSize != 0U) {
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is the end of the chaining. If so, create a new entry if we haven't found
|
// Check if this is the end of the chaining. If so, create a new entry if
|
||||||
// any entry to update along the way.
|
// we haven't found any entry to update along the way.
|
||||||
if (entryToUpdate == nullptr && curEntry->m_next.Load(std::memory_order_relaxed) == nullptr)
|
if (entryToUpdate == nullptr &&
|
||||||
{
|
curEntry->m_next.Load(std::memory_order_relaxed) == nullptr) {
|
||||||
curEntry->m_next.Store(
|
curEntry->m_next.Store(
|
||||||
new (Detail::to_raw_pointer(
|
new (Detail::to_raw_pointer(
|
||||||
this->m_hashTable.template GetAllocator<typename HashTable::Entry>().allocate(1U)))
|
this->m_hashTable
|
||||||
typename HashTable::Entry(),
|
.template GetAllocator<typename HashTable::Entry>()
|
||||||
|
.allocate(1U))) typename HashTable::Entry(),
|
||||||
std::memory_order_release);
|
std::memory_order_release);
|
||||||
|
|
||||||
stat.m_isNewEntryAdded = true;
|
stat.m_isNewEntryAdded = true;
|
||||||
|
@ -396,7 +346,8 @@ protected:
|
||||||
|
|
||||||
assert(entryToUpdate != nullptr);
|
assert(entryToUpdate != nullptr);
|
||||||
|
|
||||||
auto recordToDelete = UpdateRecord(*entryToUpdate, curDataIndex, recordToAdd, bucketInfo.second);
|
auto recordToDelete = UpdateRecord(*entryToUpdate, curDataIndex,
|
||||||
|
recordToAdd, bucketInfo.second);
|
||||||
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
|
@ -405,10 +356,9 @@ protected:
|
||||||
ReleaseRecord(recordToDelete);
|
ReleaseRecord(recordToDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The chainIndex is the 1-based index for the given entry in the chained bucket list.
|
// The chainIndex is the 1-based index for the given entry in the chained
|
||||||
// It is assumed that this function is called under a lock.
|
// bucket list. It is assumed that this function is called under a lock.
|
||||||
void Remove(typename HashTable::Entry& entry, std::uint8_t index)
|
void Remove(typename HashTable::Entry& entry, std::uint8_t index) {
|
||||||
{
|
|
||||||
auto recordToDelete = UpdateRecord(entry, index, nullptr, 0U);
|
auto recordToDelete = UpdateRecord(entry, index, nullptr, 0U);
|
||||||
|
|
||||||
assert(recordToDelete != nullptr);
|
assert(recordToDelete != nullptr);
|
||||||
|
@ -416,36 +366,32 @@ protected:
|
||||||
const auto record = this->m_recordSerializer.Deserialize(*recordToDelete);
|
const auto record = this->m_recordSerializer.Deserialize(*recordToDelete);
|
||||||
|
|
||||||
UpdatePerfDataForRemove(
|
UpdatePerfDataForRemove(
|
||||||
Stat{
|
Stat{record.m_key.m_size, record.m_value.m_size, 0U});
|
||||||
record.m_key.m_size,
|
|
||||||
record.m_value.m_size,
|
|
||||||
0U
|
|
||||||
});
|
|
||||||
|
|
||||||
ReleaseRecord(recordToDelete);
|
ReleaseRecord(recordToDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Stat;
|
struct Stat;
|
||||||
|
|
||||||
class Serializer;
|
class Serializer;
|
||||||
|
|
||||||
RecordBuffer* CreateRecordBuffer(const Key& key, const Value& value)
|
RecordBuffer* CreateRecordBuffer(const Key& key, const Value& value) {
|
||||||
{
|
const auto bufferSize =
|
||||||
const auto bufferSize = this->m_recordSerializer.CalculateBufferSize(key, value);
|
this->m_recordSerializer.CalculateBufferSize(key, value);
|
||||||
auto buffer = Detail::to_raw_pointer(
|
auto buffer = Detail::to_raw_pointer(
|
||||||
this->m_hashTable.template GetAllocator<std::uint8_t>().allocate(bufferSize));
|
this->m_hashTable.template GetAllocator<std::uint8_t>().allocate(
|
||||||
|
bufferSize));
|
||||||
|
|
||||||
return this->m_recordSerializer.Serialize(key, value, buffer, bufferSize);
|
return this->m_recordSerializer.Serialize(key, value, buffer, bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordBuffer* UpdateRecord(
|
RecordBuffer* UpdateRecord(typename HashTable::Entry& entry,
|
||||||
typename HashTable::Entry& entry,
|
|
||||||
std::uint8_t index,
|
std::uint8_t index,
|
||||||
RecordBuffer* newRecord,
|
RecordBuffer* newRecord,
|
||||||
std::uint8_t newTag)
|
std::uint8_t newTag) {
|
||||||
{
|
// This function should be called under a lock, so calling with
|
||||||
// This function should be called under a lock, so calling with memory_order_relaxed for Load() is safe.
|
// memory_order_relaxed for Load() is safe.
|
||||||
auto& recordHolder = entry.m_dataList[index];
|
auto& recordHolder = entry.m_dataList[index];
|
||||||
auto oldRecord = recordHolder.Load(std::memory_order_relaxed);
|
auto oldRecord = recordHolder.Load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
@ -455,54 +401,49 @@ private:
|
||||||
return oldRecord;
|
return oldRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReleaseRecord(RecordBuffer* record)
|
void ReleaseRecord(RecordBuffer* record) {
|
||||||
{
|
if (record == nullptr) {
|
||||||
if (record == nullptr)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_epochManager.RegisterAction(
|
m_epochManager.RegisterAction([this, record]() {
|
||||||
[this, record]()
|
|
||||||
{
|
|
||||||
record->~RecordBuffer();
|
record->~RecordBuffer();
|
||||||
this->m_hashTable.template GetAllocator<RecordBuffer>().deallocate(record, 1U);
|
this->m_hashTable.template GetAllocator<RecordBuffer>().deallocate(record,
|
||||||
|
1U);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePerfDataForAdd(const Stat& stat)
|
void UpdatePerfDataForAdd(const Stat& stat) {
|
||||||
{
|
|
||||||
auto& perfData = this->m_hashTable.m_perfData;
|
auto& perfData = this->m_hashTable.m_perfData;
|
||||||
|
|
||||||
if (stat.m_oldValueSize != 0U)
|
if (stat.m_oldValueSize != 0U) {
|
||||||
{
|
|
||||||
// Updating the existing record. Therefore, no change in the key size.
|
// Updating the existing record. Therefore, no change in the key size.
|
||||||
perfData.Add(HashTablePerfCounter::TotalValueSize,
|
perfData.Add(HashTablePerfCounter::TotalValueSize,
|
||||||
static_cast<HashTablePerfData::TValue>(stat.m_valueSize) - stat.m_oldValueSize);
|
static_cast<HashTablePerfData::TValue>(stat.m_valueSize) -
|
||||||
}
|
stat.m_oldValueSize);
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
// We are adding a new data instead of replacing.
|
// We are adding a new data instead of replacing.
|
||||||
perfData.Add(HashTablePerfCounter::TotalKeySize, stat.m_keySize);
|
perfData.Add(HashTablePerfCounter::TotalKeySize, stat.m_keySize);
|
||||||
perfData.Add(HashTablePerfCounter::TotalValueSize, stat.m_valueSize);
|
perfData.Add(HashTablePerfCounter::TotalValueSize, stat.m_valueSize);
|
||||||
perfData.Add(HashTablePerfCounter::TotalIndexSize,
|
perfData.Add(
|
||||||
|
HashTablePerfCounter::TotalIndexSize,
|
||||||
// Record overhead.
|
// Record overhead.
|
||||||
this->m_recordSerializer.CalculateRecordOverhead()
|
this->m_recordSerializer.CalculateRecordOverhead()
|
||||||
// Entry overhead if created.
|
// Entry overhead if created.
|
||||||
+ (stat.m_isNewEntryAdded ? sizeof(typename HashTable::Entry) : 0U));
|
+ (stat.m_isNewEntryAdded ? sizeof(typename HashTable::Entry)
|
||||||
|
: 0U));
|
||||||
|
|
||||||
perfData.Min(HashTablePerfCounter::MinKeySize, stat.m_keySize);
|
perfData.Min(HashTablePerfCounter::MinKeySize, stat.m_keySize);
|
||||||
perfData.Max(HashTablePerfCounter::MaxKeySize, stat.m_keySize);
|
perfData.Max(HashTablePerfCounter::MaxKeySize, stat.m_keySize);
|
||||||
|
|
||||||
perfData.Increment(HashTablePerfCounter::RecordsCount);
|
perfData.Increment(HashTablePerfCounter::RecordsCount);
|
||||||
|
|
||||||
if (stat.m_isNewEntryAdded)
|
if (stat.m_isNewEntryAdded) {
|
||||||
{
|
|
||||||
perfData.Increment(HashTablePerfCounter::ChainingEntriesCount);
|
perfData.Increment(HashTablePerfCounter::ChainingEntriesCount);
|
||||||
|
|
||||||
if (stat.m_chainIndex > 1U)
|
if (stat.m_chainIndex > 1U) {
|
||||||
{
|
perfData.Max(HashTablePerfCounter::MaxBucketChainLength,
|
||||||
perfData.Max(HashTablePerfCounter::MaxBucketChainLength, stat.m_chainIndex);
|
stat.m_chainIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -511,14 +452,14 @@ private:
|
||||||
perfData.Max(HashTablePerfCounter::MaxValueSize, stat.m_valueSize);
|
perfData.Max(HashTablePerfCounter::MaxValueSize, stat.m_valueSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePerfDataForRemove(const Stat& stat)
|
void UpdatePerfDataForRemove(const Stat& stat) {
|
||||||
{
|
|
||||||
auto& perfData = this->m_hashTable.m_perfData;
|
auto& perfData = this->m_hashTable.m_perfData;
|
||||||
|
|
||||||
perfData.Decrement(HashTablePerfCounter::RecordsCount);
|
perfData.Decrement(HashTablePerfCounter::RecordsCount);
|
||||||
perfData.Subtract(HashTablePerfCounter::TotalKeySize, stat.m_keySize);
|
perfData.Subtract(HashTablePerfCounter::TotalKeySize, stat.m_keySize);
|
||||||
perfData.Subtract(HashTablePerfCounter::TotalValueSize, stat.m_valueSize);
|
perfData.Subtract(HashTablePerfCounter::TotalValueSize, stat.m_valueSize);
|
||||||
perfData.Subtract(HashTablePerfCounter::TotalIndexSize, this->m_recordSerializer.CalculateRecordOverhead());
|
perfData.Subtract(HashTablePerfCounter::TotalIndexSize,
|
||||||
|
this->m_recordSerializer.CalculateRecordOverhead());
|
||||||
}
|
}
|
||||||
|
|
||||||
IEpochActionManager& m_epochManager;
|
IEpochActionManager& m_epochManager;
|
||||||
|
@ -526,26 +467,22 @@ private:
|
||||||
|
|
||||||
#pragma warning(pop)
|
#pragma warning(pop)
|
||||||
|
|
||||||
|
|
||||||
// WritableHashTable::Stat struct encapsulates stats for Add()/Remove().
|
// WritableHashTable::Stat struct encapsulates stats for Add()/Remove().
|
||||||
template <typename Allocator>
|
template <typename Allocator>
|
||||||
struct WritableHashTable<Allocator>::Stat
|
struct WritableHashTable<Allocator>::Stat {
|
||||||
{
|
|
||||||
using KeySize = Key::size_type;
|
using KeySize = Key::size_type;
|
||||||
using ValueSize = Value::size_type;
|
using ValueSize = Value::size_type;
|
||||||
|
|
||||||
explicit Stat(
|
explicit Stat(KeySize keySize = 0U,
|
||||||
KeySize keySize = 0U,
|
|
||||||
ValueSize valueSize = 0U,
|
ValueSize valueSize = 0U,
|
||||||
ValueSize oldValueSize = 0U,
|
ValueSize oldValueSize = 0U,
|
||||||
std::uint32_t chainIndex = 0U,
|
std::uint32_t chainIndex = 0U,
|
||||||
bool isNewEntryAdded = false)
|
bool isNewEntryAdded = false)
|
||||||
: m_keySize{ keySize }
|
: m_keySize{keySize},
|
||||||
, m_valueSize{ valueSize }
|
m_valueSize{valueSize},
|
||||||
, m_oldValueSize{ oldValueSize }
|
m_oldValueSize{oldValueSize},
|
||||||
, m_chainIndex{ chainIndex }
|
m_chainIndex{chainIndex},
|
||||||
, m_isNewEntryAdded{ isNewEntryAdded }
|
m_isNewEntryAdded{isNewEntryAdded} {}
|
||||||
{}
|
|
||||||
|
|
||||||
KeySize m_keySize;
|
KeySize m_keySize;
|
||||||
ValueSize m_valueSize;
|
ValueSize m_valueSize;
|
||||||
|
@ -554,29 +491,24 @@ struct WritableHashTable<Allocator>::Stat
|
||||||
bool m_isNewEntryAdded;
|
bool m_isNewEntryAdded;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// WritableHashTable::Serializer class that implements ISerializer, which
|
||||||
// WritableHashTable::Serializer class that implements ISerializer, which provides
|
// provides the functionality to serialize the WritableHashTable.
|
||||||
// the functionality to serialize the WritableHashTable.
|
|
||||||
template <typename Allocator>
|
template <typename Allocator>
|
||||||
class WritableHashTable<Allocator>::Serializer : public IWritableHashTable::ISerializer
|
class WritableHashTable<Allocator>::Serializer
|
||||||
{
|
: public IWritableHashTable::ISerializer {
|
||||||
public:
|
public:
|
||||||
explicit Serializer(HashTable& hashTable)
|
explicit Serializer(HashTable& hashTable) : m_hashTable{hashTable} {}
|
||||||
: m_hashTable{ hashTable }
|
|
||||||
{}
|
|
||||||
|
|
||||||
Serializer(const Serializer&) = delete;
|
Serializer(const Serializer&) = delete;
|
||||||
Serializer& operator=(const Serializer&) = delete;
|
Serializer& operator=(const Serializer&) = delete;
|
||||||
|
|
||||||
void Serialize(
|
void Serialize(std::ostream& stream,
|
||||||
std::ostream& stream,
|
const Utils::Properties& /* properties */) override {
|
||||||
const Utils::Properties& /* properties */) override
|
ReadWrite::Serializer<HashTable, ReadWrite::ReadOnlyHashTable>{}.Serialize(
|
||||||
{
|
m_hashTable, stream);
|
||||||
ReadWrite::Serializer<
|
|
||||||
HashTable, ReadWrite::ReadOnlyHashTable>{}.Serialize(m_hashTable, stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HashTable& m_hashTable;
|
HashTable& m_hashTable;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
#include <cstdint>
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
#include "Epoch/IEpochActionManager.h"
|
#include "Epoch/IEpochActionManager.h"
|
||||||
#include "Log/PerfCounter.h"
|
#include "Log/PerfCounter.h"
|
||||||
|
@ -9,27 +9,21 @@
|
||||||
#include "Utils/Exception.h"
|
#include "Utils/Exception.h"
|
||||||
#include "Utils/Properties.h"
|
#include "Utils/Properties.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace HashTable {
|
||||||
namespace HashTable
|
namespace ReadWrite {
|
||||||
{
|
|
||||||
namespace ReadWrite
|
|
||||||
{
|
|
||||||
|
|
||||||
// Note that the HashTable template parameter in this file is
|
// Note that the HashTable template parameter in this file is
|
||||||
// HashTable::ReadWrite::ReadOnlyHashTable<Allocator>::HashTable.
|
// HashTable::ReadWrite::ReadOnlyHashTable<Allocator>::HashTable.
|
||||||
// However, due to the cyclic dependency, it needs to be passed as a template type.
|
// However, due to the cyclic dependency, it needs to be passed as a template
|
||||||
|
// type.
|
||||||
|
|
||||||
|
// All the deprecated (previous versions) serializer should be put inside the
|
||||||
|
// Deprecated namespace. Removing any of the Deprecated serializers from the
|
||||||
|
// source code will require the major package version change.
|
||||||
|
namespace Deprecated {} // namespace Deprecated
|
||||||
|
|
||||||
// All the deprecated (previous versions) serializer should be put inside the Deprecated namespace.
|
namespace Current {
|
||||||
// Removing any of the Deprecated serializers from the source code will require the major package version change.
|
|
||||||
namespace Deprecated
|
|
||||||
{
|
|
||||||
} // namespace Deprecated
|
|
||||||
|
|
||||||
|
|
||||||
namespace Current
|
|
||||||
{
|
|
||||||
|
|
||||||
constexpr std::uint8_t c_version = 1U;
|
constexpr std::uint8_t c_version = 1U;
|
||||||
|
|
||||||
|
@ -40,18 +34,14 @@ constexpr std::uint8_t c_version = 1U;
|
||||||
// <Key size> <Key bytes> <Value size> <Value bytes>
|
// <Key size> <Key bytes> <Value size> <Value bytes>
|
||||||
// Otherwise, end of the records.
|
// Otherwise, end of the records.
|
||||||
template <typename HashTable, template <typename> class ReadOnlyHashTable>
|
template <typename HashTable, template <typename> class ReadOnlyHashTable>
|
||||||
class Serializer
|
class Serializer {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
Serializer() = default;
|
Serializer() = default;
|
||||||
|
|
||||||
Serializer(const Serializer&) = delete;
|
Serializer(const Serializer&) = delete;
|
||||||
Serializer& operator=(const Serializer&) = delete;
|
Serializer& operator=(const Serializer&) = delete;
|
||||||
|
|
||||||
void Serialize(
|
void Serialize(HashTable& hashTable, std::ostream& stream) const {
|
||||||
HashTable& hashTable,
|
|
||||||
std::ostream& stream) const
|
|
||||||
{
|
|
||||||
auto& perfData = hashTable.m_perfData;
|
auto& perfData = hashTable.m_perfData;
|
||||||
perfData.Set(HashTablePerfCounter::RecordsCountSavedFromSerializer, 0);
|
perfData.Set(HashTablePerfCounter::RecordsCountSavedFromSerializer, 0);
|
||||||
|
|
||||||
|
@ -61,11 +51,11 @@ public:
|
||||||
|
|
||||||
helper.Serialize(&hashTable.m_setting, sizeof(hashTable.m_setting));
|
helper.Serialize(&hashTable.m_setting, sizeof(hashTable.m_setting));
|
||||||
|
|
||||||
ReadOnlyHashTable<typename HashTable::Allocator> readOnlyHashTable(hashTable);
|
ReadOnlyHashTable<typename HashTable::Allocator> readOnlyHashTable(
|
||||||
|
hashTable);
|
||||||
|
|
||||||
auto iterator = readOnlyHashTable.GetIterator();
|
auto iterator = readOnlyHashTable.GetIterator();
|
||||||
while (iterator->MoveNext())
|
while (iterator->MoveNext()) {
|
||||||
{
|
|
||||||
helper.Serialize(true); // Indicates record exists.
|
helper.Serialize(true); // Indicates record exists.
|
||||||
const auto key = iterator->GetKey();
|
const auto key = iterator->GetKey();
|
||||||
const auto value = iterator->GetValue();
|
const auto value = iterator->GetValue();
|
||||||
|
@ -81,40 +71,39 @@ public:
|
||||||
|
|
||||||
helper.Serialize(false); // Indicates the end of records.
|
helper.Serialize(false); // Indicates the end of records.
|
||||||
|
|
||||||
// Flush perf counter so that the values are up to date when GetPerfData() is called.
|
// Flush perf counter so that the values are up to date when GetPerfData()
|
||||||
|
// is called.
|
||||||
std::atomic_thread_fence(std::memory_order_release);
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Current Deserializer used for deserializing hash tables.
|
// Current Deserializer used for deserializing hash tables.
|
||||||
template <typename Memory, typename HashTable, template <typename> class WritableHashTable>
|
template <typename Memory,
|
||||||
class Deserializer
|
typename HashTable,
|
||||||
{
|
template <typename>
|
||||||
public:
|
class WritableHashTable>
|
||||||
explicit Deserializer(const Utils::Properties& /* properties */)
|
class Deserializer {
|
||||||
{}
|
public:
|
||||||
|
explicit Deserializer(const Utils::Properties& /* properties */) {}
|
||||||
|
|
||||||
Deserializer(const Deserializer&) = delete;
|
Deserializer(const Deserializer&) = delete;
|
||||||
Deserializer& operator=(const Deserializer&) = delete;
|
Deserializer& operator=(const Deserializer&) = delete;
|
||||||
|
|
||||||
typename Memory::template UniquePtr<HashTable> Deserialize(
|
typename Memory::template UniquePtr<HashTable> Deserialize(
|
||||||
Memory& memory,
|
Memory& memory,
|
||||||
std::istream& stream) const
|
std::istream& stream) const {
|
||||||
{
|
|
||||||
DeserializerHelper helper(stream);
|
DeserializerHelper helper(stream);
|
||||||
|
|
||||||
typename HashTable::Setting setting;
|
typename HashTable::Setting setting;
|
||||||
helper.Deserialize(setting);
|
helper.Deserialize(setting);
|
||||||
|
|
||||||
auto hashTable{ memory.template MakeUnique<HashTable>(
|
auto hashTable{
|
||||||
setting,
|
memory.template MakeUnique<HashTable>(setting, memory.GetAllocator())};
|
||||||
memory.GetAllocator()) };
|
|
||||||
|
|
||||||
EpochActionManager epochActionManager;
|
EpochActionManager epochActionManager;
|
||||||
|
|
||||||
WritableHashTable<typename HashTable::Allocator> writableHashTable(
|
WritableHashTable<typename HashTable::Allocator> writableHashTable(
|
||||||
*hashTable,
|
*hashTable, epochActionManager);
|
||||||
epochActionManager);
|
|
||||||
|
|
||||||
auto& perfData = hashTable->m_perfData;
|
auto& perfData = hashTable->m_perfData;
|
||||||
|
|
||||||
|
@ -124,8 +113,7 @@ public:
|
||||||
bool hasMoreData = false;
|
bool hasMoreData = false;
|
||||||
helper.Deserialize(hasMoreData);
|
helper.Deserialize(hasMoreData);
|
||||||
|
|
||||||
while (hasMoreData)
|
while (hasMoreData) {
|
||||||
{
|
|
||||||
IReadOnlyHashTable::Key key;
|
IReadOnlyHashTable::Key key;
|
||||||
IReadOnlyHashTable::Value value;
|
IReadOnlyHashTable::Value value;
|
||||||
|
|
||||||
|
@ -143,74 +131,77 @@ public:
|
||||||
|
|
||||||
helper.Deserialize(hasMoreData);
|
helper.Deserialize(hasMoreData);
|
||||||
|
|
||||||
perfData.Increment(HashTablePerfCounter::RecordsCountLoadedFromSerializer);
|
perfData.Increment(
|
||||||
|
HashTablePerfCounter::RecordsCountLoadedFromSerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush perf counter so that the values are up to date when GetPerfData() is called.
|
// Flush perf counter so that the values are up to date when GetPerfData()
|
||||||
|
// is called.
|
||||||
std::atomic_thread_fence(std::memory_order_release);
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
|
||||||
return hashTable;
|
return hashTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Deserializer internally uses WritableHashTable for deserialization, therefore
|
// Deserializer internally uses WritableHashTable for deserialization,
|
||||||
// an implementation of IEpochActionManager is needed. Since all the keys in the hash table
|
// therefore an implementation of IEpochActionManager is needed. Since all the
|
||||||
// are expected to be unique, no RegisterAction() should be called.
|
// keys in the hash table are expected to be unique, no RegisterAction()
|
||||||
class EpochActionManager : public IEpochActionManager
|
// should be called.
|
||||||
{
|
class EpochActionManager : public IEpochActionManager {
|
||||||
public:
|
public:
|
||||||
void RegisterAction(Action&& /* action */) override
|
void RegisterAction(Action&& /* action */) override {
|
||||||
{
|
// Since it is assumed that the serializer is loading from the stream
|
||||||
// Since it is assumed that the serializer is loading from the stream generated by the same serializer,
|
// generated by the same serializer, it is guaranteed that all the keys
|
||||||
// it is guaranteed that all the keys are unique (a property of a hash table). Therefore, RegisterAction()
|
// are unique (a property of a hash table). Therefore, RegisterAction()
|
||||||
// should not be called by the WritableHashTable.
|
// should not be called by the WritableHashTable.
|
||||||
throw RuntimeException("RegisterAction() should not be called from the serializer.");
|
throw RuntimeException(
|
||||||
|
"RegisterAction() should not be called from the serializer.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Current
|
} // namespace Current
|
||||||
|
|
||||||
|
|
||||||
// Serializer is the main driver for serializing a hash table.
|
// Serializer is the main driver for serializing a hash table.
|
||||||
// It always uses the Current::Serializer for serializing a hash table.
|
// It always uses the Current::Serializer for serializing a hash table.
|
||||||
template <typename HashTable, template <typename> class ReadOnlyHashTable>
|
template <typename HashTable, template <typename> class ReadOnlyHashTable>
|
||||||
class Serializer
|
class Serializer {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
Serializer() = default;
|
Serializer() = default;
|
||||||
Serializer(const Serializer&) = delete;
|
Serializer(const Serializer&) = delete;
|
||||||
Serializer& operator=(const Serializer&) = delete;
|
Serializer& operator=(const Serializer&) = delete;
|
||||||
|
|
||||||
void Serialize(HashTable& hashTable, std::ostream& stream) const
|
void Serialize(HashTable& hashTable, std::ostream& stream) const {
|
||||||
{
|
Current::Serializer<HashTable, ReadOnlyHashTable>{}.Serialize(hashTable,
|
||||||
Current::Serializer<HashTable, ReadOnlyHashTable>{}.Serialize(hashTable, stream);
|
stream);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Deserializer is the main driver for deserializing the input stream to create a hash table.
|
// Deserializer is the main driver for deserializing the input stream to create
|
||||||
template <typename Memory, typename HashTable, template <typename> class WritableHashTable>
|
// a hash table.
|
||||||
class Deserializer
|
template <typename Memory,
|
||||||
{
|
typename HashTable,
|
||||||
public:
|
template <typename>
|
||||||
|
class WritableHashTable>
|
||||||
|
class Deserializer {
|
||||||
|
public:
|
||||||
explicit Deserializer(const Utils::Properties& properties)
|
explicit Deserializer(const Utils::Properties& properties)
|
||||||
: m_properties(properties)
|
: m_properties(properties) {}
|
||||||
{}
|
|
||||||
|
|
||||||
Deserializer(const Deserializer&) = delete;
|
Deserializer(const Deserializer&) = delete;
|
||||||
Deserializer& operator=(const Deserializer&) = delete;
|
Deserializer& operator=(const Deserializer&) = delete;
|
||||||
|
|
||||||
typename Memory::template UniquePtr<HashTable> Deserialize(
|
typename Memory::template UniquePtr<HashTable> Deserialize(
|
||||||
Memory& memory,
|
Memory& memory,
|
||||||
std::istream& stream) const
|
std::istream& stream) const {
|
||||||
{
|
|
||||||
std::uint8_t version = 0U;
|
std::uint8_t version = 0U;
|
||||||
DeserializerHelper(stream).Deserialize(version);
|
DeserializerHelper(stream).Deserialize(version);
|
||||||
|
|
||||||
switch (version)
|
switch (version) {
|
||||||
{
|
|
||||||
case Current::c_version:
|
case Current::c_version:
|
||||||
return Current::Deserializer<Memory, HashTable, WritableHashTable>{ m_properties }.Deserialize(memory, stream);
|
return Current::Deserializer<Memory, HashTable, WritableHashTable>{
|
||||||
|
m_properties}
|
||||||
|
.Deserialize(memory, stream);
|
||||||
default:
|
default:
|
||||||
boost::format err("Unsupported version '%1%' is given.");
|
boost::format err("Unsupported version '%1%' is given.");
|
||||||
err % version;
|
err % version;
|
||||||
|
@ -218,11 +209,10 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const Utils::Properties& m_properties;
|
const Utils::Properties& m_properties;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ReadWrite
|
} // namespace ReadWrite
|
||||||
} // namespace HashTable
|
} // namespace HashTable
|
||||||
} // namespace L4
|
} // namespace L4
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
@ -8,12 +9,9 @@
|
||||||
#include "Interprocess/Connection/EndPointInfo.h"
|
#include "Interprocess/Connection/EndPointInfo.h"
|
||||||
#include "Interprocess/Utils/Handle.h"
|
#include "Interprocess/Utils/Handle.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Interprocess {
|
||||||
namespace Interprocess
|
namespace Connection {
|
||||||
{
|
|
||||||
namespace Connection
|
|
||||||
{
|
|
||||||
|
|
||||||
// ConnectionMonitor monitors any registered end points.
|
// ConnectionMonitor monitors any registered end points.
|
||||||
// ConnectionMonitor creates a kernel event for local end point,
|
// ConnectionMonitor creates a kernel event for local end point,
|
||||||
|
@ -22,9 +20,8 @@ namespace Connection
|
||||||
// is closed, the callback registered is triggered and the remote endpoint
|
// is closed, the callback registered is triggered and the remote endpoint
|
||||||
// is removed from the ConnectionMonitor after the callback is finished..
|
// is removed from the ConnectionMonitor after the callback is finished..
|
||||||
class ConnectionMonitor
|
class ConnectionMonitor
|
||||||
: public std::enable_shared_from_this<ConnectionMonitor>
|
: public std::enable_shared_from_this<ConnectionMonitor> {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
using Callback = std::function<void(const EndPointInfo&)>;
|
using Callback = std::function<void(const EndPointInfo&)>;
|
||||||
|
|
||||||
ConnectionMonitor();
|
ConnectionMonitor();
|
||||||
|
@ -41,7 +38,7 @@ public:
|
||||||
ConnectionMonitor(const ConnectionMonitor&) = delete;
|
ConnectionMonitor(const ConnectionMonitor&) = delete;
|
||||||
ConnectionMonitor& operator=(const ConnectionMonitor&) = delete;
|
ConnectionMonitor& operator=(const ConnectionMonitor&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class HandleMonitor;
|
class HandleMonitor;
|
||||||
|
|
||||||
// UnRegister() removes the unregistered end points from m_remoteEvents.
|
// UnRegister() removes the unregistered end points from m_remoteEvents.
|
||||||
|
@ -51,7 +48,8 @@ private:
|
||||||
|
|
||||||
Utils::Handle m_localEvent;
|
Utils::Handle m_localEvent;
|
||||||
|
|
||||||
mutable std::map<EndPointInfo, std::unique_ptr<HandleMonitor>> m_remoteMonitors;
|
mutable std::map<EndPointInfo, std::unique_ptr<HandleMonitor>>
|
||||||
|
m_remoteMonitors;
|
||||||
|
|
||||||
mutable std::mutex m_mutexOnRemoteMonitors;
|
mutable std::mutex m_mutexOnRemoteMonitors;
|
||||||
|
|
||||||
|
@ -60,32 +58,26 @@ private:
|
||||||
mutable std::mutex m_mutexOnUnregisteredEndPoints;
|
mutable std::mutex m_mutexOnUnregisteredEndPoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ConnectionMonitor::HandleMonitor opens the given endpoint's process
|
// ConnectionMonitor::HandleMonitor opens the given endpoint's process
|
||||||
// and event handles and waits for any event triggers.
|
// and event handles and waits for any event triggers.
|
||||||
class ConnectionMonitor::HandleMonitor
|
class ConnectionMonitor::HandleMonitor {
|
||||||
{
|
public:
|
||||||
public:
|
HandleMonitor(const EndPointInfo& remoteEndPoint, Callback callback);
|
||||||
HandleMonitor(
|
|
||||||
const EndPointInfo& remoteEndPoint,
|
|
||||||
Callback callback);
|
|
||||||
|
|
||||||
HandleMonitor(const HandleMonitor&) = delete;
|
HandleMonitor(const HandleMonitor&) = delete;
|
||||||
HandleMonitor& operator=(const HandleMonitor&) = delete;
|
HandleMonitor& operator=(const HandleMonitor&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class Waiter;
|
class Waiter;
|
||||||
|
|
||||||
std::unique_ptr<Waiter> m_eventWaiter;
|
std::unique_ptr<Waiter> m_eventWaiter;
|
||||||
std::unique_ptr<Waiter> m_processWaiter;
|
std::unique_ptr<Waiter> m_processWaiter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ConnectionMonitor::HandleMonitor::Waiter waits on the given handle and calls
|
// ConnectionMonitor::HandleMonitor::Waiter waits on the given handle and calls
|
||||||
// the given callback when an event is triggered on the handle.
|
// the given callback when an event is triggered on the handle.
|
||||||
class ConnectionMonitor::HandleMonitor::Waiter
|
class ConnectionMonitor::HandleMonitor::Waiter {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
using Callback = std::function<void()>;
|
using Callback = std::function<void()>;
|
||||||
|
|
||||||
Waiter(Utils::Handle handle, Callback callback);
|
Waiter(Utils::Handle handle, Callback callback);
|
||||||
|
@ -95,9 +87,8 @@ public:
|
||||||
Waiter(const Waiter&) = delete;
|
Waiter(const Waiter&) = delete;
|
||||||
Waiter& operator=(const Waiter&) = delete;
|
Waiter& operator=(const Waiter&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static VOID CALLBACK OnEvent(
|
static VOID CALLBACK OnEvent(PTP_CALLBACK_INSTANCE instance,
|
||||||
PTP_CALLBACK_INSTANCE instance,
|
|
||||||
PVOID context,
|
PVOID context,
|
||||||
PTP_WAIT wait,
|
PTP_WAIT wait,
|
||||||
TP_WAIT_RESULT waitResult);
|
TP_WAIT_RESULT waitResult);
|
||||||
|
|
|
@ -1,34 +1,24 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <boost/uuid/uuid.hpp>
|
#include <boost/uuid/uuid.hpp>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Interprocess {
|
||||||
namespace Interprocess
|
namespace Connection {
|
||||||
{
|
|
||||||
namespace Connection
|
|
||||||
{
|
|
||||||
|
|
||||||
// EndPointInfo struct encapsulates the connection end point
|
// EndPointInfo struct encapsulates the connection end point
|
||||||
// information across process boundaries.
|
// information across process boundaries.
|
||||||
struct EndPointInfo
|
struct EndPointInfo {
|
||||||
{
|
explicit EndPointInfo(std::uint32_t pid = 0U,
|
||||||
explicit EndPointInfo(
|
|
||||||
std::uint32_t pid = 0U,
|
|
||||||
const boost::uuids::uuid& uuid = {})
|
const boost::uuids::uuid& uuid = {})
|
||||||
: m_pid{ pid }
|
: m_pid{pid}, m_uuid{uuid} {}
|
||||||
, m_uuid{ uuid }
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool operator==(const EndPointInfo& other) const
|
bool operator==(const EndPointInfo& other) const {
|
||||||
{
|
return (m_pid == other.m_pid) && (m_uuid == other.m_uuid);
|
||||||
return (m_pid == other.m_pid)
|
|
||||||
&& (m_uuid == other.m_uuid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator<(const EndPointInfo& other) const
|
bool operator<(const EndPointInfo& other) const {
|
||||||
{
|
|
||||||
return m_uuid < other.m_uuid;
|
return m_uuid < other.m_uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,26 +3,20 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "Interprocess/Connection/EndPointInfo.h"
|
#include "Interprocess/Connection/EndPointInfo.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Interprocess {
|
||||||
namespace Interprocess
|
namespace Connection {
|
||||||
{
|
|
||||||
namespace Connection
|
|
||||||
{
|
|
||||||
|
|
||||||
// EndPointInfoFactory creates an EndPointInfo object with the current
|
// EndPointInfoFactory creates an EndPointInfo object with the current
|
||||||
// process id and a random uuid.
|
// process id and a random uuid.
|
||||||
class EndPointInfoFactory
|
class EndPointInfoFactory {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
EndPointInfo Create() const;
|
EndPointInfo Create() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// StringConverter provides a functionality to convert EndPointInfo to a string.
|
// StringConverter provides a functionality to convert EndPointInfo to a string.
|
||||||
class StringConverter
|
class StringConverter {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
std::string operator()(const EndPointInfo& endPoint) const;
|
std::string operator()(const EndPointInfo& endPoint) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,13 @@
|
||||||
|
|
||||||
#include <boost/interprocess/containers/list.hpp>
|
#include <boost/interprocess/containers/list.hpp>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Interprocess {
|
||||||
namespace Interprocess
|
namespace Container {
|
||||||
{
|
|
||||||
namespace Container
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
template <typename T, typename Allocator>
|
template <typename T, typename Allocator>
|
||||||
using List = boost::interprocess::list<T, Allocator>;
|
using List = boost::interprocess::list<T, Allocator>;
|
||||||
|
|
||||||
|
|
||||||
} // namespace Container
|
} // namespace Container
|
||||||
} // namespace Interprocess
|
} // namespace Interprocess
|
||||||
} // namespace L4
|
} // namespace L4
|
|
@ -2,17 +2,13 @@
|
||||||
|
|
||||||
#include <boost/interprocess/containers/string.hpp>
|
#include <boost/interprocess/containers/string.hpp>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Interprocess {
|
||||||
namespace Interprocess
|
namespace Container {
|
||||||
{
|
|
||||||
namespace Container
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
template <typename Allocator>
|
template <typename Allocator>
|
||||||
using String = boost::interprocess::basic_string<char, std::char_traits<char>, Allocator>;
|
using String =
|
||||||
|
boost::interprocess::basic_string<char, std::char_traits<char>, Allocator>;
|
||||||
|
|
||||||
} // namespace Container
|
} // namespace Container
|
||||||
} // namespace Interprocess
|
} // namespace Interprocess
|
||||||
|
|
|
@ -2,18 +2,13 @@
|
||||||
|
|
||||||
#include <boost/interprocess/containers/vector.hpp>
|
#include <boost/interprocess/containers/vector.hpp>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Interprocess {
|
||||||
namespace Interprocess
|
namespace Container {
|
||||||
{
|
|
||||||
namespace Container
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
template <typename T, typename Allocator>
|
template <typename T, typename Allocator>
|
||||||
using Vector = boost::interprocess::vector<T, Allocator>;
|
using Vector = boost::interprocess::vector<T, Allocator>;
|
||||||
|
|
||||||
|
|
||||||
} // namespace Container
|
} // namespace Container
|
||||||
} // namespace Interprocess
|
} // namespace Interprocess
|
||||||
} // namespace L4
|
} // namespace L4
|
|
@ -4,17 +4,13 @@
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include "Utils/Windows.h"
|
#include "Utils/Windows.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Interprocess {
|
||||||
namespace Interprocess
|
namespace Utils {
|
||||||
{
|
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
|
|
||||||
// Handle is a RAII class that manages the life time of the given HANDLE.
|
// Handle is a RAII class that manages the life time of the given HANDLE.
|
||||||
class Handle
|
class Handle {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
// If verifyHandle is true, it checks whether a given handle is valid.
|
// If verifyHandle is true, it checks whether a given handle is valid.
|
||||||
explicit Handle(HANDLE handle, bool verifyHandle = false);
|
explicit Handle(HANDLE handle, bool verifyHandle = false);
|
||||||
|
|
||||||
|
@ -26,10 +22,11 @@ public:
|
||||||
Handle& operator=(const Handle&) = delete;
|
Handle& operator=(const Handle&) = delete;
|
||||||
Handle& operator=(Handle&&) = delete;
|
Handle& operator=(Handle&&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HANDLE Verify(HANDLE handle, bool verifyHandle) const;
|
HANDLE Verify(HANDLE handle, bool verifyHandle) const;
|
||||||
|
|
||||||
std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&::CloseHandle)> m_handle;
|
std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&::CloseHandle)>
|
||||||
|
m_handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
|
|
|
@ -4,50 +4,40 @@
|
||||||
#include "EpochManager.h"
|
#include "EpochManager.h"
|
||||||
#include "HashTableManager.h"
|
#include "HashTableManager.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace LocalMemory {
|
||||||
namespace LocalMemory
|
|
||||||
{
|
|
||||||
|
|
||||||
class Context : private EpochRefPolicy<EpochManager::TheEpochRefManager>
|
class Context : private EpochRefPolicy<EpochManager::TheEpochRefManager> {
|
||||||
{
|
public:
|
||||||
public:
|
Context(HashTableManager& hashTableManager,
|
||||||
Context(
|
|
||||||
HashTableManager& hashTableManager,
|
|
||||||
EpochManager::TheEpochRefManager& epochRefManager)
|
EpochManager::TheEpochRefManager& epochRefManager)
|
||||||
: EpochRefPolicy<EpochManager::TheEpochRefManager>(epochRefManager)
|
: EpochRefPolicy<EpochManager::TheEpochRefManager>(epochRefManager),
|
||||||
, m_hashTableManager{ hashTableManager }
|
m_hashTableManager{hashTableManager} {}
|
||||||
{}
|
|
||||||
|
|
||||||
Context(Context&& context)
|
Context(Context&& context)
|
||||||
: EpochRefPolicy<EpochManager::TheEpochRefManager>(std::move(context))
|
: EpochRefPolicy<EpochManager::TheEpochRefManager>(std::move(context)),
|
||||||
, m_hashTableManager{ context.m_hashTableManager }
|
m_hashTableManager{context.m_hashTableManager} {}
|
||||||
{}
|
|
||||||
|
|
||||||
const IReadOnlyHashTable& operator[](const char* name) const
|
const IReadOnlyHashTable& operator[](const char* name) const {
|
||||||
{
|
|
||||||
return m_hashTableManager.GetHashTable(name);
|
return m_hashTableManager.GetHashTable(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
IWritableHashTable& operator[](const char* name)
|
IWritableHashTable& operator[](const char* name) {
|
||||||
{
|
|
||||||
return m_hashTableManager.GetHashTable(name);
|
return m_hashTableManager.GetHashTable(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const IReadOnlyHashTable& operator[](std::size_t index) const
|
const IReadOnlyHashTable& operator[](std::size_t index) const {
|
||||||
{
|
|
||||||
return m_hashTableManager.GetHashTable(index);
|
return m_hashTableManager.GetHashTable(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
IWritableHashTable& operator[](std::size_t index)
|
IWritableHashTable& operator[](std::size_t index) {
|
||||||
{
|
|
||||||
return m_hashTableManager.GetHashTable(index);
|
return m_hashTableManager.GetHashTable(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Context(const Context&) = delete;
|
Context(const Context&) = delete;
|
||||||
Context& operator=(const Context&) = delete;
|
Context& operator=(const Context&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HashTableManager& m_hashTableManager;
|
HashTableManager& m_hashTableManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,87 +10,80 @@
|
||||||
#include "Utils/Lock.h"
|
#include "Utils/Lock.h"
|
||||||
#include "Utils/RunningThread.h"
|
#include "Utils/RunningThread.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace LocalMemory {
|
||||||
namespace LocalMemory
|
|
||||||
{
|
|
||||||
|
|
||||||
// EpochManager aggregates epoch-related functionalities such as adding/removing
|
// EpochManager aggregates epoch-related functionalities such as adding/removing
|
||||||
// client epoch queues, registering/performing actions, and updating the epoch counters.
|
// client epoch queues, registering/performing actions, and updating the epoch
|
||||||
class EpochManager : public IEpochActionManager
|
// counters.
|
||||||
{
|
class EpochManager : public IEpochActionManager {
|
||||||
public:
|
public:
|
||||||
using TheEpochQueue = EpochQueue<
|
using TheEpochQueue =
|
||||||
boost::shared_lock_guard<Utils::ReaderWriterLockSlim>,
|
EpochQueue<boost::shared_lock_guard<Utils::ReaderWriterLockSlim>,
|
||||||
std::lock_guard<Utils::ReaderWriterLockSlim>>;
|
std::lock_guard<Utils::ReaderWriterLockSlim>>;
|
||||||
|
|
||||||
using TheEpochRefManager = EpochRefManager<TheEpochQueue>;
|
using TheEpochRefManager = EpochRefManager<TheEpochQueue>;
|
||||||
|
|
||||||
EpochManager(
|
EpochManager(const EpochManagerConfig& config, ServerPerfData& perfData)
|
||||||
const EpochManagerConfig& config,
|
: m_perfData{perfData},
|
||||||
ServerPerfData& perfData)
|
m_config{config},
|
||||||
: m_perfData{ perfData }
|
m_currentEpochCounter{0U},
|
||||||
, m_config{ config }
|
m_epochQueue{m_currentEpochCounter, m_config.m_epochQueueSize},
|
||||||
, m_currentEpochCounter{ 0U }
|
m_epochRefManager{m_epochQueue},
|
||||||
, m_epochQueue{
|
m_epochCounterManager{m_epochQueue},
|
||||||
m_currentEpochCounter,
|
m_epochActionManager{config.m_numActionQueues},
|
||||||
m_config.m_epochQueueSize }
|
m_processingThread{m_config.m_epochProcessingInterval, [this] {
|
||||||
, m_epochRefManager{ m_epochQueue }
|
|
||||||
, m_epochCounterManager{ m_epochQueue }
|
|
||||||
, m_epochActionManager{ config.m_numActionQueues }
|
|
||||||
, m_processingThread{
|
|
||||||
m_config.m_epochProcessingInterval,
|
|
||||||
[this]
|
|
||||||
{
|
|
||||||
this->Remove();
|
this->Remove();
|
||||||
this->Add();
|
this->Add();
|
||||||
}}
|
}} {}
|
||||||
{}
|
|
||||||
|
|
||||||
TheEpochRefManager& GetEpochRefManager()
|
TheEpochRefManager& GetEpochRefManager() { return m_epochRefManager; }
|
||||||
{
|
|
||||||
return m_epochRefManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RegisterAction(Action&& action) override
|
void RegisterAction(Action&& action) override {
|
||||||
{
|
m_epochActionManager.RegisterAction(m_currentEpochCounter,
|
||||||
m_epochActionManager.RegisterAction(m_currentEpochCounter, std::move(action));
|
std::move(action));
|
||||||
m_perfData.Increment(ServerPerfCounter::PendingActionsCount);
|
m_perfData.Increment(ServerPerfCounter::PendingActionsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
EpochManager(const EpochManager&) = delete;
|
EpochManager(const EpochManager&) = delete;
|
||||||
EpochManager& operator=(const EpochManager&) = delete;
|
EpochManager& operator=(const EpochManager&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using TheEpochCounterManager = EpochCounterManager<TheEpochQueue>;
|
using TheEpochCounterManager = EpochCounterManager<TheEpochQueue>;
|
||||||
|
|
||||||
using ProcessingThread = Utils::RunningThread<std::function<void()>>;
|
using ProcessingThread = Utils::RunningThread<std::function<void()>>;
|
||||||
|
|
||||||
// Enqueues a new epoch whose counter value is last counter + 1.
|
// Enqueues a new epoch whose counter value is last counter + 1.
|
||||||
// This is called from the server side.
|
// This is called from the server side.
|
||||||
void Add()
|
void Add() {
|
||||||
{
|
|
||||||
// Incrementing the global epoch counter before incrementing per-connection
|
// Incrementing the global epoch counter before incrementing per-connection
|
||||||
// epoch counter is safe (not so the other way around). If the server process is
|
// epoch counter is safe (not so the other way around). If the server
|
||||||
// registering an action at the m_currentEpochCounter in RegisterAction(),
|
// process is registering an action at the m_currentEpochCounter in
|
||||||
// it is happening in the "future," and this means that if the client is referencing
|
// RegisterAction(), it is happening in the "future," and this means that if
|
||||||
// the memory to be deleted in the "future," it will be safe.
|
// the client is referencing the memory to be deleted in the "future," it
|
||||||
|
// will be safe.
|
||||||
++m_currentEpochCounter;
|
++m_currentEpochCounter;
|
||||||
|
|
||||||
m_epochCounterManager.AddNewEpoch();
|
m_epochCounterManager.AddNewEpoch();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dequeues any epochs whose ref counter is 0, meaning there is no reference at that time.
|
// Dequeues any epochs whose ref counter is 0, meaning there is no reference
|
||||||
void Remove()
|
// at that time.
|
||||||
{
|
void Remove() {
|
||||||
const auto oldestEpochCounter = m_epochCounterManager.RemoveUnreferenceEpochCounters();
|
const auto oldestEpochCounter =
|
||||||
|
m_epochCounterManager.RemoveUnreferenceEpochCounters();
|
||||||
|
|
||||||
const auto numActionsPerformed = m_epochActionManager.PerformActions(oldestEpochCounter);
|
const auto numActionsPerformed =
|
||||||
|
m_epochActionManager.PerformActions(oldestEpochCounter);
|
||||||
|
|
||||||
m_perfData.Subtract(ServerPerfCounter::PendingActionsCount, numActionsPerformed);
|
m_perfData.Subtract(ServerPerfCounter::PendingActionsCount,
|
||||||
m_perfData.Set(ServerPerfCounter::LastPerformedActionsCount, numActionsPerformed);
|
numActionsPerformed);
|
||||||
m_perfData.Set(ServerPerfCounter::OldestEpochCounterInQueue, oldestEpochCounter);
|
m_perfData.Set(ServerPerfCounter::LastPerformedActionsCount,
|
||||||
m_perfData.Set(ServerPerfCounter::LatestEpochCounterInQueue, m_currentEpochCounter);
|
numActionsPerformed);
|
||||||
|
m_perfData.Set(ServerPerfCounter::OldestEpochCounterInQueue,
|
||||||
|
oldestEpochCounter);
|
||||||
|
m_perfData.Set(ServerPerfCounter::LatestEpochCounterInQueue,
|
||||||
|
m_currentEpochCounter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reference to the performance data.
|
// Reference to the performance data.
|
||||||
|
@ -112,7 +105,8 @@ private:
|
||||||
// Handles adding/decrementing ref counts.
|
// Handles adding/decrementing ref counts.
|
||||||
TheEpochRefManager m_epochRefManager;
|
TheEpochRefManager m_epochRefManager;
|
||||||
|
|
||||||
// Handles adding new epoch and finding the epoch counts that have zero ref counts.
|
// Handles adding new epoch and finding the epoch counts that have zero ref
|
||||||
|
// counts.
|
||||||
TheEpochCounterManager m_epochCounterManager;
|
TheEpochCounterManager m_epochCounterManager;
|
||||||
|
|
||||||
// Handles registering/performing actions.
|
// Handles registering/performing actions.
|
||||||
|
|
|
@ -3,75 +3,71 @@
|
||||||
#include <boost/any.hpp>
|
#include <boost/any.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "LocalMemory/Memory.h"
|
|
||||||
#include "Epoch/IEpochActionManager.h"
|
#include "Epoch/IEpochActionManager.h"
|
||||||
|
#include "HashTable/Cache/HashTable.h"
|
||||||
#include "HashTable/Config.h"
|
#include "HashTable/Config.h"
|
||||||
#include "HashTable/ReadWrite/HashTable.h"
|
#include "HashTable/ReadWrite/HashTable.h"
|
||||||
#include "HashTable/ReadWrite/Serializer.h"
|
#include "HashTable/ReadWrite/Serializer.h"
|
||||||
#include "HashTable/Cache/HashTable.h"
|
#include "LocalMemory/Memory.h"
|
||||||
#include "Utils/Containers.h"
|
#include "Utils/Containers.h"
|
||||||
#include "Utils/Exception.h"
|
#include "Utils/Exception.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace LocalMemory {
|
||||||
namespace LocalMemory
|
|
||||||
{
|
|
||||||
|
|
||||||
class HashTableManager
|
class HashTableManager {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
template <typename Allocator>
|
template <typename Allocator>
|
||||||
std::size_t Add(
|
std::size_t Add(const HashTableConfig& config,
|
||||||
const HashTableConfig& config,
|
|
||||||
IEpochActionManager& epochActionManager,
|
IEpochActionManager& epochActionManager,
|
||||||
Allocator allocator)
|
Allocator allocator) {
|
||||||
{
|
if (m_hashTableNameToIndex.find(config.m_name) !=
|
||||||
if (m_hashTableNameToIndex.find(config.m_name) != m_hashTableNameToIndex.end())
|
m_hashTableNameToIndex.end()) {
|
||||||
{
|
|
||||||
throw RuntimeException("Same hash table name already exists.");
|
throw RuntimeException("Same hash table name already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& cacheConfig = config.m_cache;
|
const auto& cacheConfig = config.m_cache;
|
||||||
const auto& serializerConfig = config.m_serializer;
|
const auto& serializerConfig = config.m_serializer;
|
||||||
|
|
||||||
if (cacheConfig && serializerConfig)
|
if (cacheConfig && serializerConfig) {
|
||||||
{
|
|
||||||
throw RuntimeException(
|
throw RuntimeException(
|
||||||
"Constructing cache hash table via serializer is not supported.");
|
"Constructing cache hash table via serializer is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
using namespace HashTable;
|
using namespace HashTable;
|
||||||
|
|
||||||
using InternalHashTable = typename ReadWrite::WritableHashTable<Allocator>::HashTable;
|
using InternalHashTable =
|
||||||
|
typename ReadWrite::WritableHashTable<Allocator>::HashTable;
|
||||||
using Memory = typename LocalMemory::Memory<Allocator>;
|
using Memory = typename LocalMemory::Memory<Allocator>;
|
||||||
|
|
||||||
Memory memory{ allocator };
|
Memory memory{allocator};
|
||||||
|
|
||||||
std::shared_ptr<InternalHashTable> internalHashTable = (serializerConfig && serializerConfig->m_stream != nullptr)
|
std::shared_ptr<InternalHashTable> internalHashTable =
|
||||||
? ReadWrite::Deserializer<Memory, InternalHashTable, ReadWrite::WritableHashTable>(
|
(serializerConfig && serializerConfig->m_stream != nullptr)
|
||||||
serializerConfig->m_properties.get_value_or(HashTableConfig::Serializer::Properties())).
|
? ReadWrite::Deserializer<Memory, InternalHashTable,
|
||||||
Deserialize(
|
ReadWrite::WritableHashTable>(
|
||||||
memory,
|
serializerConfig->m_properties.get_value_or(
|
||||||
*(serializerConfig->m_stream))
|
HashTableConfig::Serializer::Properties()))
|
||||||
|
.Deserialize(memory, *(serializerConfig->m_stream))
|
||||||
: memory.template MakeUnique<InternalHashTable>(
|
: memory.template MakeUnique<InternalHashTable>(
|
||||||
typename InternalHashTable::Setting{
|
typename InternalHashTable::Setting{
|
||||||
config.m_setting.m_numBuckets,
|
config.m_setting.m_numBuckets,
|
||||||
(std::max)(config.m_setting.m_numBucketsPerMutex.get_value_or(1U), 1U),
|
(std::max)(
|
||||||
|
config.m_setting.m_numBucketsPerMutex.get_value_or(
|
||||||
|
1U),
|
||||||
|
1U),
|
||||||
config.m_setting.m_fixedKeySize.get_value_or(0U),
|
config.m_setting.m_fixedKeySize.get_value_or(0U),
|
||||||
config.m_setting.m_fixedValueSize.get_value_or(0U) },
|
config.m_setting.m_fixedValueSize.get_value_or(0U)},
|
||||||
memory.GetAllocator());
|
memory.GetAllocator());
|
||||||
|
|
||||||
auto hashTable =
|
auto hashTable =
|
||||||
cacheConfig
|
cacheConfig ? std::make_unique<Cache::WritableHashTable<Allocator>>(
|
||||||
? std::make_unique<Cache::WritableHashTable<Allocator>>(
|
*internalHashTable, epochActionManager,
|
||||||
*internalHashTable,
|
|
||||||
epochActionManager,
|
|
||||||
cacheConfig->m_maxCacheSizeInBytes,
|
cacheConfig->m_maxCacheSizeInBytes,
|
||||||
cacheConfig->m_recordTimeToLive,
|
cacheConfig->m_recordTimeToLive,
|
||||||
cacheConfig->m_forceTimeBasedEviction)
|
cacheConfig->m_forceTimeBasedEviction)
|
||||||
: std::make_unique<ReadWrite::WritableHashTable<Allocator>>(
|
: std::make_unique<ReadWrite::WritableHashTable<Allocator>>(
|
||||||
*internalHashTable,
|
*internalHashTable, epochActionManager);
|
||||||
epochActionManager);
|
|
||||||
|
|
||||||
m_internalHashTables.emplace_back(std::move(internalHashTable));
|
m_internalHashTables.emplace_back(std::move(internalHashTable));
|
||||||
m_hashTables.emplace_back(std::move(hashTable));
|
m_hashTables.emplace_back(std::move(hashTable));
|
||||||
|
@ -83,19 +79,17 @@ public:
|
||||||
return newIndex;
|
return newIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
IWritableHashTable& GetHashTable(const char* name)
|
IWritableHashTable& GetHashTable(const char* name) {
|
||||||
{
|
|
||||||
assert(m_hashTableNameToIndex.find(name) != m_hashTableNameToIndex.cend());
|
assert(m_hashTableNameToIndex.find(name) != m_hashTableNameToIndex.cend());
|
||||||
return GetHashTable(m_hashTableNameToIndex.find(name)->second);
|
return GetHashTable(m_hashTableNameToIndex.find(name)->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
IWritableHashTable& GetHashTable(std::size_t index)
|
IWritableHashTable& GetHashTable(std::size_t index) {
|
||||||
{
|
|
||||||
assert(index < m_hashTables.size());
|
assert(index < m_hashTables.size());
|
||||||
return *m_hashTables[index];
|
return *m_hashTables[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Utils::StdStringKeyMap<std::size_t> m_hashTableNameToIndex;
|
Utils::StdStringKeyMap<std::size_t> m_hashTableNameToIndex;
|
||||||
|
|
||||||
std::vector<boost::any> m_internalHashTables;
|
std::vector<boost::any> m_internalHashTables;
|
||||||
|
|
|
@ -5,33 +5,26 @@
|
||||||
#include "HashTable/Config.h"
|
#include "HashTable/Config.h"
|
||||||
#include "Log/PerfCounter.h"
|
#include "Log/PerfCounter.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace LocalMemory {
|
||||||
namespace LocalMemory
|
|
||||||
{
|
|
||||||
|
|
||||||
class HashTableService
|
class HashTableService {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
explicit HashTableService(
|
explicit HashTableService(
|
||||||
const EpochManagerConfig& epochManagerConfig = EpochManagerConfig())
|
const EpochManagerConfig& epochManagerConfig = EpochManagerConfig())
|
||||||
: m_epochManager{ epochManagerConfig, m_serverPerfData }
|
: m_epochManager{epochManagerConfig, m_serverPerfData} {}
|
||||||
{}
|
|
||||||
|
|
||||||
template <typename Allocator = std::allocator<void>>
|
template <typename Allocator = std::allocator<void>>
|
||||||
std::size_t AddHashTable(
|
std::size_t AddHashTable(const HashTableConfig& config,
|
||||||
const HashTableConfig& config,
|
Allocator allocator = Allocator()) {
|
||||||
Allocator allocator = Allocator())
|
|
||||||
{
|
|
||||||
return m_hashTableManager.Add(config, m_epochManager, allocator);
|
return m_hashTableManager.Add(config, m_epochManager, allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
Context GetContext()
|
Context GetContext() {
|
||||||
{
|
|
||||||
return Context(m_hashTableManager, m_epochManager.GetEpochRefManager());
|
return Context(m_hashTableManager, m_epochManager.GetEpochRefManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ServerPerfData m_serverPerfData;
|
ServerPerfData m_serverPerfData;
|
||||||
|
|
||||||
HashTableManager m_hashTableManager;
|
HashTableManager m_hashTableManager;
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace LocalMemory {
|
||||||
namespace LocalMemory
|
|
||||||
{
|
|
||||||
|
|
||||||
// Simple local memory model that stores the given allocator object.
|
// Simple local memory model that stores the given allocator object.
|
||||||
template <typename Alloc>
|
template <typename Alloc>
|
||||||
class Memory
|
class Memory {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
using Allocator = Alloc;
|
using Allocator = Alloc;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -18,31 +15,24 @@ public:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using Deleter = typename std::default_delete<T>;
|
using Deleter = typename std::default_delete<T>;
|
||||||
|
|
||||||
explicit Memory(Allocator allocator = Allocator())
|
explicit Memory(Allocator allocator = Allocator()) : m_allocator{allocator} {}
|
||||||
: m_allocator{ allocator }
|
|
||||||
{}
|
|
||||||
|
|
||||||
template <typename T, typename... Args>
|
template <typename T, typename... Args>
|
||||||
auto MakeUnique(Args&&... args)
|
auto MakeUnique(Args&&... args) {
|
||||||
{
|
|
||||||
return std::make_unique<T>(std::forward<Args>(args)...);
|
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
Allocator GetAllocator()
|
Allocator GetAllocator() { return Allocator(m_allocator); }
|
||||||
{
|
|
||||||
return Allocator(m_allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
auto GetDeleter()
|
auto GetDeleter() {
|
||||||
{
|
|
||||||
return Deleter<T>();
|
return Deleter<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Memory(const Memory&) = delete;
|
Memory(const Memory&) = delete;
|
||||||
Memory& operator=(const Memory&) = delete;
|
Memory& operator=(const Memory&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Allocator m_allocator;
|
Allocator m_allocator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,10 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "PerfCounter.h"
|
#include "PerfCounter.h"
|
||||||
|
|
||||||
|
namespace L4 {
|
||||||
namespace L4
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
// IPerfLogger interface.
|
// IPerfLogger interface.
|
||||||
struct IPerfLogger
|
struct IPerfLogger {
|
||||||
{
|
|
||||||
struct IData;
|
struct IData;
|
||||||
|
|
||||||
virtual ~IPerfLogger() = default;
|
virtual ~IPerfLogger() = default;
|
||||||
|
@ -18,14 +14,12 @@ struct IPerfLogger
|
||||||
virtual void Log(const IData& data) = 0;
|
virtual void Log(const IData& data) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// IPerfLogger::IData interface that provides access to ServerPerfData and the aggregated HashTablePerfData.
|
// IPerfLogger::IData interface that provides access to ServerPerfData and the
|
||||||
// Note that the user of IPerfLogger only needs to implement IPerfLogger since IPerfLogger::IData is
|
// aggregated HashTablePerfData. Note that the user of IPerfLogger only needs to
|
||||||
// implemented internally.
|
// implement IPerfLogger since IPerfLogger::IData is implemented internally.
|
||||||
struct IPerfLogger::IData
|
struct IPerfLogger::IData {
|
||||||
{
|
using HashTablesPerfData =
|
||||||
using HashTablesPerfData = std::map<
|
std::map<std::string, std::reference_wrapper<const HashTablePerfData>>;
|
||||||
std::string,
|
|
||||||
std::reference_wrapper<const HashTablePerfData>>;
|
|
||||||
|
|
||||||
virtual ~IData() = default;
|
virtual ~IData() = default;
|
||||||
|
|
||||||
|
@ -34,5 +28,4 @@ struct IPerfLogger::IData
|
||||||
virtual const HashTablesPerfData& GetHashTablesPerfData() const = 0;
|
virtual const HashTablesPerfData& GetHashTablesPerfData() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace L4
|
} // namespace L4
|
|
@ -1,16 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <limits>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
enum class ServerPerfCounter : std::uint16_t
|
enum class ServerPerfCounter : std::uint16_t {
|
||||||
{
|
|
||||||
// Connection Manager
|
// Connection Manager
|
||||||
ClientConnectionsCount = 0U,
|
ClientConnectionsCount = 0U,
|
||||||
|
|
||||||
|
@ -23,22 +21,17 @@ enum class ServerPerfCounter : std::uint16_t
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::array<
|
const std::array<const char*,
|
||||||
const char*,
|
static_cast<std::uint16_t>(ServerPerfCounter::Count)>
|
||||||
static_cast<std::uint16_t>(ServerPerfCounter::Count)> c_serverPerfCounterNames =
|
c_serverPerfCounterNames = {
|
||||||
{
|
|
||||||
// Connection Manager
|
// Connection Manager
|
||||||
"ClientConnectionsCount",
|
"ClientConnectionsCount",
|
||||||
|
|
||||||
// EpochManager
|
// EpochManager
|
||||||
"OldestEpochCounterInQueue",
|
"OldestEpochCounterInQueue", "LatestEpochCounterInQueue",
|
||||||
"LatestEpochCounterInQueue",
|
"PendingActionsCount", "LastPerformedActionsCount"};
|
||||||
"PendingActionsCount",
|
|
||||||
"LastPerformedActionsCount"
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class HashTablePerfCounter : std::uint16_t
|
enum class HashTablePerfCounter : std::uint16_t {
|
||||||
{
|
|
||||||
RecordsCount = 0U,
|
RecordsCount = 0U,
|
||||||
BucketsCount,
|
BucketsCount,
|
||||||
TotalKeySize,
|
TotalKeySize,
|
||||||
|
@ -65,11 +58,9 @@ enum class HashTablePerfCounter : std::uint16_t
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::array<
|
const std::array<const char*,
|
||||||
const char*,
|
static_cast<std::uint16_t>(HashTablePerfCounter::Count)>
|
||||||
static_cast<std::uint16_t>(HashTablePerfCounter::Count)> c_hashTablePerfCounterNames =
|
c_hashTablePerfCounterNames = {"RecordsCount",
|
||||||
{
|
|
||||||
"RecordsCount",
|
|
||||||
"BucketsCount",
|
"BucketsCount",
|
||||||
"TotalKeySize",
|
"TotalKeySize",
|
||||||
"TotalValueSize",
|
"TotalValueSize",
|
||||||
|
@ -84,114 +75,92 @@ const std::array<
|
||||||
"RecordsCountSavedFromSerializer",
|
"RecordsCountSavedFromSerializer",
|
||||||
"CacheHitCount",
|
"CacheHitCount",
|
||||||
"CacheMissCount",
|
"CacheMissCount",
|
||||||
"EvictedRecordsCount"
|
"EvictedRecordsCount"};
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template <typename TCounterEnum>
|
template <typename TCounterEnum>
|
||||||
class PerfCounters
|
class PerfCounters {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
typedef std::int64_t TValue;
|
typedef std::int64_t TValue;
|
||||||
typedef std::atomic<TValue> TCounter;
|
typedef std::atomic<TValue> TCounter;
|
||||||
|
|
||||||
PerfCounters()
|
PerfCounters() {
|
||||||
{
|
std::for_each(std::begin(m_counters), std::end(m_counters),
|
||||||
std::for_each(
|
[](TCounter& counter) { counter = 0; });
|
||||||
std::begin(m_counters),
|
|
||||||
std::end(m_counters),
|
|
||||||
[] (TCounter& counter)
|
|
||||||
{
|
|
||||||
counter = 0;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that since the ordering doesn't matter when the counter is updated, memory_order_relaxed
|
// Note that since the ordering doesn't matter when the counter is updated,
|
||||||
// is used for all perf counter updates.
|
// memory_order_relaxed is used for all perf counter updates. More from
|
||||||
// More from http://en.cppreference.com/w/cpp/atomic/memory_order:
|
// http://en.cppreference.com/w/cpp/atomic/memory_order: Typical use for
|
||||||
// Typical use for relaxed memory ordering is updating counters, such as the reference counters
|
// relaxed memory ordering is updating counters, such as the reference
|
||||||
// of std::shared_ptr, since this only requires atomicity, but not ordering or synchronization.
|
// counters of std::shared_ptr, since this only requires atomicity, but not
|
||||||
TValue Get(TCounterEnum counterEnum) const
|
// ordering or synchronization.
|
||||||
{
|
TValue Get(TCounterEnum counterEnum) const {
|
||||||
return m_counters[static_cast<std::uint16_t>(counterEnum)].load(std::memory_order_relaxed);
|
return m_counters[static_cast<std::uint16_t>(counterEnum)].load(
|
||||||
|
std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Set(TCounterEnum counterEnum, TValue value)
|
void Set(TCounterEnum counterEnum, TValue value) {
|
||||||
{
|
m_counters[static_cast<std::uint16_t>(counterEnum)].store(
|
||||||
m_counters[static_cast<std::uint16_t>(counterEnum)].store(value, std::memory_order_relaxed);
|
value, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Increment(TCounterEnum counterEnum)
|
void Increment(TCounterEnum counterEnum) {
|
||||||
{
|
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_add(
|
||||||
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_add(1, std::memory_order_relaxed);
|
1, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Decrement(TCounterEnum counterEnum)
|
void Decrement(TCounterEnum counterEnum) {
|
||||||
{
|
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_sub(
|
||||||
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_sub(1, std::memory_order_relaxed);
|
1, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Add(TCounterEnum counterEnum, TValue value)
|
void Add(TCounterEnum counterEnum, TValue value) {
|
||||||
{
|
if (value != 0) {
|
||||||
if (value != 0)
|
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_add(
|
||||||
{
|
value, std::memory_order_relaxed);
|
||||||
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_add(value, std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Subtract(TCounterEnum counterEnum, TValue value)
|
void Subtract(TCounterEnum counterEnum, TValue value) {
|
||||||
{
|
if (value != 0) {
|
||||||
if (value != 0)
|
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_sub(
|
||||||
{
|
value, std::memory_order_relaxed);
|
||||||
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_sub(value, std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Max(TCounterEnum counterEnum, TValue value)
|
void Max(TCounterEnum counterEnum, TValue value) {
|
||||||
{
|
|
||||||
auto& counter = m_counters[static_cast<std::uint16_t>(counterEnum)];
|
auto& counter = m_counters[static_cast<std::uint16_t>(counterEnum)];
|
||||||
|
|
||||||
TValue startValue = counter.load(std::memory_order_acquire);
|
TValue startValue = counter.load(std::memory_order_acquire);
|
||||||
|
|
||||||
do
|
do {
|
||||||
{
|
|
||||||
// "load()" from counter is needed only once since the value of Max is
|
// "load()" from counter is needed only once since the value of Max is
|
||||||
// monotonically increasing. If startValue is changed by other threads,
|
// monotonically increasing. If startValue is changed by other threads,
|
||||||
// compare_exchange_strong will return false and startValue will be
|
// compare_exchange_strong will return false and startValue will be
|
||||||
// written to the latest value, thus returning to this code path.
|
// written to the latest value, thus returning to this code path.
|
||||||
if (startValue > value)
|
if (startValue > value) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
} while (!counter.compare_exchange_strong(startValue, value,
|
||||||
while (!counter.compare_exchange_strong(
|
|
||||||
startValue,
|
|
||||||
value,
|
|
||||||
std::memory_order_release,
|
std::memory_order_release,
|
||||||
std::memory_order_acquire));
|
std::memory_order_acquire));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Min(TCounterEnum counterEnum, TValue value)
|
void Min(TCounterEnum counterEnum, TValue value) {
|
||||||
{
|
|
||||||
auto& counter = m_counters[static_cast<std::uint16_t>(counterEnum)];
|
auto& counter = m_counters[static_cast<std::uint16_t>(counterEnum)];
|
||||||
|
|
||||||
TValue startValue = counter.load(std::memory_order_acquire);
|
TValue startValue = counter.load(std::memory_order_acquire);
|
||||||
do
|
do {
|
||||||
{
|
|
||||||
// Check the comment in Max() and Min() is monotonically decreasing.
|
// Check the comment in Max() and Min() is monotonically decreasing.
|
||||||
if (startValue < value)
|
if (startValue < value) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
} while (!counter.compare_exchange_strong(startValue, value,
|
||||||
while (!counter.compare_exchange_strong(
|
|
||||||
startValue,
|
|
||||||
value,
|
|
||||||
std::memory_order_release,
|
std::memory_order_release,
|
||||||
std::memory_order_acquire));
|
std::memory_order_acquire));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
__declspec(align(8)) TCounter m_counters[TCounterEnum::Count];
|
__declspec(align(8)) TCounter m_counters[TCounterEnum::Count];
|
||||||
#else
|
#else
|
||||||
|
@ -204,12 +173,11 @@ private:
|
||||||
|
|
||||||
typedef PerfCounters<ServerPerfCounter> ServerPerfData;
|
typedef PerfCounters<ServerPerfCounter> ServerPerfData;
|
||||||
|
|
||||||
struct HashTablePerfData : public PerfCounters<HashTablePerfCounter>
|
struct HashTablePerfData : public PerfCounters<HashTablePerfCounter> {
|
||||||
{
|
HashTablePerfData() {
|
||||||
HashTablePerfData()
|
|
||||||
{
|
|
||||||
// Initialize any min counters to the max value.
|
// Initialize any min counters to the max value.
|
||||||
const auto maxValue = (std::numeric_limits<HashTablePerfData::TValue>::max)();
|
const auto maxValue =
|
||||||
|
(std::numeric_limits<HashTablePerfData::TValue>::max)();
|
||||||
|
|
||||||
Set(HashTablePerfCounter::MinValueSize, maxValue);
|
Set(HashTablePerfCounter::MinValueSize, maxValue);
|
||||||
Set(HashTablePerfCounter::MinKeySize, maxValue);
|
Set(HashTablePerfCounter::MinKeySize, maxValue);
|
||||||
|
|
|
@ -2,20 +2,16 @@
|
||||||
|
|
||||||
#include "IPerfLogger.h"
|
#include "IPerfLogger.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
struct PerfLoggerManagerConfig;
|
struct PerfLoggerManagerConfig;
|
||||||
|
|
||||||
|
// PerfData class, which holds the ServerPerfData and HashTablePerfData for each
|
||||||
|
// hash table. Note that PerfData owns the ServerPerfData but has only the const
|
||||||
|
// references to HashTablePerfData, which is owned by the HashTable.
|
||||||
|
|
||||||
// PerfData class, which holds the ServerPerfData and HashTablePerfData for each hash table.
|
class PerfData : public IPerfLogger::IData {
|
||||||
// Note that PerfData owns the ServerPerfData but has only the const references to HashTablePerfData,
|
public:
|
||||||
// which is owned by the HashTable.
|
|
||||||
|
|
||||||
class PerfData : public IPerfLogger::IData
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PerfData() = default;
|
PerfData() = default;
|
||||||
|
|
||||||
ServerPerfData& GetServerPerfData();
|
ServerPerfData& GetServerPerfData();
|
||||||
|
@ -24,33 +20,30 @@ public:
|
||||||
|
|
||||||
const HashTablesPerfData& GetHashTablesPerfData() const override;
|
const HashTablesPerfData& GetHashTablesPerfData() const override;
|
||||||
|
|
||||||
void AddHashTablePerfData(const char* hashTableName, const HashTablePerfData& perfData);
|
void AddHashTablePerfData(const char* hashTableName,
|
||||||
|
const HashTablePerfData& perfData);
|
||||||
|
|
||||||
PerfData(const PerfData&) = delete;
|
PerfData(const PerfData&) = delete;
|
||||||
PerfData& operator=(const PerfData&) = delete;
|
PerfData& operator=(const PerfData&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ServerPerfData m_serverPerfData;
|
ServerPerfData m_serverPerfData;
|
||||||
HashTablesPerfData m_hashTablesPerfData;
|
HashTablesPerfData m_hashTablesPerfData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// PerfData inline implementations.
|
// PerfData inline implementations.
|
||||||
|
|
||||||
inline ServerPerfData& PerfData::GetServerPerfData()
|
inline ServerPerfData& PerfData::GetServerPerfData() {
|
||||||
{
|
|
||||||
return m_serverPerfData;
|
return m_serverPerfData;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const ServerPerfData& PerfData::GetServerPerfData() const
|
inline const ServerPerfData& PerfData::GetServerPerfData() const {
|
||||||
{
|
|
||||||
return m_serverPerfData;
|
return m_serverPerfData;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const PerfData::HashTablesPerfData& PerfData::GetHashTablesPerfData() const
|
inline const PerfData::HashTablesPerfData& PerfData::GetHashTablesPerfData()
|
||||||
{
|
const {
|
||||||
return m_hashTablesPerfData;
|
return m_hashTablesPerfData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace L4
|
} // namespace L4
|
|
@ -3,62 +3,48 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
// SerializerHelper provides help functions to write to IStreamWriter.
|
// SerializerHelper provides help functions to write to IStreamWriter.
|
||||||
class SerializerHelper
|
class SerializerHelper {
|
||||||
{
|
public:
|
||||||
public:
|
SerializerHelper(std::ostream& stream) : m_stream{stream} {}
|
||||||
SerializerHelper(std::ostream& stream)
|
|
||||||
: m_stream{ stream }
|
|
||||||
{}
|
|
||||||
|
|
||||||
SerializerHelper(const SerializerHelper&) = delete;
|
SerializerHelper(const SerializerHelper&) = delete;
|
||||||
SerializerHelper& operator=(const SerializerHelper&) = delete;
|
SerializerHelper& operator=(const SerializerHelper&) = delete;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void Serialize(const T& obj)
|
void Serialize(const T& obj) {
|
||||||
{
|
|
||||||
m_stream.write(reinterpret_cast<const char*>(&obj), sizeof(obj));
|
m_stream.write(reinterpret_cast<const char*>(&obj), sizeof(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Serialize(const void* data, std::uint32_t dataSize)
|
void Serialize(const void* data, std::uint32_t dataSize) {
|
||||||
{
|
|
||||||
m_stream.write(static_cast<const char*>(data), dataSize);
|
m_stream.write(static_cast<const char*>(data), dataSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::ostream& m_stream;
|
std::ostream& m_stream;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// DeserializerHelper provides help functions to read from IStreamReader.
|
// DeserializerHelper provides help functions to read from IStreamReader.
|
||||||
class DeserializerHelper
|
class DeserializerHelper {
|
||||||
{
|
public:
|
||||||
public:
|
DeserializerHelper(std::istream& stream) : m_stream{stream} {}
|
||||||
DeserializerHelper(std::istream& stream)
|
|
||||||
: m_stream{ stream }
|
|
||||||
{}
|
|
||||||
|
|
||||||
DeserializerHelper(const DeserializerHelper&) = delete;
|
DeserializerHelper(const DeserializerHelper&) = delete;
|
||||||
DeserializerHelper& operator=(const DeserializerHelper&) = delete;
|
DeserializerHelper& operator=(const DeserializerHelper&) = delete;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void Deserialize(T& obj)
|
void Deserialize(T& obj) {
|
||||||
{
|
|
||||||
m_stream.read(reinterpret_cast<char*>(&obj), sizeof(obj));
|
m_stream.read(reinterpret_cast<char*>(&obj), sizeof(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Deserialize(void* data, std::uint32_t dataSize)
|
void Deserialize(void* data, std::uint32_t dataSize) {
|
||||||
{
|
|
||||||
m_stream.read(static_cast<char*>(data), dataSize);
|
m_stream.read(static_cast<char*>(data), dataSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::istream& m_stream;
|
std::istream& m_stream;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace L4
|
} // namespace L4
|
||||||
|
|
||||||
|
|
|
@ -1,53 +1,50 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdint>
|
|
||||||
#include <boost/version.hpp>
|
|
||||||
#include <boost/interprocess/offset_ptr.hpp>
|
#include <boost/interprocess/offset_ptr.hpp>
|
||||||
|
#include <boost/version.hpp>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Utils {
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
// AtomicOffsetPtr provides a way to atomically update the offset pointer.
|
// AtomicOffsetPtr provides a way to atomically update the offset pointer.
|
||||||
// The current boost::interprocess::offset_ptr cannot be used with std::atomic<> because
|
// The current boost::interprocess::offset_ptr cannot be used with std::atomic<>
|
||||||
// the class is not trivially copyable. AtomicOffsetPtr borrows the same concept to calculate
|
// because the class is not trivially copyable. AtomicOffsetPtr borrows the same
|
||||||
// the pointer address based on the offset (boost::interprocess::ipcdetail::offset_ptr_to* functions
|
// concept to calculate the pointer address based on the offset
|
||||||
// are reused).
|
// (boost::interprocess::ipcdetail::offset_ptr_to* functions are reused). Note
|
||||||
// Note that ->, *, copy/assignment operators are not implemented intentionally so that
|
// that ->, *, copy/assignment operators are not implemented intentionally so
|
||||||
// the user (inside this library) is aware of what he is intended to do without accidentally
|
// that the user (inside this library) is aware of what he is intended to do
|
||||||
// incurring any performance hits.
|
// without accidentally incurring any performance hits.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class AtomicOffsetPtr
|
class AtomicOffsetPtr {
|
||||||
{
|
public:
|
||||||
public:
|
AtomicOffsetPtr() : m_offset(1) {}
|
||||||
AtomicOffsetPtr()
|
|
||||||
: m_offset(1)
|
|
||||||
{}
|
|
||||||
|
|
||||||
AtomicOffsetPtr(const AtomicOffsetPtr&) = delete;
|
AtomicOffsetPtr(const AtomicOffsetPtr&) = delete;
|
||||||
AtomicOffsetPtr& operator=(const AtomicOffsetPtr&) = delete;
|
AtomicOffsetPtr& operator=(const AtomicOffsetPtr&) = delete;
|
||||||
|
|
||||||
T* Load(std::memory_order memoryOrder = std::memory_order_seq_cst) const
|
T* Load(std::memory_order memoryOrder = std::memory_order_seq_cst) const {
|
||||||
{
|
|
||||||
return static_cast<T*>(
|
return static_cast<T*>(
|
||||||
boost::interprocess::ipcdetail::offset_ptr_to_raw_pointer(
|
boost::interprocess::ipcdetail::offset_ptr_to_raw_pointer(
|
||||||
this,
|
this, m_offset.load(memoryOrder)));
|
||||||
m_offset.load(memoryOrder)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Store(T* ptr, std::memory_order memoryOrder = std::memory_order_seq_cst)
|
void Store(T* ptr,
|
||||||
{
|
std::memory_order memoryOrder = std::memory_order_seq_cst) {
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
m_offset.store(boost::interprocess::ipcdetail::offset_ptr_to_offset(ptr, this), memoryOrder);
|
m_offset.store(
|
||||||
|
boost::interprocess::ipcdetail::offset_ptr_to_offset(ptr, this),
|
||||||
|
memoryOrder);
|
||||||
#else
|
#else
|
||||||
m_offset.store(boost::interprocess::ipcdetail::offset_ptr_to_offset<std::uintptr_t>(ptr, this), memoryOrder);
|
m_offset.store(
|
||||||
|
boost::interprocess::ipcdetail::offset_ptr_to_offset<std::uintptr_t>(
|
||||||
|
ptr, this),
|
||||||
|
memoryOrder);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
std::atomic_uint64_t m_offset;
|
std::atomic_uint64_t m_offset;
|
||||||
#else
|
#else
|
||||||
|
@ -55,6 +52,5 @@ private:
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
} // namespace L4
|
} // namespace L4
|
||||||
|
|
|
@ -2,23 +2,16 @@
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace L4 {
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
namespace L4
|
class EpochClock {
|
||||||
{
|
public:
|
||||||
namespace Utils
|
std::chrono::seconds GetCurrentEpochTime() const {
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
class EpochClock
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::chrono::seconds GetCurrentEpochTime() const
|
|
||||||
{
|
|
||||||
return std::chrono::duration_cast<std::chrono::seconds>(
|
return std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
std::chrono::high_resolution_clock::now().time_since_epoch());
|
std::chrono::high_resolution_clock::now().time_since_epoch());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
} // namespace L4
|
} // namespace L4
|
||||||
|
|
|
@ -1,47 +1,40 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/functional/hash.hpp>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <boost/functional/hash.hpp>
|
|
||||||
|
|
||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
#define _stricmp strcasecmp
|
#define _stricmp strcasecmp
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Utils {
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
|
|
||||||
|
// CaseInsensitiveStdStringComparer is a STL-compatible case-insensitive ANSI
|
||||||
// CaseInsensitiveStdStringComparer is a STL-compatible case-insensitive ANSI std::string comparer.
|
// std::string comparer.
|
||||||
struct CaseInsensitiveStdStringComparer
|
struct CaseInsensitiveStdStringComparer {
|
||||||
{
|
bool operator()(const std::string& str1, const std::string& str2) const {
|
||||||
bool operator()(const std::string& str1, const std::string& str2) const
|
|
||||||
{
|
|
||||||
return _stricmp(str1.c_str(), str2.c_str()) == 0;
|
return _stricmp(str1.c_str(), str2.c_str()) == 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// CaseInsensitiveStringComparer is a STL-compatible case-insensitive ANSI string comparer.
|
// CaseInsensitiveStringComparer is a STL-compatible case-insensitive ANSI
|
||||||
struct CaseInsensitiveStringComparer
|
// string comparer.
|
||||||
{
|
struct CaseInsensitiveStringComparer {
|
||||||
bool operator()(const char* const str1, const char* const str2) const
|
bool operator()(const char* const str1, const char* const str2) const {
|
||||||
{
|
|
||||||
return _stricmp(str1, str2) == 0;
|
return _stricmp(str1, str2) == 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// CaseInsensitiveStringHasher is a STL-compatible case-insensitive ANSI std::string hasher.
|
// CaseInsensitiveStringHasher is a STL-compatible case-insensitive ANSI
|
||||||
struct CaseInsensitiveStdStringHasher
|
// std::string hasher.
|
||||||
{
|
struct CaseInsensitiveStdStringHasher {
|
||||||
std::size_t operator()(const std::string& str) const
|
std::size_t operator()(const std::string& str) const {
|
||||||
{
|
|
||||||
std::size_t seed = 0;
|
std::size_t seed = 0;
|
||||||
|
|
||||||
for (auto c : str)
|
for (auto c : str) {
|
||||||
{
|
|
||||||
boost::hash_combine(seed, std::toupper(c));
|
boost::hash_combine(seed, std::toupper(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,17 +42,15 @@ struct CaseInsensitiveStdStringHasher
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// CaseInsensitiveStringHasher is a STL-compatible case-insensitive ANSI string hasher.
|
// CaseInsensitiveStringHasher is a STL-compatible case-insensitive ANSI string
|
||||||
struct CaseInsensitiveStringHasher
|
// hasher.
|
||||||
{
|
struct CaseInsensitiveStringHasher {
|
||||||
std::size_t operator()(const char* str) const
|
std::size_t operator()(const char* str) const {
|
||||||
{
|
|
||||||
assert(str != nullptr);
|
assert(str != nullptr);
|
||||||
|
|
||||||
std::size_t seed = 0;
|
std::size_t seed = 0;
|
||||||
|
|
||||||
while (*str)
|
while (*str) {
|
||||||
{
|
|
||||||
boost::hash_combine(seed, std::toupper(*str++));
|
boost::hash_combine(seed, std::toupper(*str++));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +58,5 @@ struct CaseInsensitiveStringHasher
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
} // namespace L4
|
} // namespace L4
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/functional/hash.hpp>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <boost/functional/hash.hpp>
|
|
||||||
#include "Utils/ComparerHasher.h"
|
#include "Utils/ComparerHasher.h"
|
||||||
|
|
||||||
|
namespace L4 {
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
namespace L4
|
// StdStringKeyMap is an unordered_map where the key is std::string. It is
|
||||||
{
|
// slower than StringKeyMap above, but it owns the memory of the string, so it's
|
||||||
namespace Utils
|
// easier to use.
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
// StdStringKeyMap is an unordered_map where the key is std::string. It is slower than
|
|
||||||
// StringKeyMap above, but it owns the memory of the string, so it's easier to use.
|
|
||||||
template <typename TValue>
|
template <typename TValue>
|
||||||
using StdStringKeyMap = std::unordered_map<
|
using StdStringKeyMap =
|
||||||
std::string,
|
std::unordered_map<std::string,
|
||||||
TValue,
|
TValue,
|
||||||
Utils::CaseInsensitiveStdStringHasher,
|
Utils::CaseInsensitiveStdStringHasher,
|
||||||
Utils::CaseInsensitiveStdStringComparer>;
|
Utils::CaseInsensitiveStdStringComparer>;
|
||||||
|
@ -26,20 +23,15 @@ using StdStringKeyMap = std::unordered_map<
|
||||||
// The memory of the key is not owned by StringKeyMap,
|
// The memory of the key is not owned by StringKeyMap,
|
||||||
// but it is faster (than StdStringKeyMap below) for look up.
|
// but it is faster (than StdStringKeyMap below) for look up.
|
||||||
template <typename TValue>
|
template <typename TValue>
|
||||||
using StringKeyMap = std::unordered_map<
|
using StringKeyMap = std::unordered_map<const char*,
|
||||||
const char*,
|
|
||||||
TValue,
|
TValue,
|
||||||
Utils::CaseInsensitiveStringHasher,
|
Utils::CaseInsensitiveStringHasher,
|
||||||
Utils::CaseInsensitiveStringComparer>;
|
Utils::CaseInsensitiveStringComparer>;
|
||||||
|
|
||||||
// IntegerKeyMap using boost::hash and std::equal_to comparer and hasher.
|
// IntegerKeyMap using boost::hash and std::equal_to comparer and hasher.
|
||||||
template <typename TKey, typename TValue>
|
template <typename TKey, typename TValue>
|
||||||
using IntegerKeyMap = std::unordered_map<
|
using IntegerKeyMap =
|
||||||
TKey,
|
std::unordered_map<TKey, TValue, boost::hash<TKey>, std::equal_to<TKey>>;
|
||||||
TValue,
|
|
||||||
boost::hash<TKey>,
|
|
||||||
std::equal_to<TKey>>;
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
} // namespace L4
|
} // namespace L4
|
|
@ -1,22 +1,18 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
// RuntimeException class used across L4 library.
|
// RuntimeException class used across L4 library.
|
||||||
class RuntimeException : public std::runtime_error
|
class RuntimeException : public std::runtime_error {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
explicit RuntimeException(const std::string& message)
|
explicit RuntimeException(const std::string& message)
|
||||||
: std::runtime_error(message.c_str())
|
: std::runtime_error(message.c_str()) {}
|
||||||
{}
|
|
||||||
|
|
||||||
explicit RuntimeException(const char* message)
|
explicit RuntimeException(const char* message)
|
||||||
: std::runtime_error(message)
|
: std::runtime_error(message) {}
|
||||||
{}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace L4
|
} // namespace L4
|
||||||
|
|
|
@ -12,84 +12,52 @@
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace L4 {
|
||||||
namespace L4
|
namespace Utils {
|
||||||
{
|
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
|
|
||||||
// Represents a RAII wrapper for Win32 CRITICAL_SECTION.
|
// Represents a RAII wrapper for Win32 CRITICAL_SECTION.
|
||||||
class CriticalSection : protected ::CRITICAL_SECTION
|
class CriticalSection : protected ::CRITICAL_SECTION {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
// Constructs and initializes the critical section.
|
// Constructs and initializes the critical section.
|
||||||
CriticalSection()
|
CriticalSection() { ::InitializeCriticalSection(this); }
|
||||||
{
|
|
||||||
::InitializeCriticalSection(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
CriticalSection(const CriticalSection& other) = delete;
|
CriticalSection(const CriticalSection& other) = delete;
|
||||||
CriticalSection& operator=(const CriticalSection& other) = delete;
|
CriticalSection& operator=(const CriticalSection& other) = delete;
|
||||||
|
|
||||||
// Destructs the critical section.
|
// Destructs the critical section.
|
||||||
~CriticalSection()
|
~CriticalSection() { ::DeleteCriticalSection(this); }
|
||||||
{
|
|
||||||
::DeleteCriticalSection(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Waits for ownership of the critical section.
|
// Waits for ownership of the critical section.
|
||||||
void lock()
|
void lock() { ::EnterCriticalSection(this); }
|
||||||
{
|
|
||||||
::EnterCriticalSection(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Releases ownership of the critical section.
|
// Releases ownership of the critical section.
|
||||||
void unlock()
|
void unlock() { ::LeaveCriticalSection(this); }
|
||||||
{
|
|
||||||
::LeaveCriticalSection(this);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Represents a RAII wrapper for Win32 SRW lock.
|
// Represents a RAII wrapper for Win32 SRW lock.
|
||||||
class ReaderWriterLockSlim
|
class ReaderWriterLockSlim {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
// Constructs and initializes an SRW lock.
|
// Constructs and initializes an SRW lock.
|
||||||
ReaderWriterLockSlim()
|
ReaderWriterLockSlim() { ::InitializeSRWLock(&m_lock); }
|
||||||
{
|
|
||||||
::InitializeSRWLock(&m_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
ReaderWriterLockSlim(const ReaderWriterLockSlim& other) = delete;
|
ReaderWriterLockSlim(const ReaderWriterLockSlim& other) = delete;
|
||||||
ReaderWriterLockSlim& operator=(const ReaderWriterLockSlim& other) = delete;
|
ReaderWriterLockSlim& operator=(const ReaderWriterLockSlim& other) = delete;
|
||||||
|
|
||||||
// Acquires an SRW lock in shared mode.
|
// Acquires an SRW lock in shared mode.
|
||||||
void lock_shared()
|
void lock_shared() { ::AcquireSRWLockShared(&m_lock); }
|
||||||
{
|
|
||||||
::AcquireSRWLockShared(&m_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acquires an SRW lock in exclusive mode.
|
// Acquires an SRW lock in exclusive mode.
|
||||||
void lock()
|
void lock() { ::AcquireSRWLockExclusive(&m_lock); }
|
||||||
{
|
|
||||||
::AcquireSRWLockExclusive(&m_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Releases an SRW lock that was opened in shared mode.
|
// Releases an SRW lock that was opened in shared mode.
|
||||||
void unlock_shared()
|
void unlock_shared() { ::ReleaseSRWLockShared(&m_lock); }
|
||||||
{
|
|
||||||
::ReleaseSRWLockShared(&m_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Releases an SRW lock that was opened in exclusive mode.
|
// Releases an SRW lock that was opened in exclusive mode.
|
||||||
void unlock()
|
void unlock() { ::ReleaseSRWLockExclusive(&m_lock); }
|
||||||
{
|
|
||||||
::ReleaseSRWLockExclusive(&m_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Stores the Win32 SRW lock.
|
// Stores the Win32 SRW lock.
|
||||||
::SRWLOCK m_lock;
|
::SRWLOCK m_lock;
|
||||||
};
|
};
|
||||||
|
@ -97,60 +65,38 @@ private:
|
||||||
#else
|
#else
|
||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
|
|
||||||
class CriticalSection
|
class CriticalSection {
|
||||||
{
|
public:
|
||||||
public:
|
CriticalSection() : m_mutex{} {}
|
||||||
CriticalSection()
|
|
||||||
: m_mutex{}
|
|
||||||
{}
|
|
||||||
|
|
||||||
CriticalSection(const CriticalSection& other) = delete;
|
CriticalSection(const CriticalSection& other) = delete;
|
||||||
CriticalSection& operator=(const CriticalSection& other) = delete;
|
CriticalSection& operator=(const CriticalSection& other) = delete;
|
||||||
|
|
||||||
~CriticalSection() = default;
|
~CriticalSection() = default;
|
||||||
|
|
||||||
void lock()
|
void lock() { pthread_mutex_lock(&m_mutex); }
|
||||||
{
|
|
||||||
pthread_mutex_lock(&m_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void unlock()
|
void unlock() { pthread_mutex_unlock(&m_mutex); }
|
||||||
{
|
|
||||||
pthread_mutex_unlock(&m_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
pthread_mutex_t m_mutex;
|
pthread_mutex_t m_mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ReaderWriterLockSlim
|
class ReaderWriterLockSlim {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
ReaderWriterLockSlim() = default;
|
ReaderWriterLockSlim() = default;
|
||||||
ReaderWriterLockSlim(const ReaderWriterLockSlim& other) = delete;
|
ReaderWriterLockSlim(const ReaderWriterLockSlim& other) = delete;
|
||||||
ReaderWriterLockSlim& operator=(const ReaderWriterLockSlim& other) = delete;
|
ReaderWriterLockSlim& operator=(const ReaderWriterLockSlim& other) = delete;
|
||||||
|
|
||||||
void lock_shared()
|
void lock_shared() { pthread_rwlock_rdlock(&m_lock); }
|
||||||
{
|
|
||||||
pthread_rwlock_rdlock(&m_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lock()
|
void lock() { pthread_rwlock_wrlock(&m_lock); }
|
||||||
{
|
|
||||||
pthread_rwlock_wrlock(&m_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void unlock_shared()
|
void unlock_shared() { pthread_rwlock_unlock(&m_lock); }
|
||||||
{
|
|
||||||
pthread_rwlock_unlock(&m_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void unlock()
|
void unlock() { unlock_shared(); }
|
||||||
{
|
|
||||||
unlock_shared();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
pthread_rwlock_t m_lock = PTHREAD_RWLOCK_INITIALIZER;
|
pthread_rwlock_t m_lock = PTHREAD_RWLOCK_INITIALIZER;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,31 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <complex>
|
#include <complex>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace L4 {
|
||||||
namespace L4
|
namespace Utils {
|
||||||
{
|
namespace Math {
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
namespace Math
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
// Rounds up the number to the nearest multiple of base.
|
// Rounds up the number to the nearest multiple of base.
|
||||||
inline std::uint64_t RoundUp(std::uint64_t number, std::uint64_t base)
|
inline std::uint64_t RoundUp(std::uint64_t number, std::uint64_t base) {
|
||||||
{
|
|
||||||
return base ? (((number + base - 1) / base) * base) : number;
|
return base ? (((number + base - 1) / base) * base) : number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rounds down the number to the nearest multiple of base.
|
// Rounds down the number to the nearest multiple of base.
|
||||||
inline std::uint64_t RoundDown(std::uint64_t number, std::uint64_t base)
|
inline std::uint64_t RoundDown(std::uint64_t number, std::uint64_t base) {
|
||||||
{
|
|
||||||
return base ? ((number / base) * base) : number;
|
return base ? ((number / base) * base) : number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the given number is a power of 2.
|
// Returns true if the given number is a power of 2.
|
||||||
inline bool IsPowerOfTwo(std::uint64_t number)
|
inline bool IsPowerOfTwo(std::uint64_t number) {
|
||||||
{
|
|
||||||
return number && ((number & (number - 1)) == 0);
|
return number && ((number & (number - 1)) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the next highest power of two from the given value.
|
// Returns the next highest power of two from the given value.
|
||||||
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2.
|
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2.
|
||||||
inline std::uint32_t NextHighestPowerOfTwo(std::uint32_t val)
|
inline std::uint32_t NextHighestPowerOfTwo(std::uint32_t val) {
|
||||||
{
|
|
||||||
--val;
|
--val;
|
||||||
val |= val >> 1;
|
val |= val >> 1;
|
||||||
val |= val >> 2;
|
val |= val >> 2;
|
||||||
|
@ -44,36 +35,30 @@ inline std::uint32_t NextHighestPowerOfTwo(std::uint32_t val)
|
||||||
return ++val;
|
return ++val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Provides utility functions doing pointer related arithmetics.
|
// Provides utility functions doing pointer related arithmetics.
|
||||||
namespace PointerArithmetic
|
namespace PointerArithmetic {
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
// Returns a new pointer after adding an offset.
|
// Returns a new pointer after adding an offset.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline T* Add(T* ptr, std::size_t offset)
|
inline T* Add(T* ptr, std::size_t offset) {
|
||||||
{
|
|
||||||
return reinterpret_cast<T*>(reinterpret_cast<std::uintptr_t>(ptr) + offset);
|
return reinterpret_cast<T*>(reinterpret_cast<std::uintptr_t>(ptr) + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a new pointer after subtracting an offset.
|
// Returns a new pointer after subtracting an offset.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline T* Subtract(T* ptr, std::size_t offset)
|
inline T* Subtract(T* ptr, std::size_t offset) {
|
||||||
{
|
|
||||||
return reinterpret_cast<T*>(reinterpret_cast<std::uintptr_t>(ptr) - offset);
|
return reinterpret_cast<T*>(reinterpret_cast<std::uintptr_t>(ptr) - offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the absolute value of difference in the number of bytes between two pointers.
|
// Returns the absolute value of difference in the number of bytes between two
|
||||||
inline std::size_t Distance(const void* lhs, const void* rhs)
|
// pointers.
|
||||||
{
|
inline std::size_t Distance(const void* lhs, const void* rhs) {
|
||||||
return std::abs(reinterpret_cast<std::ptrdiff_t>(lhs) - reinterpret_cast<std::ptrdiff_t>(rhs));
|
return std::abs(reinterpret_cast<std::ptrdiff_t>(lhs) -
|
||||||
|
reinterpret_cast<std::ptrdiff_t>(rhs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace PointerArithmetic
|
} // namespace PointerArithmetic
|
||||||
|
|
||||||
|
|
||||||
} // namespace Math
|
} // namespace Math
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
} // namespace L4
|
} // namespace L4
|
|
@ -4,44 +4,33 @@
|
||||||
|
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
|
|
||||||
|
namespace L4 {
|
||||||
namespace L4
|
namespace Utils {
|
||||||
{
|
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
// Properties class represents a string to string map (case insensitive).
|
// Properties class represents a string to string map (case insensitive).
|
||||||
// It can be used where the configurations should be generic.
|
// It can be used where the configurations should be generic.
|
||||||
class Properties : public StdStringKeyMap<std::string>
|
class Properties : public StdStringKeyMap<std::string> {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
using Base = Utils::StdStringKeyMap<std::string>;
|
using Base = Utils::StdStringKeyMap<std::string>;
|
||||||
using Value = Base::value_type;
|
using Value = Base::value_type;
|
||||||
|
|
||||||
Properties() = default;
|
Properties() = default;
|
||||||
|
|
||||||
// Expose a constructor with initializer_list for convenience.
|
// Expose a constructor with initializer_list for convenience.
|
||||||
Properties(std::initializer_list<Value> values)
|
Properties(std::initializer_list<Value> values) : Base(values) {}
|
||||||
: Base(values)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the given key exists and the value associated with
|
// Returns true if the given key exists and the value associated with
|
||||||
// the key can be converted to the TValue type. If the conversion fails, the value
|
// the key can be converted to the TValue type. If the conversion fails, the
|
||||||
// of the given val is guaranteed to remain the same.
|
// value of the given val is guaranteed to remain the same.
|
||||||
template <typename TValue>
|
template <typename TValue>
|
||||||
bool TryGet(const std::string& key, TValue& val) const
|
bool TryGet(const std::string& key, TValue& val) const {
|
||||||
{
|
|
||||||
const auto it = find(key);
|
const auto it = find(key);
|
||||||
if (it == end())
|
if (it == end()) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TValue tmp;
|
TValue tmp;
|
||||||
if (!boost::conversion::try_lexical_convert(it->second, tmp))
|
if (!boost::conversion::try_lexical_convert(it->second, tmp)) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +40,5 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
} // namespace L4
|
} // namespace L4
|
|
@ -1,49 +1,34 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
|
|
||||||
namespace L4
|
|
||||||
{
|
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
|
|
||||||
|
namespace L4 {
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
// NoOp is a function object that doesn't do anything.
|
// NoOp is a function object that doesn't do anything.
|
||||||
struct NoOp
|
struct NoOp {
|
||||||
{
|
|
||||||
void operator()(...) {}
|
void operator()(...) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// RunningThread wraps around std::thread and repeatedly runs a given function after yielding
|
// RunningThread wraps around std::thread and repeatedly runs a given function
|
||||||
// for the given interval. Note that the destructor waits for the thread to stop.
|
// after yielding for the given interval. Note that the destructor waits for the
|
||||||
|
// thread to stop.
|
||||||
template <typename CoreFunc, typename PrepFunc = NoOp>
|
template <typename CoreFunc, typename PrepFunc = NoOp>
|
||||||
class RunningThread
|
class RunningThread {
|
||||||
{
|
public:
|
||||||
public:
|
RunningThread(std::chrono::milliseconds interval,
|
||||||
RunningThread(
|
|
||||||
std::chrono::milliseconds interval,
|
|
||||||
CoreFunc coreFunc,
|
CoreFunc coreFunc,
|
||||||
PrepFunc prepFunc = PrepFunc())
|
PrepFunc prepFunc = PrepFunc())
|
||||||
: m_isRunning(),
|
: m_isRunning(),
|
||||||
m_thread(
|
m_thread(&RunningThread::Start, this, interval, coreFunc, prepFunc) {}
|
||||||
&RunningThread::Start,
|
|
||||||
this,
|
|
||||||
interval,
|
|
||||||
coreFunc,
|
|
||||||
prepFunc)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~RunningThread()
|
~RunningThread() {
|
||||||
{
|
|
||||||
m_isRunning.store(false);
|
m_isRunning.store(false);
|
||||||
|
|
||||||
if (m_thread.joinable())
|
if (m_thread.joinable()) {
|
||||||
{
|
|
||||||
m_thread.join();
|
m_thread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,18 +36,15 @@ public:
|
||||||
RunningThread(const RunningThread&) = delete;
|
RunningThread(const RunningThread&) = delete;
|
||||||
RunningThread& operator=(const RunningThread&) = delete;
|
RunningThread& operator=(const RunningThread&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Start(
|
void Start(std::chrono::milliseconds interval,
|
||||||
std::chrono::milliseconds interval,
|
|
||||||
CoreFunc coreFunc,
|
CoreFunc coreFunc,
|
||||||
PrepFunc prepFunc)
|
PrepFunc prepFunc) {
|
||||||
{
|
|
||||||
m_isRunning.store(true);
|
m_isRunning.store(true);
|
||||||
|
|
||||||
prepFunc();
|
prepFunc();
|
||||||
|
|
||||||
while (m_isRunning.load())
|
while (m_isRunning.load()) {
|
||||||
{
|
|
||||||
coreFunc();
|
coreFunc();
|
||||||
|
|
||||||
std::this_thread::sleep_for(interval);
|
std::this_thread::sleep_for(interval);
|
||||||
|
@ -74,6 +56,5 @@ private:
|
||||||
std::thread m_thread;
|
std::thread m_thread;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
} // namespace L4
|
} // namespace L4
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
// Allow macro redefinition.
|
// Allow macro redefinition.
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable:4005)
|
#pragma warning(disable : 4005)
|
||||||
|
|
||||||
// Explicitly excluding API groups
|
// Explicitly excluding API groups
|
||||||
//#define NOGDICAPMASKS // - CC_*, LC_*, PC_*, CP_*, TC_*, RC_
|
//#define NOGDICAPMASKS // - CC_*, LC_*, PC_*, CP_*, TC_*, RC_
|
||||||
|
@ -44,14 +44,15 @@
|
||||||
#define NODEFERWINDOWPOS // - DeferWindowPos routines
|
#define NODEFERWINDOWPOS // - DeferWindowPos routines
|
||||||
#define NOMCX // - Modem Configuration Extensions
|
#define NOMCX // - Modem Configuration Extensions
|
||||||
|
|
||||||
// Enabling STRICT redefines certain data types so that the compiler does not permit assignment from one type to another without an explicit cast.
|
// Enabling STRICT redefines certain data types so that the compiler does not
|
||||||
|
// permit assignment from one type to another without an explicit cast.
|
||||||
#define STRICT
|
#define STRICT
|
||||||
|
|
||||||
// Define WIN32_LEAN_AND_MEAN to exclude APIs such as Cryptography, DDE, RPC, Shell, and Windows Sockets.
|
// Define WIN32_LEAN_AND_MEAN to exclude APIs such as Cryptography, DDE, RPC,
|
||||||
// Cryptography is needed due to <boost/uuids/random_generator.hpp>
|
// Shell, and Windows Sockets. Cryptography is needed due to
|
||||||
|
// <boost/uuids/random_generator.hpp>
|
||||||
//#define WIN32_LEAN_AND_MEAN
|
//#define WIN32_LEAN_AND_MEAN
|
||||||
|
|
||||||
#pragma warning(pop)
|
#pragma warning(pop)
|
||||||
|
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
|
@ -2,14 +2,10 @@
|
||||||
|
|
||||||
#include <boost/interprocess/detail/utilities.hpp>
|
#include <boost/interprocess/detail/utilities.hpp>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Detail {
|
||||||
namespace Detail
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
using boost::interprocess::ipcdetail::to_raw_pointer;
|
using boost::interprocess::ipcdetail::to_raw_pointer;
|
||||||
|
|
||||||
|
|
||||||
} // namespace Detail
|
} // namespace Detail
|
||||||
} // namespace L4
|
} // namespace L4
|
|
@ -5,36 +5,33 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
// EpochActionManager class implementation.
|
// EpochActionManager class implementation.
|
||||||
|
|
||||||
EpochActionManager::EpochActionManager(std::uint8_t numActionQueues)
|
EpochActionManager::EpochActionManager(std::uint8_t numActionQueues)
|
||||||
: m_epochToActionsList{}
|
: m_epochToActionsList{}, m_counter{} {
|
||||||
, m_counter{}
|
|
||||||
{
|
|
||||||
// Calculate numActionQueues as the next highest power of two.
|
// Calculate numActionQueues as the next highest power of two.
|
||||||
std::uint16_t newNumActionQueues = numActionQueues;
|
std::uint16_t newNumActionQueues = numActionQueues;
|
||||||
if (numActionQueues == 0U)
|
if (numActionQueues == 0U) {
|
||||||
{
|
newNumActionQueues =
|
||||||
newNumActionQueues = static_cast<std::uint16_t>(std::thread::hardware_concurrency());
|
static_cast<std::uint16_t>(std::thread::hardware_concurrency());
|
||||||
}
|
}
|
||||||
newNumActionQueues = static_cast<std::uint16_t>(Utils::Math::NextHighestPowerOfTwo(newNumActionQueues));
|
newNumActionQueues = static_cast<std::uint16_t>(
|
||||||
|
Utils::Math::NextHighestPowerOfTwo(newNumActionQueues));
|
||||||
|
|
||||||
assert(newNumActionQueues != 0U && Utils::Math::IsPowerOfTwo(newNumActionQueues));
|
assert(newNumActionQueues != 0U &&
|
||||||
|
Utils::Math::IsPowerOfTwo(newNumActionQueues));
|
||||||
|
|
||||||
// Initialize m_epochToActionsList.
|
// Initialize m_epochToActionsList.
|
||||||
m_epochToActionsList.resize(newNumActionQueues);
|
m_epochToActionsList.resize(newNumActionQueues);
|
||||||
for (auto& epochToActions : m_epochToActionsList)
|
for (auto& epochToActions : m_epochToActionsList) {
|
||||||
{
|
|
||||||
std::get<0>(epochToActions) = std::make_unique<Mutex>();
|
std::get<0>(epochToActions) = std::make_unique<Mutex>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EpochActionManager::RegisterAction(std::uint64_t epochCounter,
|
||||||
void EpochActionManager::RegisterAction(std::uint64_t epochCounter, IEpochActionManager::Action&& action)
|
IEpochActionManager::Action&& action) {
|
||||||
{
|
|
||||||
std::uint32_t index = ++m_counter & (m_epochToActionsList.size() - 1);
|
std::uint32_t index = ++m_counter & (m_epochToActionsList.size() - 1);
|
||||||
auto& epochToActions = m_epochToActionsList[index];
|
auto& epochToActions = m_epochToActionsList[index];
|
||||||
|
|
||||||
|
@ -42,14 +39,11 @@ void EpochActionManager::RegisterAction(std::uint64_t epochCounter, IEpochAction
|
||||||
std::get<1>(epochToActions)[epochCounter].emplace_back(std::move(action));
|
std::get<1>(epochToActions)[epochCounter].emplace_back(std::move(action));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::uint64_t EpochActionManager::PerformActions(std::uint64_t epochCounter) {
|
||||||
std::uint64_t EpochActionManager::PerformActions(std::uint64_t epochCounter)
|
|
||||||
{
|
|
||||||
// Actions will be moved here and performed without a lock.
|
// Actions will be moved here and performed without a lock.
|
||||||
Actions actionsToPerform;
|
Actions actionsToPerform;
|
||||||
|
|
||||||
for (auto& epochToActionsWithLock : m_epochToActionsList)
|
for (auto& epochToActionsWithLock : m_epochToActionsList) {
|
||||||
{
|
|
||||||
Lock lock(*std::get<0>(epochToActionsWithLock));
|
Lock lock(*std::get<0>(epochToActionsWithLock));
|
||||||
|
|
||||||
// lower_bound() so that it is deleted up to but not including epochCounter.
|
// lower_bound() so that it is deleted up to but not including epochCounter.
|
||||||
|
@ -58,14 +52,13 @@ std::uint64_t EpochActionManager::PerformActions(std::uint64_t epochCounter)
|
||||||
|
|
||||||
auto it = epochToActions.begin();
|
auto it = epochToActions.begin();
|
||||||
|
|
||||||
while (it != endIt)
|
while (it != endIt) {
|
||||||
{
|
actionsToPerform.insert(actionsToPerform.end(),
|
||||||
actionsToPerform.insert(
|
|
||||||
actionsToPerform.end(),
|
|
||||||
std::make_move_iterator(it->second.begin()),
|
std::make_move_iterator(it->second.begin()),
|
||||||
std::make_move_iterator(it->second.end()));
|
std::make_move_iterator(it->second.end()));
|
||||||
|
|
||||||
// The following post increment is intentional to avoid iterator invalidation issue.
|
// The following post increment is intentional to avoid iterator
|
||||||
|
// invalidation issue.
|
||||||
epochToActions.erase(it++);
|
epochToActions.erase(it++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,11 +68,8 @@ std::uint64_t EpochActionManager::PerformActions(std::uint64_t epochCounter)
|
||||||
return actionsToPerform.size();
|
return actionsToPerform.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EpochActionManager::ApplyActions(Actions& actions) {
|
||||||
void EpochActionManager::ApplyActions(Actions& actions)
|
for (auto& action : actions) {
|
||||||
{
|
|
||||||
for (auto& action : actions)
|
|
||||||
{
|
|
||||||
action();
|
action();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,41 @@
|
||||||
#include "Interprocess/Connection/ConnectionMonitor.h"
|
#include "Interprocess/Connection/ConnectionMonitor.h"
|
||||||
|
#include <atomic>
|
||||||
#include "Interprocess/Connection/EndPointInfoUtils.h"
|
#include "Interprocess/Connection/EndPointInfoUtils.h"
|
||||||
#include "Utils/Exception.h"
|
#include "Utils/Exception.h"
|
||||||
#include "Utils/Windows.h"
|
#include "Utils/Windows.h"
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Interprocess {
|
||||||
namespace Interprocess
|
namespace Connection {
|
||||||
{
|
|
||||||
namespace Connection
|
|
||||||
{
|
|
||||||
|
|
||||||
// ConnectionMonitor class implementation.
|
// ConnectionMonitor class implementation.
|
||||||
|
|
||||||
ConnectionMonitor::ConnectionMonitor()
|
ConnectionMonitor::ConnectionMonitor()
|
||||||
: m_localEndPoint{ EndPointInfoFactory().Create() }
|
: m_localEndPoint{EndPointInfoFactory().Create()},
|
||||||
, m_localEvent{
|
m_localEvent{::CreateEvent(
|
||||||
::CreateEvent(
|
|
||||||
NULL,
|
NULL,
|
||||||
TRUE, // Manual reset in order to notify all end points registered.
|
TRUE, // Manual reset in order to notify all end points registered.
|
||||||
FALSE,
|
FALSE,
|
||||||
StringConverter()(m_localEndPoint).c_str()) }
|
StringConverter()(m_localEndPoint).c_str())} {}
|
||||||
{}
|
|
||||||
|
|
||||||
|
ConnectionMonitor::~ConnectionMonitor() {
|
||||||
ConnectionMonitor::~ConnectionMonitor()
|
|
||||||
{
|
|
||||||
// Notify the remote endpoints.
|
// Notify the remote endpoints.
|
||||||
::SetEvent(static_cast<HANDLE>(m_localEvent));
|
::SetEvent(static_cast<HANDLE>(m_localEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EndPointInfo& ConnectionMonitor::GetLocalEndPointInfo() const {
|
||||||
const EndPointInfo& ConnectionMonitor::GetLocalEndPointInfo() const
|
|
||||||
{
|
|
||||||
return m_localEndPoint;
|
return m_localEndPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::size_t ConnectionMonitor::GetRemoteConnectionsCount() const {
|
||||||
std::size_t ConnectionMonitor::GetRemoteConnectionsCount() const
|
|
||||||
{
|
|
||||||
UnRegister();
|
UnRegister();
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(m_mutexOnRemoteMonitors);
|
std::lock_guard<std::mutex> lock(m_mutexOnRemoteMonitors);
|
||||||
return m_remoteMonitors.size();
|
return m_remoteMonitors.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConnectionMonitor::Register(const EndPointInfo& remoteEndPoint,
|
||||||
void ConnectionMonitor::Register(const EndPointInfo& remoteEndPoint, Callback callback)
|
Callback callback) {
|
||||||
{
|
|
||||||
UnRegister();
|
UnRegister();
|
||||||
|
|
||||||
// The following is needed to prevent the case where the callback is trying
|
// The following is needed to prevent the case where the callback is trying
|
||||||
|
@ -62,111 +50,97 @@ void ConnectionMonitor::Register(const EndPointInfo& remoteEndPoint, Callback ca
|
||||||
|
|
||||||
// Note that the following call may throw since opening handles may fail, but
|
// Note that the following call may throw since opening handles may fail, but
|
||||||
// it is exception safe (std::map::emplace has a strong guarantee on it).
|
// it is exception safe (std::map::emplace has a strong guarantee on it).
|
||||||
if (!m_remoteMonitors.emplace(
|
if (!m_remoteMonitors
|
||||||
remoteEndPoint,
|
.emplace(remoteEndPoint,
|
||||||
std::make_unique<HandleMonitor>(
|
std::make_unique<HandleMonitor>(
|
||||||
remoteEndPoint,
|
remoteEndPoint,
|
||||||
[thisWeakPtr, callback, isCalled](const auto& remoteEndPoint)
|
[thisWeakPtr, callback,
|
||||||
{
|
isCalled](const auto& remoteEndPoint) {
|
||||||
if (isCalled->exchange(true))
|
if (isCalled->exchange(true)) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(remoteEndPoint);
|
callback(remoteEndPoint);
|
||||||
auto connectionMonitor = thisWeakPtr.lock();
|
auto connectionMonitor = thisWeakPtr.lock();
|
||||||
if (connectionMonitor != nullptr)
|
if (connectionMonitor != nullptr) {
|
||||||
{
|
// Cannot call UnRegister() because it will
|
||||||
// Cannot call UnRegister() because it will self-destruct.
|
// self-destruct. Instead, call the UnRegister(const
|
||||||
// Instead, call the UnRegister(const EndPointInfo&) and queue up the end point
|
// EndPointInfo&) and queue up the end point that
|
||||||
// that will be removed from m_remoteEvents at a later time.
|
// will be removed from m_remoteEvents at a later
|
||||||
|
// time.
|
||||||
connectionMonitor->UnRegister(remoteEndPoint);
|
connectionMonitor->UnRegister(remoteEndPoint);
|
||||||
}
|
}
|
||||||
})).second)
|
}))
|
||||||
{
|
.second) {
|
||||||
throw RuntimeException("Duplicate end point found.");
|
throw RuntimeException("Duplicate end point found.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConnectionMonitor::UnRegister(const EndPointInfo& remoteEndPoint) {
|
||||||
void ConnectionMonitor::UnRegister(const EndPointInfo& remoteEndPoint)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(m_mutexOnUnregisteredEndPoints);
|
std::lock_guard<std::mutex> lock(m_mutexOnUnregisteredEndPoints);
|
||||||
m_unregisteredEndPoints.emplace_back(remoteEndPoint);
|
m_unregisteredEndPoints.emplace_back(remoteEndPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConnectionMonitor::UnRegister() const {
|
||||||
void ConnectionMonitor::UnRegister() const
|
|
||||||
{
|
|
||||||
std::vector<EndPointInfo> unregisteredEndPoints;
|
std::vector<EndPointInfo> unregisteredEndPoints;
|
||||||
{
|
{
|
||||||
// It is possible that the erase() in the following block can
|
// It is possible that the erase() in the following block can
|
||||||
// wait for the callback to finish (::WaitForThreadpoolWaitCallbacks).
|
// wait for the callback to finish (::WaitForThreadpoolWaitCallbacks).
|
||||||
// Since the callback calls the UnRegister(const EndPointinfo&), it can deadlock
|
// Since the callback calls the UnRegister(const EndPointinfo&), it can
|
||||||
// if this function holds the lock while calling the erase().
|
// deadlock if this function holds the lock while calling the erase(). Thus,
|
||||||
// Thus, copy the m_unregisteredEndPoints and release the lock before calling erase() below.
|
// copy the m_unregisteredEndPoints and release the lock before calling
|
||||||
|
// erase() below.
|
||||||
std::lock_guard<std::mutex> lock(m_mutexOnUnregisteredEndPoints);
|
std::lock_guard<std::mutex> lock(m_mutexOnUnregisteredEndPoints);
|
||||||
unregisteredEndPoints.swap(m_unregisteredEndPoints);
|
unregisteredEndPoints.swap(m_unregisteredEndPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(m_mutexOnRemoteMonitors);
|
std::lock_guard<std::mutex> lock(m_mutexOnRemoteMonitors);
|
||||||
for (const auto& endPoint : unregisteredEndPoints)
|
for (const auto& endPoint : unregisteredEndPoints) {
|
||||||
{
|
|
||||||
m_remoteMonitors.erase(endPoint);
|
m_remoteMonitors.erase(endPoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ConnectionMonitor::HandleMonitor::HandleMonitor class implementation.
|
// ConnectionMonitor::HandleMonitor::HandleMonitor class implementation.
|
||||||
|
|
||||||
ConnectionMonitor::HandleMonitor::HandleMonitor(
|
ConnectionMonitor::HandleMonitor::HandleMonitor(
|
||||||
const EndPointInfo& remoteEndPoint,
|
const EndPointInfo& remoteEndPoint,
|
||||||
Callback callback)
|
Callback callback)
|
||||||
: m_eventWaiter{
|
: m_eventWaiter{std::make_unique<Waiter>(
|
||||||
std::make_unique<Waiter>(
|
Utils::Handle{::OpenEvent(SYNCHRONIZE,
|
||||||
Utils::Handle{ ::OpenEvent(SYNCHRONIZE, FALSE, StringConverter()(remoteEndPoint).c_str()) },
|
FALSE,
|
||||||
[callback, endPoint = remoteEndPoint] { callback(endPoint); }) }
|
StringConverter()(remoteEndPoint).c_str())},
|
||||||
, m_processWaiter{
|
[callback, endPoint = remoteEndPoint] { callback(endPoint); })},
|
||||||
std::make_unique<Waiter>(
|
m_processWaiter{std::make_unique<Waiter>(
|
||||||
Utils::Handle{ ::OpenProcess(SYNCHRONIZE, FALSE, remoteEndPoint.m_pid) },
|
Utils::Handle{
|
||||||
[callback, endPoint = remoteEndPoint] { callback(endPoint); }) }
|
::OpenProcess(SYNCHRONIZE, FALSE, remoteEndPoint.m_pid)},
|
||||||
{}
|
[callback, endPoint = remoteEndPoint] { callback(endPoint); })} {}
|
||||||
|
|
||||||
|
|
||||||
// ConnectionMonitor::HandleMonitor::Waiter class implementation.
|
// ConnectionMonitor::HandleMonitor::Waiter class implementation.
|
||||||
|
|
||||||
ConnectionMonitor::HandleMonitor::Waiter::Waiter(Utils::Handle handle, Callback callback)
|
ConnectionMonitor::HandleMonitor::Waiter::Waiter(Utils::Handle handle,
|
||||||
: m_handle{ std::move(handle) }
|
Callback callback)
|
||||||
, m_callback{ callback }
|
: m_handle{std::move(handle)},
|
||||||
, m_wait{
|
m_callback{callback},
|
||||||
::CreateThreadpoolWait(OnEvent, this, NULL),
|
m_wait{::CreateThreadpoolWait(OnEvent, this, NULL),
|
||||||
::CloseThreadpoolWait }
|
::CloseThreadpoolWait} {
|
||||||
{
|
|
||||||
::SetThreadpoolWait(m_wait.get(), static_cast<HANDLE>(m_handle), NULL);
|
::SetThreadpoolWait(m_wait.get(), static_cast<HANDLE>(m_handle), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectionMonitor::HandleMonitor::Waiter::~Waiter() {
|
||||||
ConnectionMonitor::HandleMonitor::Waiter::~Waiter()
|
|
||||||
{
|
|
||||||
::SetThreadpoolWait(m_wait.get(), NULL, NULL);
|
::SetThreadpoolWait(m_wait.get(), NULL, NULL);
|
||||||
|
|
||||||
::WaitForThreadpoolWaitCallbacks(m_wait.get(), TRUE);
|
::WaitForThreadpoolWaitCallbacks(m_wait.get(), TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
VOID CALLBACK ConnectionMonitor::HandleMonitor::Waiter::OnEvent(
|
VOID CALLBACK ConnectionMonitor::HandleMonitor::Waiter::OnEvent(
|
||||||
PTP_CALLBACK_INSTANCE /*instance*/,
|
PTP_CALLBACK_INSTANCE /*instance*/,
|
||||||
PVOID context,
|
PVOID context,
|
||||||
PTP_WAIT /*wait*/,
|
PTP_WAIT /*wait*/,
|
||||||
TP_WAIT_RESULT waitResult)
|
TP_WAIT_RESULT waitResult) {
|
||||||
{
|
if (waitResult == WAIT_OBJECT_0) {
|
||||||
if (waitResult == WAIT_OBJECT_0)
|
|
||||||
{
|
|
||||||
static_cast<Waiter*>(context)->m_callback();
|
static_cast<Waiter*>(context)->m_callback();
|
||||||
}
|
} else {
|
||||||
else
|
throw std::runtime_error{"Unexpected wait result is received."};
|
||||||
{
|
|
||||||
throw std::runtime_error{ "Unexpected wait result is received." };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,33 +1,24 @@
|
||||||
#include "Interprocess/Connection/EndPointInfoUtils.h"
|
#include "Interprocess/Connection/EndPointInfoUtils.h"
|
||||||
#include "Utils/Windows.h"
|
|
||||||
#include <boost/uuid/random_generator.hpp>
|
#include <boost/uuid/random_generator.hpp>
|
||||||
#include <boost/uuid/uuid_io.hpp>
|
#include <boost/uuid/uuid_io.hpp>
|
||||||
|
#include "Utils/Windows.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Interprocess {
|
||||||
namespace Interprocess
|
namespace Connection {
|
||||||
{
|
|
||||||
namespace Connection
|
|
||||||
{
|
|
||||||
|
|
||||||
// EndPointInfoFactory class implementation.
|
// EndPointInfoFactory class implementation.
|
||||||
|
|
||||||
EndPointInfo EndPointInfoFactory::Create() const
|
EndPointInfo EndPointInfoFactory::Create() const {
|
||||||
{
|
return EndPointInfo{GetCurrentProcessId(),
|
||||||
return EndPointInfo{ GetCurrentProcessId(), boost::uuids::random_generator()() };
|
boost::uuids::random_generator()()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// StringConverter class implementation.
|
// StringConverter class implementation.
|
||||||
|
|
||||||
std::string StringConverter::operator()(const EndPointInfo& endPoint) const
|
std::string StringConverter::operator()(const EndPointInfo& endPoint) const {
|
||||||
{
|
return "[pid:" + std::to_string(endPoint.m_pid) + "," +
|
||||||
return "[pid:"
|
"uuid:" + boost::uuids::to_string(endPoint.m_uuid) + "]";
|
||||||
+ std::to_string(endPoint.m_pid)
|
|
||||||
+ ","
|
|
||||||
+ "uuid:"
|
|
||||||
+ boost::uuids::to_string(endPoint.m_uuid)
|
|
||||||
+ "]";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Connection
|
} // namespace Connection
|
||||||
|
|
|
@ -1,39 +1,26 @@
|
||||||
#include "Interprocess/Utils/Handle.h"
|
#include "Interprocess/Utils/Handle.h"
|
||||||
#include "Utils/Exception.h"
|
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
#include "Utils/Exception.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
namespace Interprocess {
|
||||||
namespace Interprocess
|
namespace Utils {
|
||||||
{
|
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
|
|
||||||
// Handle class implementation.
|
// Handle class implementation.
|
||||||
|
|
||||||
Handle::Handle(HANDLE handle, bool verifyHandle)
|
Handle::Handle(HANDLE handle, bool verifyHandle)
|
||||||
: m_handle{ Verify(handle, verifyHandle), ::CloseHandle }
|
: m_handle{Verify(handle, verifyHandle), ::CloseHandle} {}
|
||||||
{}
|
|
||||||
|
|
||||||
|
Handle::Handle(Handle&& other) : m_handle{std::move(other.m_handle)} {}
|
||||||
|
|
||||||
Handle::Handle(Handle&& other)
|
Handle::operator HANDLE() const {
|
||||||
: m_handle{ std::move(other.m_handle) }
|
|
||||||
{}
|
|
||||||
|
|
||||||
|
|
||||||
Handle::operator HANDLE() const
|
|
||||||
{
|
|
||||||
return m_handle.get();
|
return m_handle.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HANDLE Handle::Verify(HANDLE handle, bool verifyHandle) const {
|
||||||
HANDLE Handle::Verify(HANDLE handle, bool verifyHandle) const
|
if (handle == NULL || handle == INVALID_HANDLE_VALUE || verifyHandle) {
|
||||||
{
|
|
||||||
if (handle == NULL || handle == INVALID_HANDLE_VALUE || verifyHandle)
|
|
||||||
{
|
|
||||||
auto error = ::GetLastError();
|
auto error = ::GetLastError();
|
||||||
if (error != ERROR_SUCCESS)
|
if (error != ERROR_SUCCESS) {
|
||||||
{
|
|
||||||
boost::format err("Invalid handle: %1%.");
|
boost::format err("Invalid handle: %1%.");
|
||||||
err % error;
|
err % error;
|
||||||
throw RuntimeException(err.str());
|
throw RuntimeException(err.str());
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
#include "Log/PerfLogger.h"
|
#include "Log/PerfLogger.h"
|
||||||
#include "Utils/Exception.h"
|
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
#include "Utils/Exception.h"
|
||||||
|
|
||||||
namespace L4
|
namespace L4 {
|
||||||
{
|
|
||||||
|
|
||||||
// PerfData class implementation.
|
// PerfData class implementation.
|
||||||
|
|
||||||
void PerfData::AddHashTablePerfData(const char* hashTableName, const HashTablePerfData& perfData)
|
void PerfData::AddHashTablePerfData(const char* hashTableName,
|
||||||
{
|
const HashTablePerfData& perfData) {
|
||||||
auto result = m_hashTablesPerfData.insert(
|
auto result = m_hashTablesPerfData.insert(
|
||||||
std::make_pair(
|
std::make_pair(hashTableName, HashTablesPerfData::mapped_type(perfData)));
|
||||||
hashTableName,
|
|
||||||
HashTablesPerfData::mapped_type(perfData)));
|
|
||||||
|
|
||||||
if (!result.second)
|
if (!result.second) {
|
||||||
{
|
|
||||||
boost::format err("Duplicate hash table name found: '%1%'.");
|
boost::format err("Duplicate hash table name found: '%1%'.");
|
||||||
err % hashTableName;
|
err % hashTableName;
|
||||||
throw RuntimeException(err.str());
|
throw RuntimeException(err.str());
|
||||||
|
|
Загрузка…
Ссылка в новой задаче