Commit f54a0bdb authored by Lovro Puzar's avatar Lovro Puzar Committed by Facebook Github Bot

Add AtomicIntrusiveLinkedList::reverseSweep()

Summary: In D4558451 I want to pull elements off an atomic list onto a thread-local (single-consumer) vector and pop to get elements in insertion order.  A sweep that provides elements in LIFO order will avoid a redundant pair of list reversals.

Reviewed By: nbronson

Differential Revision: D4564816

fbshipit-source-id: 38cf50418e6afe0be3eec96ce85d571c65f06d7e
parent b49a44c4
......@@ -18,6 +18,7 @@
#include <atomic>
#include <cassert>
#include <utility>
namespace folly {
......@@ -104,15 +105,31 @@ class AtomicIntrusiveLinkedList {
void sweep(F&& func) {
while (auto head = head_.exchange(nullptr)) {
auto rhead = reverse(head);
while (rhead != nullptr) {
auto t = rhead;
rhead = next(t);
next(t) = nullptr;
func(t);
}
unlinkAll(rhead, std::forward<F>(func));
}
}
/**
* Similar to sweep() but calls func() on elements in LIFO order.
*
* func() is called for all elements in the list at the moment
* reverseSweep() is called. Unlike sweep() it does not loop to ensure the
* list is empty at some point after the last invocation. This way callers
* can reason about the ordering: elements inserted since the last call to
* reverseSweep() will be provided in LIFO order.
*
* Example: if elements are inserted in the order 1-2-3, the callback is
* invoked 3-2-1. If the callback moves elements onto a stack, popping off
* the stack will produce the original insertion order 1-2-3.
*/
template <typename F>
void reverseSweep(F&& func) {
// We don't loop like sweep() does because the overall order of callbacks
// would be strand-wise LIFO which is meaningless to callers.
auto head = head_.exchange(nullptr);
unlinkAll(head, std::forward<F>(func));
}
private:
std::atomic<T*> head_{nullptr};
......@@ -132,6 +149,18 @@ class AtomicIntrusiveLinkedList {
}
return rhead;
}
/* Unlinks all elements in the linked list fragment pointed to by `head',
* calling func() on every element */
template <typename F>
void unlinkAll(T* head, F&& func) {
while (head != nullptr) {
auto t = head;
head = next(t);
next(t) = nullptr;
func(t);
}
}
};
} // namespace folly
......@@ -73,6 +73,28 @@ class AtomicLinkedList {
});
}
/**
* Similar to sweep() but calls func() on elements in LIFO order.
*
* func() is called for all elements in the list at the moment
* reverseSweep() is called. Unlike sweep() it does not loop to ensure the
* list is empty at some point after the last invocation. This way callers
* can reason about the ordering: elements inserted since the last call to
* reverseSweep() will be provided in LIFO order.
*
* Example: if elements are inserted in the order 1-2-3, the callback is
* invoked 3-2-1. If the callback moves elements onto a stack, popping off
* the stack will produce the original insertion order 1-2-3.
*/
template <typename F>
void reverseSweep(F&& func) {
list_.reverseSweep([&](Wrapper* wrapperPtr) mutable {
std::unique_ptr<Wrapper> wrapper(wrapperPtr);
func(std::move(wrapper->data));
});
}
private:
struct Wrapper {
explicit Wrapper(T&& t) : data(std::move(t)) {}
......
......@@ -74,6 +74,23 @@ TEST(AtomicIntrusiveLinkedList, Basic) {
TestIntrusiveObject::List movedList = std::move(list);
}
TEST(AtomicIntrusiveLinkedList, ReverseSweep) {
TestIntrusiveObject a(1), b(2), c(3);
TestIntrusiveObject::List list;
list.insertHead(&a);
list.insertHead(&b);
list.insertHead(&c);
size_t next_expected_id = 3;
list.reverseSweep([&](TestIntrusiveObject* obj) {
EXPECT_EQ(next_expected_id--, obj->id());
});
EXPECT_TRUE(list.empty());
// Test that we can still insert
list.insertHead(&a);
EXPECT_FALSE(list.empty());
list.reverseSweep([](TestIntrusiveObject*) {});
}
TEST(AtomicIntrusiveLinkedList, Move) {
TestIntrusiveObject a(1), b(2);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment