mirror of
https://github.com/oxen-io/lokinet.git
synced 2024-10-29 11:05:43 +00:00
339 lines
8.9 KiB
C++
339 lines
8.9 KiB
C++
|
#include <util/timerqueue.hpp>
|
||
|
|
||
|
#include <thread>
|
||
|
|
||
|
#include <gtest/gtest.h>
|
||
|
#include <gmock/gmock.h>
|
||
|
|
||
|
using CharQueue = llarp::thread::TimerQueue< const char* >;
|
||
|
using CharItem = llarp::thread::TimerQueueItem< const char* >;
|
||
|
|
||
|
TEST(TimerQueue, smoke)
|
||
|
{
|
||
|
CharQueue queue;
|
||
|
const absl::Time TA = absl::Time();
|
||
|
const absl::Time TB = TA + absl::Seconds(1);
|
||
|
const absl::Time TC = TB + absl::Seconds(1);
|
||
|
const absl::Time TD = TC + absl::Seconds(1);
|
||
|
const absl::Time TE = TD + absl::Seconds(1);
|
||
|
|
||
|
const char* VA = "hello";
|
||
|
const char* VB = "world,";
|
||
|
const char* VC = "how";
|
||
|
const char* VD = "are";
|
||
|
const char* VE = "you";
|
||
|
|
||
|
int HA = queue.add(TA, VA);
|
||
|
int HB = queue.add(TB, VB);
|
||
|
int HC = queue.add(TC, VC);
|
||
|
int HD = queue.add(TD, VD);
|
||
|
int HE = queue.add(TE, VE);
|
||
|
|
||
|
CharItem tItem;
|
||
|
absl::Time newMinTime;
|
||
|
size_t newSize;
|
||
|
|
||
|
ASSERT_TRUE(queue.popFront(&tItem));
|
||
|
ASSERT_EQ(VA, tItem.value());
|
||
|
ASSERT_EQ(TA, tItem.time());
|
||
|
ASSERT_EQ(HA, tItem.handle());
|
||
|
|
||
|
ASSERT_TRUE(queue.popFront(&tItem, &newSize, &newMinTime));
|
||
|
ASSERT_EQ(3, newSize);
|
||
|
ASSERT_EQ(TC, newMinTime);
|
||
|
ASSERT_EQ(TB, tItem.time());
|
||
|
ASSERT_EQ(VB, tItem.value());
|
||
|
ASSERT_EQ(HB, tItem.handle());
|
||
|
|
||
|
std::vector< CharItem > a1;
|
||
|
|
||
|
queue.popLess(TD, &a1, &newSize, &newMinTime);
|
||
|
ASSERT_EQ(2, a1.size());
|
||
|
ASSERT_EQ(1, newSize);
|
||
|
ASSERT_EQ(TE, newMinTime);
|
||
|
ASSERT_EQ(TC, a1[0].time());
|
||
|
ASSERT_EQ(VC, a1[0].value());
|
||
|
ASSERT_EQ(HC, a1[0].handle());
|
||
|
ASSERT_EQ(TD, a1[1].time());
|
||
|
ASSERT_EQ(VD, a1[1].value());
|
||
|
ASSERT_EQ(HD, a1[1].handle());
|
||
|
|
||
|
std::vector< CharItem > a2;
|
||
|
|
||
|
queue.popLess(TD, &a2, &newSize, &newMinTime);
|
||
|
ASSERT_EQ(0, a2.size());
|
||
|
ASSERT_EQ(1, newSize);
|
||
|
ASSERT_EQ(TE, newMinTime);
|
||
|
|
||
|
std::vector< CharItem > a3;
|
||
|
|
||
|
queue.popLess(TE, &a3, &newSize, &newMinTime);
|
||
|
ASSERT_EQ(1, a3.size());
|
||
|
ASSERT_EQ(0, newSize);
|
||
|
ASSERT_EQ(TE, a3[0].time());
|
||
|
ASSERT_EQ(VE, a3[0].value());
|
||
|
ASSERT_EQ(HE, a3[0].handle());
|
||
|
}
|
||
|
|
||
|
TEST(TimerQueue, KeySmoke)
|
||
|
{
|
||
|
CharQueue x1;
|
||
|
const absl::Time TA = absl::Time();
|
||
|
const absl::Time TB = TA + absl::Seconds(1);
|
||
|
const absl::Time TC = TB + absl::Seconds(1);
|
||
|
const absl::Time TD = TC + absl::Seconds(1);
|
||
|
const absl::Time TE = TD + absl::Seconds(1);
|
||
|
|
||
|
const char* VA = "hello";
|
||
|
const char* VB = "world,";
|
||
|
const char* VC = "how";
|
||
|
const char* VD = "are";
|
||
|
const char* VE = "you";
|
||
|
|
||
|
typedef CharQueue::Key Key;
|
||
|
|
||
|
const Key KA = Key(&TA);
|
||
|
const Key KB = Key(&TB);
|
||
|
const Key KC = Key(382);
|
||
|
const Key KD = Key(123);
|
||
|
const Key KE = Key(&VE);
|
||
|
|
||
|
int HA = x1.add(TA, VA, KA);
|
||
|
int HB = x1.add(TB, VB, KB);
|
||
|
int HC = x1.add(TC, VC, KC);
|
||
|
int HD = x1.add(TD, VD, KD);
|
||
|
int HE = x1.add(TE, VE, KE);
|
||
|
|
||
|
ASSERT_FALSE(x1.remove(HA, KB));
|
||
|
ASSERT_TRUE(x1.isValid(HA, KA));
|
||
|
ASSERT_FALSE(x1.update(HC, KD, TE));
|
||
|
|
||
|
CharItem tItem;
|
||
|
absl::Time newMinTime;
|
||
|
size_t newSize;
|
||
|
|
||
|
ASSERT_TRUE(x1.popFront(&tItem));
|
||
|
ASSERT_EQ(VA, tItem.value());
|
||
|
ASSERT_EQ(TA, tItem.time());
|
||
|
ASSERT_EQ(HA, tItem.handle());
|
||
|
ASSERT_EQ(KA, tItem.key());
|
||
|
|
||
|
ASSERT_TRUE(x1.popFront(&tItem, &newSize, &newMinTime));
|
||
|
ASSERT_EQ(3, newSize);
|
||
|
ASSERT_EQ(TC, newMinTime);
|
||
|
ASSERT_EQ(TB, tItem.time());
|
||
|
ASSERT_EQ(VB, tItem.value());
|
||
|
ASSERT_EQ(HB, tItem.handle());
|
||
|
ASSERT_EQ(KB, tItem.key());
|
||
|
|
||
|
std::vector< CharItem > a1;
|
||
|
|
||
|
x1.popLess(TD, &a1, &newSize, &newMinTime);
|
||
|
ASSERT_EQ(2, a1.size());
|
||
|
ASSERT_EQ(1, newSize);
|
||
|
ASSERT_EQ(TE, newMinTime);
|
||
|
ASSERT_EQ(TC, a1[0].time());
|
||
|
ASSERT_EQ(VC, a1[0].value());
|
||
|
ASSERT_EQ(HC, a1[0].handle());
|
||
|
ASSERT_EQ(KC, a1[0].key());
|
||
|
ASSERT_EQ(TD, a1[1].time());
|
||
|
ASSERT_EQ(VD, a1[1].value());
|
||
|
ASSERT_EQ(HD, a1[1].handle());
|
||
|
ASSERT_EQ(KD, a1[1].key());
|
||
|
|
||
|
std::vector< CharItem > a2;
|
||
|
|
||
|
x1.popLess(TD, &a2, &newSize, &newMinTime);
|
||
|
ASSERT_EQ(0, a2.size());
|
||
|
ASSERT_EQ(1, newSize);
|
||
|
ASSERT_EQ(TE, newMinTime);
|
||
|
|
||
|
std::vector< CharItem > a3;
|
||
|
|
||
|
x1.popLess(TE, &a3, &newSize, &newMinTime);
|
||
|
ASSERT_EQ(1, a3.size());
|
||
|
ASSERT_EQ(0, newSize);
|
||
|
ASSERT_EQ(TE, a3[0].time());
|
||
|
ASSERT_EQ(VE, a3[0].value());
|
||
|
ASSERT_EQ(HE, a3[0].handle());
|
||
|
ASSERT_EQ(KE, a3[0].key());
|
||
|
}
|
||
|
|
||
|
TEST(TimerQueue, Update)
|
||
|
{
|
||
|
const char VA[] = "A";
|
||
|
const char VB[] = "B";
|
||
|
const char VC[] = "C";
|
||
|
const char VD[] = "D";
|
||
|
const char VE[] = "E";
|
||
|
|
||
|
// clang-format off
|
||
|
static const struct
|
||
|
{
|
||
|
int m_secs;
|
||
|
int m_nsecs;
|
||
|
const char* m_value;
|
||
|
int m_updsecs;
|
||
|
int m_updnsecs;
|
||
|
bool m_isNewTop;
|
||
|
} VALUES[] = {
|
||
|
{2, 1000000, VA, 0, 1000000, false},
|
||
|
{2, 1000000, VB, 3, 1000000, false},
|
||
|
{2, 1000000, VC, 0, 4000, false},
|
||
|
{2, 1000001, VB, 0, 3999, true},
|
||
|
{1, 9999998, VC, 4, 9999998, false},
|
||
|
{1, 9999999, VD, 0, 0, true},
|
||
|
{0, 4000, VE, 10, 4000, false}};
|
||
|
// clang-format on
|
||
|
|
||
|
static const int POP_ORDER[] = {5, 3, 2, 0, 1, 4, 6};
|
||
|
|
||
|
const int NUM_VALUES = sizeof VALUES / sizeof *VALUES;
|
||
|
int handles[NUM_VALUES];
|
||
|
|
||
|
CharQueue queue;
|
||
|
|
||
|
{
|
||
|
CharItem item;
|
||
|
ASSERT_FALSE(queue.popFront(&item));
|
||
|
}
|
||
|
|
||
|
for(int i = 0; i < NUM_VALUES; ++i)
|
||
|
{
|
||
|
const char* VAL = VALUES[i].m_value;
|
||
|
const int SECS = VALUES[i].m_secs;
|
||
|
const int NSECS = VALUES[i].m_nsecs;
|
||
|
absl::Time TIME =
|
||
|
absl::Time() + absl::Seconds(SECS) + absl::Nanoseconds(NSECS);
|
||
|
|
||
|
handles[i] = queue.add(TIME, VAL);
|
||
|
|
||
|
ASSERT_EQ(i + 1, queue.size());
|
||
|
ASSERT_TRUE(queue.isValid(handles[i]));
|
||
|
}
|
||
|
|
||
|
for(int i = 0; i < NUM_VALUES; ++i)
|
||
|
{
|
||
|
const int UPDSECS = VALUES[i].m_updsecs;
|
||
|
const bool EXPNEWTOP = VALUES[i].m_isNewTop;
|
||
|
const int UPDNSECS = VALUES[i].m_updnsecs;
|
||
|
absl::Time UPDTIME =
|
||
|
absl::Time() + absl::Seconds(UPDSECS) + absl::Nanoseconds(UPDNSECS);
|
||
|
|
||
|
bool isNewTop;
|
||
|
|
||
|
CharItem item;
|
||
|
ASSERT_TRUE(queue.isValid(handles[i])) << i;
|
||
|
ASSERT_TRUE(queue.update(handles[i], UPDTIME, &isNewTop)) << i;
|
||
|
EXPECT_EQ(EXPNEWTOP, isNewTop) << i;
|
||
|
ASSERT_TRUE(queue.isValid(handles[i])) << i;
|
||
|
}
|
||
|
|
||
|
for(int i = 0; i < NUM_VALUES; ++i)
|
||
|
{
|
||
|
const int I = POP_ORDER[i];
|
||
|
const char* EXPVAL = VALUES[I].m_value;
|
||
|
const int EXPSECS = VALUES[I].m_updsecs;
|
||
|
const int EXPNSECS = VALUES[I].m_updnsecs;
|
||
|
absl::Time EXPTIME =
|
||
|
absl::Time() + absl::Seconds(EXPSECS) + absl::Nanoseconds(EXPNSECS);
|
||
|
|
||
|
CharItem item;
|
||
|
ASSERT_TRUE(queue.isValid(handles[I]));
|
||
|
ASSERT_TRUE(queue.popFront(&item));
|
||
|
ASSERT_EQ(EXPTIME, item.time());
|
||
|
ASSERT_EQ(EXPVAL, item.value());
|
||
|
ASSERT_FALSE(queue.isValid(handles[I]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TEST(TimerQueue, ThreadSafety)
|
||
|
{
|
||
|
using Data = std::string;
|
||
|
using StringQueue = llarp::thread::TimerQueue< std::string >;
|
||
|
using StringItem = llarp::thread::TimerQueueItem< std::string >;
|
||
|
|
||
|
using Info = std::pair< int, std::vector< StringItem >* >;
|
||
|
|
||
|
static constexpr size_t NUM_THREADS = 10;
|
||
|
static constexpr size_t NUM_ITERATIONS = 1000;
|
||
|
static constexpr size_t NUM_REMOVE_ALL = NUM_ITERATIONS / 2;
|
||
|
|
||
|
Info info[NUM_THREADS];
|
||
|
std::thread threads[NUM_THREADS + 1];
|
||
|
std::vector< StringItem > items[NUM_THREADS];
|
||
|
|
||
|
absl::Barrier barrier(NUM_THREADS + 1);
|
||
|
|
||
|
StringQueue queue;
|
||
|
|
||
|
for(size_t i = 0; i < NUM_THREADS; ++i)
|
||
|
{
|
||
|
info[i].first = i;
|
||
|
info[i].second = &items[i];
|
||
|
threads[i] = std::thread(
|
||
|
[](Info* info, absl::Barrier* barrier, StringQueue* queue) {
|
||
|
const int THREAD_ID = info->first;
|
||
|
std::vector< StringItem >* vPtr = info->second;
|
||
|
|
||
|
// We stagger the removeAll steps among the threads.
|
||
|
const int STEP_REMOVE_ALL = THREAD_ID * NUM_REMOVE_ALL / NUM_THREADS;
|
||
|
|
||
|
std::ostringstream oss;
|
||
|
oss << THREAD_ID;
|
||
|
Data V(oss.str());
|
||
|
|
||
|
barrier->Block();
|
||
|
|
||
|
size_t newSize;
|
||
|
absl::Time newMinTime;
|
||
|
StringItem item;
|
||
|
for(size_t i = 0; i < NUM_ITERATIONS; ++i)
|
||
|
{
|
||
|
const absl::Time TIME =
|
||
|
absl::Time() + absl::Seconds((i * (i + 3)) % NUM_ITERATIONS);
|
||
|
int h = queue->add(TIME, V);
|
||
|
queue->update(h, TIME);
|
||
|
if(queue->popFront(&item, &newSize, &newMinTime))
|
||
|
{
|
||
|
vPtr->push_back(item);
|
||
|
}
|
||
|
h = queue->add(newMinTime, V);
|
||
|
queue->popLess(newMinTime, vPtr);
|
||
|
if(queue->remove(h, &item, &newSize, &newMinTime))
|
||
|
{
|
||
|
vPtr->push_back(item);
|
||
|
}
|
||
|
if(i % NUM_REMOVE_ALL == STEP_REMOVE_ALL)
|
||
|
{
|
||
|
queue->removeAll(vPtr);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
&info[i], &barrier, &queue);
|
||
|
}
|
||
|
|
||
|
threads[NUM_THREADS] = std::thread(
|
||
|
[](absl::Barrier* barrier, StringQueue* queue) {
|
||
|
barrier->Block();
|
||
|
for(size_t i = 0; i < NUM_ITERATIONS; ++i)
|
||
|
{
|
||
|
size_t size = queue->size();
|
||
|
ASSERT_GE(size, 0);
|
||
|
ASSERT_LE(size, NUM_THREADS);
|
||
|
}
|
||
|
},
|
||
|
&barrier, &queue);
|
||
|
|
||
|
size_t size = 0;
|
||
|
for(size_t i = 0; i < NUM_THREADS; ++i)
|
||
|
{
|
||
|
threads[i].join();
|
||
|
size += static_cast< int >(items[i].size());
|
||
|
}
|
||
|
threads[NUM_THREADS].join();
|
||
|
|
||
|
ASSERT_EQ(0, queue.size());
|
||
|
ASSERT_EQ(1000 * NUM_THREADS * 2, size);
|
||
|
}
|