Add clear method to SkipList

Reviewers: teon.banek

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2385
This commit is contained in:
Matej Ferencevic 2019-09-13 13:26:59 +02:00
parent 55d8b98aeb
commit 9652f8e265
2 changed files with 117 additions and 24 deletions

View File

@ -187,24 +187,12 @@ class SkipListGc final {
SkipListGc(SkipListGc &&other) = delete;
SkipListGc &operator=(SkipListGc &&other) = delete;
~SkipListGc() {
Block *head = head_.load(std::memory_order_acquire);
while (head != nullptr) {
Allocator<Block> block_allocator(memory_);
Block *prev = head->prev.load(std::memory_order_acquire);
head->~Block();
block_allocator.deallocate(head, 1);
head = prev;
}
std::optional<TDeleted> item;
while ((item = deleted_.Pop())) {
size_t bytes = SkipListNodeSize(*item->second);
item->second->~TNode();
memory_->Deallocate(item->second, bytes);
}
}
~SkipListGc() { Clear(); }
uint64_t AllocateId() {
#ifndef NDEBUG
alive_accessors_.fetch_add(1, std::memory_order_acq_rel);
#endif
return accessor_id_.fetch_add(1, std::memory_order_acq_rel);
}
@ -238,6 +226,9 @@ class SkipListGc final {
break;
}
}
#ifndef NDEBUG
alive_accessors_.fetch_add(-1, std::memory_order_acq_rel);
#endif
}
void Collect(TNode *node) {
@ -311,6 +302,36 @@ class SkipListGc final {
MemoryResource *GetMemoryResource() const { return memory_; }
void Clear() {
#ifndef NDEBUG
CHECK(alive_accessors_ == 0)
<< "The SkipList can't be cleared while there are existing accessors!";
#endif
// Delete all allocated blocks.
Block *head = head_.load(std::memory_order_acquire);
while (head != nullptr) {
Allocator<Block> block_allocator(memory_);
Block *prev = head->prev.load(std::memory_order_acquire);
head->~Block();
block_allocator.deallocate(head, 1);
head = prev;
}
// Delete all items that have to be garbage collected.
std::optional<TDeleted> item;
while ((item = deleted_.Pop())) {
size_t bytes = SkipListNodeSize(*item->second);
item->second->~TNode();
memory_->Deallocate(item->second, bytes);
}
// Reset all variables.
accessor_id_ = 0;
head_ = nullptr;
tail_ = nullptr;
last_id_ = 0;
}
private:
MemoryResource *memory_;
SpinLock lock_;
@ -319,6 +340,9 @@ class SkipListGc final {
std::atomic<Block *> tail_{nullptr};
uint64_t last_id_{0};
TStack deleted_;
#ifndef NDEBUG
std::atomic<uint64_t> alive_accessors_{0};
#endif
};
/// Concurrent skip list. It is mostly lock-free and fine-grained locking is
@ -824,14 +848,9 @@ class SkipList final {
~SkipList() {
if (head_ != nullptr) {
TNode *curr = head_->nexts[0].load(std::memory_order_acquire);
while (curr != nullptr) {
TNode *succ = curr->nexts[0].load(std::memory_order_acquire);
size_t bytes = SkipListNodeSize(*curr);
curr->~TNode();
GetMemoryResource()->Deallocate(curr, bytes);
curr = succ;
}
// Remove all items from the list.
clear();
// We need to free the `head_` node manually because we didn't call its
// constructor (see the note in the `SkipList` constructor). We mustn't
// call the `TObj` destructor because we didn't call its constructor.
@ -851,6 +870,25 @@ class SkipList final {
MemoryResource *GetMemoryResource() const { return gc_.GetMemoryResource(); }
/// This function removes all elements from the list.
/// NOTE: The function *isn't* thread-safe. It must be called while there are
/// no more active accessors using the list.
void clear() {
TNode *curr = head_->nexts[0].load(std::memory_order_acquire);
while (curr != nullptr) {
TNode *succ = curr->nexts[0].load(std::memory_order_acquire);
size_t bytes = SkipListNodeSize(*curr);
curr->~TNode();
GetMemoryResource()->Deallocate(curr, bytes);
curr = succ;
}
for (int layer = 0; layer < kSkipListMaxHeight; ++layer) {
head_->nexts[layer] = nullptr;
}
size_ = 0;
gc_.Clear();
}
private:
template <typename TKey>
int find_node(const TKey &key, TNode *preds[], TNode *succs[]) const {

View File

@ -346,6 +346,61 @@ TEST(SkipList, Move) {
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST(SkipList, Clear) {
utils::SkipList<int64_t> list;
{
auto acc = list.access();
for (int64_t i = -1000; i <= 1000; ++i) {
acc.insert(i);
}
ASSERT_EQ(acc.size(), 2001);
}
{
auto acc = list.access();
int64_t val = -1000;
for (auto &item : acc) {
ASSERT_EQ(item, val);
++val;
}
ASSERT_EQ(val, 1001);
ASSERT_EQ(acc.size(), 2001);
}
list.clear();
{
auto acc = list.access();
uint64_t count = 0;
for (auto it = acc.begin(); it != acc.end(); ++it) {
++count;
}
ASSERT_EQ(count, 0);
ASSERT_EQ(acc.size(), 0);
}
{
auto acc = list.access();
for (int64_t i = -1000; i <= 1000; ++i) {
acc.insert(i);
}
ASSERT_EQ(acc.size(), 2001);
}
{
auto acc = list.access();
int64_t val = -1000;
for (auto &item : acc) {
ASSERT_EQ(item, val);
++val;
}
ASSERT_EQ(val, 1001);
ASSERT_EQ(acc.size(), 2001);
}
}
struct Inception {
uint64_t id;
utils::SkipList<uint64_t> data;