Source/PLCrashAsyncLinkedList.hpp (212 lines of code) (raw):
/*
* Author: Landon Fuller <landonf@plausible.coop>
*
* Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PLCRASH_ASYNC_LINKED_LIST_H
#define PLCRASH_ASYNC_LINKED_LIST_H 1
#include "PLCrashAsync.h"
#include "PLCrashMacros.h"
#include "PLCrashCompatConstants.h"
#include <atomic>
PLCR_CPP_BEGIN_NS
namespace async {
/**
* @internal
* @ingroup plcrash_async
*
* An async-safe linked list implementation.
*
* Maintains a linked list with support for async-safe iteration. Writing may occur concurrently with
* async-safe reading, but is not async-safe.
*
* Atomic compare and swap is used to ensure a consistent view of the list for readers. To simplify implementation, a
* write mutex is held for all updates; the implementation is not designed for efficiency in the face of contention
* between readers and writers, and it's assumed that no contention should realistically occur.
*
* @tparam V The list element type.
*/
template <typename V>
class async_list {
public:
/**
* Async-safe image list element.
*/
class node {
public:
friend class async_list<V>;
// Custom new/delete that do not rely on the stdlib
void *operator new (size_t size) {
void *ptr = malloc(size);
PLCF_ASSERT(ptr != NULL);
return ptr;
}
void operator delete (void *ptr) {
free(ptr);
}
/**
* Return the list item value.
*/
V value (void) {
return _value;
}
private:
/**
* Construct a new node with @a value.
*
* @param value The value for this node.
*/
node (V value) {
_value = value;
_prev = NULL;
_next = NULL;
}
/**
* Reset a node for re-use.
*
* @param value The new value for this node.
*/
void reset (V value) {
_value = value;
_prev = NULL;
_next = NULL;
}
/** The list entry value. */
V _value;
/** The previous item in the list, or NULL */
node *_prev;
/** The next image in the list, or NULL. */
std::atomic<node *> _next;
};
async_list (void);
~async_list (void);
void nasync_prepend (V value);
void nasync_append (V value);
void nasync_remove_first_value (V value);
void nasync_remove_node (node *deleted_node);
void set_reading (bool enable);
node *next (node *current);
// Custom new/delete that do not rely on the stdlib
void *operator new (size_t size) {
void *ptr = malloc(size);
PLCF_ASSERT(ptr != NULL);
return ptr;
}
void operator delete (void *ptr) { free(ptr); }
/**
* Sanity check list validity. Intended to be used from the unit tests; will fire
* an assertion if the list structure is invalid.
*
* This method acquires no locks and is not thread-safe. It should not be used
* when making concurrent changes to the list, or otherwise outside of a test environment.
*/
inline void assert_list_valid (void) {
/* Verify list linkage in both directions. */
node *prev = NULL;
for (node *cur = _head; cur != NULL; cur = cur->_next) {
PLCF_ASSERT(cur->_prev == prev);
prev = cur;
}
PLCF_ASSERT(prev == _tail);
}
private:
void free_list (node *next);
/** The lock used by writers. No lock is required for readers. */
PLCR_COMPAT_LOCK_TYPE _write_lock;
/** The head of the list, or NULL if the list is empty. Must only be used to iterate or delete entries. */
std::atomic<node *> _head;
/** The tail of the list, or NULL if the list is empty. Must only be used to append new entries. */
node *_tail;
/** The list reference count. No nodes will be deallocated while the count is greater than 0. If the count
* reaches 0, all nodes in the free list will be deallocated. */
std::atomic_int32_t _refcount;
/** The node free list. */
node *_free;
};
/** Construct a new, empty linked list */
template <typename V> async_list<V>::async_list (void) {
_head = NULL;
_tail = NULL;
_free = NULL;
_refcount = 0;
_write_lock = PLCR_COMPAT_LOCK_INIT;
}
template <typename V> async_list<V>::~async_list (void) {
/* Free all nodes */
if (_head != NULL)
free_list(_head);
if (_free != NULL)
free_list(_free);
}
/**
* Prepend a new entry value to the list
*
* @param value The value to be prepended.
*
* @warning This method is not async safe.
*/
template <typename V> void async_list<V>::nasync_prepend (V value) {
/* Lock the list from other writers. */
PLCR_COMPAT_LOCK_LOCK(&_write_lock); {
/* Construct the new entry, or recycle an existing one. */
node *new_node;
if (_free != NULL) {
/* Fetch a node from the free list */
new_node = _free;
new_node->reset(value);
/* Update the free list */
_free = _free->_next;
} else {
new_node = new node(value);
}
/* Issue a memory barrier to ensure a consistent view of the value. */
std::atomic_thread_fence(std::memory_order_seq_cst);
/* If this is the first entry, initialize the list. */
if (_tail == NULL) {
/* Update the list tail. This need not be done atomically, as tail is never accessed by a lockless reader. */
_tail = new_node;
/* Atomically update the list head; this will be iterated upon by lockless readers. */
node *expected = NULL;
if (!_head.compare_exchange_strong(expected, new_node)) {
/* Should never occur */
PLCF_DEBUG("An async image head was set with tail == NULL despite holding lock.");
}
}
/* Otherwise, prepend to the head of the list */
else {
new_node->_next = (node *)_head;
new_node->_prev = NULL;
/* Update the prev pointers. This is never accessed without a lock, so no additional synchronization
* is required here. */
((node *)_head)->_prev = new_node;
/* Issue a memory barrier to ensure a consistent view of the nodes. */
std::atomic_thread_fence(std::memory_order_seq_cst);
/* Atomically slot the new record into place; this may be iterated on by a lockless reader. */
node *expected = new_node->_next;
if (!_head.compare_exchange_strong(expected, new_node)) {
PLCF_DEBUG("Failed to prepend to image list despite holding lock");
}
}
} PLCR_COMPAT_LOCK_UNLOCK(&_write_lock);
}
/**
* Append a new entry value to the list
*
* @param value The value to be appended.
*
* @warning This method is not async safe.
*/
template <typename V> void async_list<V>::nasync_append (V value) {
/* Lock the list from other writers. */
PLCR_COMPAT_LOCK_LOCK(&_write_lock); {
/* Construct the new entry, or recycle an existing one. */
node *new_node;
if (_free != NULL) {
/* Fetch a node from the free list */
new_node = _free;
new_node->reset(value);
/* Update the free list */
_free = _free->_next;
} else {
new_node = new node(value);
}
/* Issue a memory barrier to ensure a consistent view of the value. */
std::atomic_thread_fence(std::memory_order_seq_cst);
/* If this is the first entry, initialize the list. */
if (_tail == NULL) {
/* Update the list tail. This need not be done atomically, as tail is never accessed by a lockless reader. */
_tail = new_node;
/* Atomically update the list head; this will be iterated upon by lockless readers. */
node *expected = NULL;
if (!_head.compare_exchange_strong(expected, new_node)) {
/* Should never occur */
PLCF_DEBUG("An async image head was set with tail == NULL despite holding lock.");
}
}
/* Otherwise, append to the end of the list */
else {
/* Atomically slot the new record into place; this may be iterated on by a lockless reader. */
node *expected = NULL;
if (!_tail->_next.compare_exchange_strong(expected, new_node)) {
PLCF_DEBUG("Failed to append to image list despite holding lock");
}
/* Update the prev and tail pointers. This is never accessed without a lock, so no additional barrier
* is required here. */
new_node->_prev = _tail;
_tail = new_node;
}
} PLCR_COMPAT_LOCK_UNLOCK(&_write_lock);
}
/**
* Find and remove the first entry node with @a value. Direct '==' equality checking
* is performed.
*
* @param value The value to search for.
*
* @warning This method is not async safe.
*/
template <typename V> void async_list<V>::nasync_remove_first_value (V value) {
set_reading(true);
node *n = NULL;
while ((n = next(n)) != NULL) {
if (n->value() == value) {
nasync_remove_node(n);
break;
}
}
set_reading(false);
}
/**
* Remove a specific entry node from the list.
*
* @param deleted_node The node to be removed.
*
* @warning This method is not async safe.
*/
template <typename V> void async_list<V>::nasync_remove_node (node *deleted_node) {
/* Lock the list from other writers. */
PLCR_COMPAT_LOCK_LOCK(&_write_lock); {
/* Find the record. */
node *item = _head;
while (item != NULL) {
if (item == deleted_node)
break;
item = item->_next;
}
/* If not found, nothing to do */
if (item == NULL) {
PLCR_COMPAT_LOCK_UNLOCK(&_write_lock);
return;
}
/*
* Atomically make the item unreachable by readers.
*
* This serves as a synchronization point -- after the CAS, the item is no longer reachable via the list.
*/
if (item == _head) {
if (!_head.compare_exchange_strong(item, item->_next)) {
PLCF_DEBUG("Failed to remove image list head despite holding lock");
}
} else {
/* There MUST be a non-NULL prev pointer, as this is not HEAD. */
if (!item->_prev->_next.compare_exchange_strong(item, item->_next)) {
PLCF_DEBUG("Failed to remove image list item despite holding lock");
}
}
/* Now that the item is unreachable, update the prev/tail pointers. These are never accessed without a lock,
* and need not be updated atomically. */
if (item->_next != NULL) {
/* Item is not the tail (otherwise next would be NULL), so simply update the next item's prev pointer. */
((node *)item->_next)->_prev = item->_prev;
} else {
/* Item is the tail (next is NULL). Simply update the tail record. */
_tail = item->_prev;
}
/* If a reader is active, place the node on the free list. The item is unreachable here when readers
* aren't active, so if we have a 0 refcount, we can safely delete the item, and be sure that no
* reader holds a reference to it. */
if (_refcount > 0) {
item->_prev = NULL;
item->_next = _free;
if (_free != NULL)
_free->_prev = item;
_free = item;
} else {
delete item;
}
} PLCR_COMPAT_LOCK_UNLOCK(&_write_lock);
}
/**
* Retain or release the list for reading. This method is async-safe.
*
* This must be issued prior to attempting to iterate the list, and must called again once reads have completed.
*
* @param enable If true, the list will be retained. If false, released.
*/
template <typename V> void async_list<V>::set_reading (bool enable) {
if (enable) {
/* Increment and issue a barrier. Once issued, no items will be deallocated while a reference is held. */
_refcount++;
} else {
/* Increment and issue a barrier. Once issued, items may again be deallocated. */
_refcount--;
}
}
/**
* Iterate over list nodes. This method is async-safe. If no additional nodes are available, will return NULL.
*
* The list must be marked for reading before iteration is performed.
*
* @param current The current list node, or NULL to start iteration.
*/
template <typename V> typename async_list<V>::node *async_list<V>::next (node *current) {
PLCF_ASSERT(_refcount > 0);
if (current != NULL)
return current->_next;
return _head;
}
/*
* @internal
*
* Free all items in @a next list.
*
* @param next The head of the list to deallocate.
*
* @warning This method is not async-safe, and must only be called with the write lock held, or
* from the deconstructor.
*/
template <typename V> void async_list<V>::free_list (node *next) {
while (next != NULL) {
/* Save the current pointer and fetch the next pointer. */
node *cur = next;
next = cur->_next;
/* Deallocate the current item. */
delete cur;
}
}
PLCR_CPP_END_NS
}
#endif /* PLCRASH_ASYNC_LINKED_LIST_H */