Commit 379e39cc authored by Yiding Jia's avatar Yiding Jia Committed by Facebook GitHub Bot

Reapply with more fixes: "[folly] Fix concurrency issues in ConcurrentSkipList.""

Summary:
Original diff D29248955 (https://github.com/facebook/folly/commit/6f4811eff3b7472347cc34c0ac9876ddd96287fc) had a bug that was causing a memory leak and was reverted.

This diff reapplies it but with an extra fix.

Reviewed By: yfeldblum

Differential Revision: D30082935

fbshipit-source-id: 0f119189fe631fc363dffe5c515a8bfa9a054cf6
parent 21f9cf7d
...@@ -97,7 +97,7 @@ class SkipListNode { ...@@ -97,7 +97,7 @@ class SkipListNode {
inline SkipListNode* skip(int layer) const { inline SkipListNode* skip(int layer) const {
DCHECK_LT(layer, height_); DCHECK_LT(layer, height_);
return skip_[layer].load(std::memory_order_consume); return skip_[layer].load(std::memory_order_acquire);
} }
// next valid node as in the linked list // next valid node as in the linked list
...@@ -155,7 +155,7 @@ class SkipListNode { ...@@ -155,7 +155,7 @@ class SkipListNode {
} }
} }
uint16_t getFlags() const { return flags_.load(std::memory_order_consume); } uint16_t getFlags() const { return flags_.load(std::memory_order_acquire); }
void setFlags(uint16_t flags) { void setFlags(uint16_t flags) {
flags_.store(flags, std::memory_order_release); flags_.store(flags, std::memory_order_release);
} }
...@@ -269,43 +269,41 @@ class NodeRecycler< ...@@ -269,43 +269,41 @@ class NodeRecycler<
dirty_.store(true, std::memory_order_relaxed); dirty_.store(true, std::memory_order_relaxed);
} }
int addRef() { return refs_.fetch_add(1, std::memory_order_relaxed); } int addRef() { return refs_.fetch_add(1, std::memory_order_acq_rel); }
int releaseRef() { int releaseRef() {
// We don't expect to clean the recycler immediately everytime it is OK // This if statement is purely an optimization. It's possible that this
// to do so. Here, it is possible that multiple accessors all release at // misses an opportunity to delete, but that's OK, we'll try again at
// the same time but nobody would clean the recycler here. If this // the next opportunity. It does not harm the thread safety. For this
// happens, the recycler will usually still get cleaned when // reason, we can use relaxed loads to make the decision.
// such a race doesn't happen. The worst case is the recycler will if (!dirty_.load(std::memory_order_relaxed) || refs() > 1) {
// eventually get deleted along with the skiplist. return refs_.fetch_add(-1, std::memory_order_acq_rel);
if (LIKELY(!dirty_.load(std::memory_order_relaxed) || refs() > 1)) {
return refs_.fetch_add(-1, std::memory_order_relaxed);
} }
std::unique_ptr<std::vector<NodeType*>> newNodes; std::unique_ptr<std::vector<NodeType*>> newNodes;
int ret;
{ {
// The order at which we lock, add, swap, is very important for
// correctness.
std::lock_guard<MicroSpinLock> g(lock_); std::lock_guard<MicroSpinLock> g(lock_);
if (nodes_.get() == nullptr || refs() > 1) { ret = refs_.fetch_add(-1, std::memory_order_acq_rel);
return refs_.fetch_add(-1, std::memory_order_relaxed); if (ret == 1) {
// When releasing the last reference, it is safe to remove all the
// current nodes in the recycler, as we already acquired the lock here
// so no more new nodes can be added, even though new accessors may be
// added after this.
newNodes.swap(nodes_);
dirty_.store(false, std::memory_order_relaxed);
} }
// once refs_ reaches 1 and there is no other accessor, it is safe to
// remove all the current nodes in the recycler, as we already acquired
// the lock here so no more new nodes can be added, even though new
// accessors may be added after that.
newNodes.swap(nodes_);
dirty_.store(false, std::memory_order_relaxed);
} }
// TODO(xliu) should we spawn a thread to do this when there are large // TODO(xliu) should we spawn a thread to do this when there are large
// number of nodes in the recycler? // number of nodes in the recycler?
for (auto& node : *newNodes) { if (newNodes) {
NodeType::destroy(alloc_, node); for (auto& node : *newNodes) {
NodeType::destroy(alloc_, node);
}
} }
return ret;
// decrease the ref count at the very end, to minimize the
// chance of other threads acquiring lock_ to clear the deleted
// nodes again.
return refs_.fetch_add(-1, std::memory_order_relaxed);
} }
NodeAlloc& alloc() { return alloc_; } NodeAlloc& alloc() { return alloc_; }
......
...@@ -250,7 +250,7 @@ class ConcurrentSkipList { ...@@ -250,7 +250,7 @@ class ConcurrentSkipList {
return foundLayer; return foundLayer;
} }
int height() const { return head_.load(std::memory_order_consume)->height(); } int height() const { return head_.load(std::memory_order_acquire)->height(); }
int maxLayer() const { return height() - 1; } int maxLayer() const { return height() - 1; }
...@@ -401,12 +401,12 @@ class ConcurrentSkipList { ...@@ -401,12 +401,12 @@ class ConcurrentSkipList {
} }
const value_type* first() const { const value_type* first() const {
auto node = head_.load(std::memory_order_consume)->skip(0); auto node = head_.load(std::memory_order_acquire)->skip(0);
return node ? &node->data() : nullptr; return node ? &node->data() : nullptr;
} }
const value_type* last() const { const value_type* last() const {
NodeType* pred = head_.load(std::memory_order_consume); NodeType* pred = head_.load(std::memory_order_acquire);
NodeType* node = nullptr; NodeType* node = nullptr;
for (int layer = maxLayer(); layer >= 0; --layer) { for (int layer = maxLayer(); layer >= 0; --layer) {
do { do {
...@@ -434,7 +434,7 @@ class ConcurrentSkipList { ...@@ -434,7 +434,7 @@ class ConcurrentSkipList {
int* max_layer) const { int* max_layer) const {
*max_layer = maxLayer(); *max_layer = maxLayer();
return findInsertionPoint( return findInsertionPoint(
head_.load(std::memory_order_consume), *max_layer, data, preds, succs); head_.load(std::memory_order_acquire), *max_layer, data, preds, succs);
} }
// Find node for access. Returns a paired values: // Find node for access. Returns a paired values:
...@@ -450,7 +450,7 @@ class ConcurrentSkipList { ...@@ -450,7 +450,7 @@ class ConcurrentSkipList {
// results, this is slightly faster than findNodeRightDown for better // results, this is slightly faster than findNodeRightDown for better
// localality on the skipping pointers. // localality on the skipping pointers.
std::pair<NodeType*, int> findNodeDownRight(const value_type& data) const { std::pair<NodeType*, int> findNodeDownRight(const value_type& data) const {
NodeType* pred = head_.load(std::memory_order_consume); NodeType* pred = head_.load(std::memory_order_acquire);
int ht = pred->height(); int ht = pred->height();
NodeType* node = nullptr; NodeType* node = nullptr;
...@@ -478,7 +478,7 @@ class ConcurrentSkipList { ...@@ -478,7 +478,7 @@ class ConcurrentSkipList {
// find node by first stepping right then stepping down. // find node by first stepping right then stepping down.
// We still keep this for reference purposes. // We still keep this for reference purposes.
std::pair<NodeType*, int> findNodeRightDown(const value_type& data) const { std::pair<NodeType*, int> findNodeRightDown(const value_type& data) const {
NodeType* pred = head_.load(std::memory_order_consume); NodeType* pred = head_.load(std::memory_order_acquire);
NodeType* node = nullptr; NodeType* node = nullptr;
auto top = maxLayer(); auto top = maxLayer();
int found = 0; int found = 0;
...@@ -502,7 +502,7 @@ class ConcurrentSkipList { ...@@ -502,7 +502,7 @@ class ConcurrentSkipList {
} }
void growHeight(int height) { void growHeight(int height) {
NodeType* oldHead = head_.load(std::memory_order_consume); NodeType* oldHead = head_.load(std::memory_order_acquire);
if (oldHead->height() >= height) { // someone else already did this if (oldHead->height() >= height) { // someone else already did this
return; return;
} }
...@@ -598,7 +598,7 @@ class ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>::Accessor { ...@@ -598,7 +598,7 @@ class ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>::Accessor {
size_type count(const key_type& data) const { return contains(data); } size_type count(const key_type& data) const { return contains(data); }
iterator begin() const { iterator begin() const {
NodeType* head = sl_->head_.load(std::memory_order_consume); NodeType* head = sl_->head_.load(std::memory_order_acquire);
return iterator(head->next()); return iterator(head->next());
} }
iterator end() const { return iterator(nullptr); } iterator end() const { return iterator(nullptr); }
...@@ -814,7 +814,7 @@ class ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>::Skipper { ...@@ -814,7 +814,7 @@ class ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>::Skipper {
private: private:
NodeType* head() const { NodeType* head() const {
return accessor_.skiplist()->head_.load(std::memory_order_consume); return accessor_.skiplist()->head_.load(std::memory_order_acquire);
} }
Accessor accessor_; Accessor accessor_;
......
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