Compare commits
2 Commits
marka-sing
...
fanf-trans
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cf07c8481 | ||
|
|
70a0bf9090 |
7
CHANGES
7
CHANGES
@@ -1,3 +1,10 @@
|
||||
6104. [port] Detect compiler support for transparent unions, to
|
||||
improve the type safety of polymorphic functions.
|
||||
[GL !7472]
|
||||
|
||||
6103. [func] Support for simple lock-free singly-linked lists.
|
||||
[GL !7470]
|
||||
|
||||
6102. [cleanup] Several nugatory headers have been removed from libisc.
|
||||
[GL !7464]
|
||||
|
||||
|
||||
@@ -649,7 +649,7 @@ More macros are provided for iterating the list:
|
||||
|
||||
isc_foo_t *foo;
|
||||
for (foo = ISC_LIST_HEAD(foolist);
|
||||
foo != NULL;
|
||||
foo != NULL;
|
||||
foo = ISC_LIST_NEXT(foo, link))
|
||||
{
|
||||
/* do things */
|
||||
@@ -662,6 +662,44 @@ Items can be removed from the list using `ISC_LIST_UNLINK`:
|
||||
|
||||
ISC_LIST_UNLINK(foolist, foo, link);
|
||||
|
||||
##### Atomic lists
|
||||
|
||||
In `<isc/list.h>`, there are also similar macros for atomic
|
||||
singly-linked lists.
|
||||
|
||||
In general, atomic linked lists have a number of pitfalls that these
|
||||
macros avoid by having a restricted API. Firstly, it is difficult to
|
||||
implement atomic doubly-linked lists without at least a double-width
|
||||
compare-exchange, which is not a widely supported primitive, so this
|
||||
list is singly-linked. Secondly, When individual elements can be
|
||||
removed from a list (whether doubly-linked or singly-linked), we run
|
||||
into the ABA problem, where removes and (re-)inserts race and as a
|
||||
result the list gets in a tangle. Safe support for removing individual
|
||||
elements requires some kind of garbage collection, or extending list
|
||||
pointers with generation counters. Instead the ALIST macros only
|
||||
provide a function for emptying the entire list in one go,
|
||||
`ISC_ALIST_DRAIN()`. This only requires setting the `head` pointer to
|
||||
`NULL`, which cannot cause `ISC_ALIST_PREPEND()` to go wrong.
|
||||
|
||||
The `ISC_ALIST` and `ISC_ALINK` macros are used the same way as the
|
||||
non-atomic doubly-linked lists described above, except that not all of
|
||||
the `ISC_LIST` macros have `ISC_ALIST` equivalents.
|
||||
|
||||
As well as read-only iteration as described above, there is an idiom
|
||||
for emptying a list and processing its contents:
|
||||
|
||||
isc_foo_t *foo = ISC_ALIST_DRAIN(foolist);
|
||||
while (foo != NULL) {
|
||||
isc_foo_t *next = ISC_ALIST_TAKE(foo, link);
|
||||
/* do things */
|
||||
/* maybe ISC_ALIST_PREPEND(foolist, foo, link) */
|
||||
foo = next;
|
||||
}
|
||||
|
||||
There is also an `ISC_ALIST_ADD()` macro, which is a convenience
|
||||
wrapper for prepending an element to a list if it has not already been
|
||||
added.
|
||||
|
||||
#### <a name="names"></a>Names
|
||||
|
||||
The `dns_name` API has facilities for processing DNS names and labels,
|
||||
|
||||
@@ -42,9 +42,6 @@
|
||||
#define atomic_compare_exchange_strong_relaxed(o, e, d) \
|
||||
atomic_compare_exchange_strong_explicit( \
|
||||
(o), (e), (d), memory_order_relaxed, memory_order_relaxed)
|
||||
#define atomic_compare_exchange_strong_acq_rel(o, e, d) \
|
||||
atomic_compare_exchange_strong_explicit( \
|
||||
(o), (e), (d), memory_order_acq_rel, memory_order_acquire)
|
||||
|
||||
/* Acquire-Release Memory Ordering */
|
||||
|
||||
@@ -73,3 +70,6 @@
|
||||
/* compare/exchange that MUST succeed */
|
||||
#define atomic_compare_exchange_enforced(o, e, d) \
|
||||
RUNTIME_CHECK(atomic_compare_exchange_strong((o), (e), (d)))
|
||||
|
||||
/* more comfortable atomic pointer declarations */
|
||||
#define atomic_ptr(type) _Atomic(type *)
|
||||
|
||||
@@ -80,3 +80,23 @@
|
||||
#define ISC_ATTR_MALLOC_DEALLOCATOR(deallocator)
|
||||
#define ISC_ATTR_MALLOC_DEALLOCATOR_IDX(deallocator, idx)
|
||||
#endif /* HAVE_FUNC_ATTRIBUTE_MALLOC */
|
||||
|
||||
/*
|
||||
* When we are using an inheritance pattern (where several structures
|
||||
* share a common prefix) we would like it to be easy to pass any of
|
||||
* these subtype structures to functions that only operate on the
|
||||
* common prefix, and we would also like it to be type safe.
|
||||
*
|
||||
* GNU C transparent unions have quiet conversions like void pointers,
|
||||
* but only for a specific set of types. So they make it easy to pass
|
||||
* just the known subtypes to a function that operates on their common
|
||||
* prefix.
|
||||
*
|
||||
* On compilers without transparent unions we can use void pointers
|
||||
* instead; we still benefit from type checking during development.
|
||||
*/
|
||||
#ifdef __has_attribute
|
||||
#if __has_attribute(__transparent_union__)
|
||||
#define ISC_ATTR_TRANSPARENT __attribute__((__transparent_union__))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <isc/assertions.h>
|
||||
#include <isc/atomic.h>
|
||||
#include <isc/pause.h>
|
||||
|
||||
#define ISC_LINK_TOMBSTONE(type) ((type *)-1)
|
||||
|
||||
@@ -227,3 +229,102 @@
|
||||
INSIST(ISC_LIST_EMPTY(dest)); \
|
||||
ISC_LIST_MOVEUNSAFE(dest, src); \
|
||||
}
|
||||
|
||||
/*
|
||||
* An atomic list has an atomic pointer at its head so that multiple
|
||||
* threads can add elements without locking (though they may need to
|
||||
* wait briefly when there is contention). We sidestep the ABA problem
|
||||
* which occurs when elements can be removed individually: instead the
|
||||
* whole list must be drained. This is also why operations on `next`
|
||||
* pointers are not atomic.
|
||||
*
|
||||
* The pointers are wrapped in structs so that their types are
|
||||
* distinct, and to match the ISC_LIST macros.
|
||||
*
|
||||
* See doc/dev/dev.md for examples.
|
||||
*/
|
||||
|
||||
#define ISC_ALIST(type) \
|
||||
struct { \
|
||||
atomic_ptr(type) head; \
|
||||
}
|
||||
#define ISC_ALIST_INITIALIZER \
|
||||
{ \
|
||||
.head = NULL, \
|
||||
}
|
||||
#define ISC_ALIST_INIT(list) \
|
||||
do { \
|
||||
(list).head = NULL; \
|
||||
} while (0)
|
||||
|
||||
#define ISC_ALINK(type) \
|
||||
struct { \
|
||||
type *next; \
|
||||
}
|
||||
#define ISC_ALINK_INITIALIZER_TYPE(type) \
|
||||
{ \
|
||||
.next = ISC_LINK_TOMBSTONE(type), \
|
||||
}
|
||||
#define ISC_ALINK_INIT_TYPE(elt, link, type) \
|
||||
do { \
|
||||
(elt)->link.next = ISC_LINK_TOMBSTONE(type); \
|
||||
} while (0)
|
||||
|
||||
#define ISC_ALINK_INITIALIZER ISC_ALINK_INITIALIZER_TYPE(void)
|
||||
#define ISC_ALINK_INIT(elt, link) ISC_ALINK_INIT_TYPE(elt, link, void)
|
||||
|
||||
#define ISC_ALIST_NEXT(elt, link) ((elt)->link.next)
|
||||
|
||||
#define ISC_ALINK_LINKED_TYPE(elt, link, type) \
|
||||
(ISC_ALIST_NEXT(elt, link) != ISC_LINK_TOMBSTONE(type))
|
||||
|
||||
#define ISC_ALINK_LINKED(elt, link) ISC_ALINK_LINKED_TYPE(elt, link, void)
|
||||
|
||||
#define ISC_ALIST_TAKE_TYPE(elt, link, type) \
|
||||
({ \
|
||||
type *__next = ISC_ALIST_NEXT(elt, link); \
|
||||
ISC_ALINK_INIT_TYPE(elt, link, type); \
|
||||
__next; \
|
||||
})
|
||||
#define ISC_ALIST_TAKE(elt, link) ISC_ALIST_TAKE_TYPE(elt, link, void)
|
||||
|
||||
/*
|
||||
* ATOMIC: for performance, this kind of retry loop should use a weak
|
||||
* CAS with relaxed ordering in the failure case; on success, release
|
||||
* ordering ensures that writing the element contents happens before
|
||||
* reading them, following the acquire in ISC_ALIST_HEAD() and _DRAIN().
|
||||
*/
|
||||
#define __ISC_ALIST_PREPENDUNSAFE(list, elt, link) \
|
||||
do { \
|
||||
(elt)->link.next = atomic_load_relaxed(&(list).head); \
|
||||
while (!atomic_compare_exchange_weak_explicit( \
|
||||
&(list).head, &(elt)->link.next, elt, \
|
||||
memory_order_release, memory_order_relaxed)) \
|
||||
{ \
|
||||
isc_pause(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define ISC_ALIST_PREPEND(list, elt, link) \
|
||||
do { \
|
||||
ISC_LINK_INSIST(!ISC_ALINK_LINKED(elt, link)); \
|
||||
__ISC_ALIST_PREPENDUNSAFE(list, elt, link); \
|
||||
} while (0)
|
||||
|
||||
#define ISC_ALIST_ADD(list, elt, link) \
|
||||
do { \
|
||||
if (!ISC_ALINK_LINKED(elt, link)) { \
|
||||
__ISC_ALIST_PREPENDUNSAFE(list, elt, link); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* ATOMIC: acquire ordering pairs with __ISC_ALIST_PREPENDUNSAFE()
|
||||
*/
|
||||
#define ISC_ALIST_HEAD(list) atomic_load_acquire(&(list).head)
|
||||
|
||||
/*
|
||||
* ATOMIC: acquire ordering pairs with __ISC_ALIST_PREPENDUNSAFE()
|
||||
*/
|
||||
#define ISC_ALIST_DRAIN(list) \
|
||||
atomic_exchange_explicit(&(list).head, NULL, memory_order_acquire)
|
||||
|
||||
Reference in New Issue
Block a user