Add AtomicIntrusiveLinkedList::reverseSweep()
authorLovro Puzar <lovro@fb.com>
Thu, 16 Feb 2017 11:33:51 +0000 (03:33 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Thu, 16 Feb 2017 11:34:39 +0000 (03:34 -0800)
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

folly/AtomicIntrusiveLinkedList.h
folly/AtomicLinkedList.h
folly/test/AtomicLinkedListTest.cpp

index ce5c52e6bb55e18327211587945f196c13ab2776..c0ee1b144274a8252f924355140e95a12b4c7612 100644 (file)
@@ -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
index 022a7123a18cb920eaf89869adde192b86b2548c..3ee27bbb1e3befbdf0806f93d1c487015c4367d4 100644 (file)
@@ -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)) {}
index 008b71002883fd1b338edb010513a42c175b11c7..303261ff06e92cf023d5a5b2f8e57fc7db8e1bf1 100644 (file)
@@ -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);