chg: dev: Convert slabtop and slabheader to use the cds list

This is the first MR in series that aims to reduce the node locking
by replacing the single-linked list of slabtop(s) and slabheader(s)
with CDS linked list.  This commit doesn't do anything else beyond
replacing .next and .down links with the cds_list_head.  The RCU
semantics will be added later.

Merge branch 'ondrej/use-rcu-list-for-slabtop' into 'main'

See merge request isc-projects/bind9!10944
This commit is contained in:
Ondřej Surý 2025-09-24 00:06:35 +02:00
commit 7443ff330c
5 changed files with 191 additions and 168 deletions

View file

@ -64,17 +64,20 @@ struct dns_slabheader_proof {
dns_rdatatype_t type;
};
#define DNS_SLABTOP_FOREACH(elt, first) \
for (dns_slabtop_t *elt = first, \
*elt##_next = (elt != NULL) ? elt->next : NULL; \
elt != NULL; \
elt = elt##_next, elt##_next = (elt != NULL) ? elt->next : NULL)
#define DNS_SLABTOP_FOREACH(pos, head) \
dns_slabtop_t *pos = NULL, *pos##_next = NULL; \
cds_list_for_each_entry_safe(pos, pos##_next, head, types_link)
#define DNS_SLABTOP_FOREACH_FROM(pos, head, first) \
dns_slabtop_t *pos = first, *pos##_next = NULL; \
cds_list_for_each_entry_safe_from(pos, pos##_next, head, types_link)
typedef struct dns_slabtop dns_slabtop_t;
struct dns_slabtop {
dns_slabtop_t *next;
dns_slabheader_t *header;
dns_typepair_t typepair;
struct cds_list_head types_link;
struct cds_list_head headers;
dns_typepair_t typepair;
/*% Used for SIEVE-LRU (cache) and changed_list (zone) */
ISC_LINK(struct dns_slabtop) link;
@ -114,10 +117,9 @@ struct dns_slabheader {
dns_slabtop_t *top;
/*%
* Points to the header for the next older version of
* this rdataset.
* Link to the other versions of this rdataset.
*/
struct dns_slabheader *down;
struct cds_list_head headers_link;
/*%
* The database node objects containing this rdataset, if any.

View file

@ -157,7 +157,8 @@ struct qpcnode {
isc_refcount_t references;
isc_refcount_t erefs;
dns_slabtop_t *data;
struct cds_list_head types_list;
struct cds_list_head *data;
/*%
* NOTE: The 'dirty' flag is protected by the node lock, so
@ -466,7 +467,17 @@ rdataset_size(dns_slabheader_t *header) {
static dns_slabheader_t *
first_header(dns_slabtop_t *top) {
return top->header;
dns_slabheader_t *header = NULL;
cds_list_for_each_entry(header, &top->headers, headers_link) {
return header;
}
return NULL;
}
static dns_slabheader_t *
next_header(dns_slabheader_t *header) {
return cds_list_entry((header)->headers_link.next, dns_slabheader_t,
headers_link);
}
static dns_slabheader_t *
@ -547,22 +558,21 @@ qpcache_hit(qpcache_t *qpdb ISC_ATTR_UNUSED, dns_slabheader_t *header) {
static void
clean_cache_headers(dns_slabtop_t *top) {
if (top->header == NULL) {
dns_slabheader_t *parent = first_header(top);
if (parent == NULL) {
return;
}
dns_slabheader_t *header = top->header, *header_down = NULL;
for (header = header->down; header != NULL; header = header_down) {
header_down = header->down;
dns_slabheader_t *header = next_header(parent), *header_next = NULL;
cds_list_for_each_entry_safe_from(header, header_next, &top->headers,
headers_link) {
cds_list_del(&header->headers_link);
dns_slabheader_destroy(&header);
}
top->header->down = NULL;
}
static void
clean_cache_node(qpcache_t *qpdb, qpcnode_t *node) {
dns_slabtop_t *top_prev = NULL;
/*
* Caller must be holding the node lock.
*/
@ -574,21 +584,23 @@ clean_cache_node(qpcache_t *qpdb, qpcnode_t *node) {
* If current top header is nonexistent, ancient, or stale
* and we are not keeping stale, we can clean it up too.
*/
if (!EXISTS(top->header) || ANCIENT(top->header) ||
(STALE(top->header) && !KEEPSTALE(qpdb)))
dns_slabheader_t *header = first_header(top);
if (header == NULL) {
continue;
}
if (!EXISTS(header) || ANCIENT(header) ||
(STALE(header) && !KEEPSTALE(qpdb)))
{
dns_slabheader_destroy(&top->header);
cds_list_del(&header->headers_link);
dns_slabheader_destroy(&header);
}
/*
* If current slabtop is empty, we can clean it up.
*/
if (top->header == NULL) {
if (top_prev != NULL) {
top_prev->next = top->next;
} else {
node->data = top->next;
}
if (header == NULL) {
cds_list_del(&top->types_link);
if (ISC_LINK_LINKED(top, link)) {
ISC_SIEVE_UNLINK(
@ -596,12 +608,10 @@ clean_cache_node(qpcache_t *qpdb, qpcnode_t *node) {
link);
}
dns_slabtop_destroy(((dns_db_t *)qpdb)->mctx, &top);
} else {
top_prev = top;
}
}
node->dirty = 0;
node->dirty = false;
}
/*
@ -750,7 +760,7 @@ qpcnode_release(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t *nlocktypep,
}
/* Handle easy and typical case first. */
if (!node->dirty && node->data != NULL) {
if (!node->dirty && !cds_list_empty(node->data)) {
goto unref;
}
@ -781,7 +791,7 @@ qpcnode_release(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t *nlocktypep,
clean_cache_node(qpdb, node);
}
if (node->data != NULL) {
if (!cds_list_empty(node->data)) {
goto unref;
}
@ -2383,6 +2393,8 @@ static qpcnode_t *
new_qpcnode(qpcache_t *qpdb, const dns_name_t *name, dns_namespace_t nspace) {
qpcnode_t *newdata = isc_mem_get(qpdb->common.mctx, sizeof(*newdata));
*newdata = (qpcnode_t){
.types_list = CDS_LIST_HEAD_INIT(newdata->types_list),
.data = &newdata->types_list,
.methods = &qpcnode_methods,
.qpdb = qpdb,
.name = DNS_NAME_INITEMPTY,
@ -2705,12 +2717,12 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode, dns_slabheader_t *newheader,
if (top->typepair == newheader->typepair) {
INSIST(oldheader == NULL);
oldheader = top->header;
oldheader = first_header(top);
}
if (sigpair != dns_rdatatype_none && top->typepair == sigpair) {
INSIST(oldsigheader == NULL);
oldsigheader = top->header;
oldsigheader = first_header(top);
}
}
@ -2854,9 +2866,9 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode, dns_slabheader_t *newheader,
return DNS_R_UNCHANGED;
}
oldheader->top->header = newheader;
newheader->top = oldheader->top;
newheader->down = oldheader;
cds_list_add(&newheader->headers_link,
&oldheader->top->headers);
ISC_SIEVE_UNLINK(qpdb->buckets[qpnode->locknum].sieve,
oldheader->top, link);
@ -2876,28 +2888,24 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode, dns_slabheader_t *newheader,
return DNS_R_UNCHANGED;
} else {
/* No rdatasets of the given type exist at the node. */
INSIST(newheader->down == NULL);
dns_slabtop_t *newtop = dns_slabtop_new(
((dns_db_t *)qpdb)->mctx, newheader->typepair);
newtop->header = newheader;
newheader->top = newtop;
cds_list_add(&newheader->headers_link, &newtop->headers);
qpcache_miss(qpdb, newheader, &nlocktype,
&tlocktype DNS__DB_FLARG_PASS);
if (prio_header(newtop)) {
/* This is a priority type, prepend it */
newtop->next = qpnode->data;
qpnode->data = newtop;
cds_list_add(&newtop->types_link, qpnode->data);
} else if (priotop != NULL) {
/* Append after the priority headers */
newtop->next = priotop->next;
priotop->next = newtop;
cds_list_add(&newtop->types_link, &priotop->types_link);
} else {
/* There were no priority headers */
newtop->next = qpnode->data;
qpnode->data = newtop;
cds_list_add(&newtop->types_link, qpnode->data);
}
if (overmaxtype(qpdb, ntypes)) {
@ -3387,24 +3395,29 @@ rdatasetiter_next(dns_rdatasetiter_t *it DNS__DB_FLARG) {
qpcnode_t *qpnode = (qpcnode_t *)iterator->common.node;
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
isc_rwlock_t *nlock = &qpdb->buckets[qpnode->locknum].lock;
dns_slabtop_t *next = NULL;
dns_slabtop_t *from = NULL;
if (iterator->current == NULL) {
return ISC_R_NOMORE;
}
next = iterator->current->next;
iterator->current = NULL;
NODE_RDLOCK(nlock, &nlocktype);
DNS_SLABTOP_FOREACH(top, next) {
dns_slabheader_t *header = first_existing_header(top);
from = cds_list_entry(iterator->current->types_link.next, dns_slabtop_t,
types_link);
iterator->current = NULL;
if (EXPIREDOK(iterator) ||
(header != NULL && iterator_active(qpdb, iterator, header)))
{
iterator->current = top;
break;
if (from != NULL) {
DNS_SLABTOP_FOREACH_FROM(top, qpnode->data, from) {
dns_slabheader_t *header = first_existing_header(top);
if (EXPIREDOK(iterator) ||
(header != NULL &&
iterator_active(qpdb, iterator, header)))
{
iterator->current = top;
break;
}
}
}
@ -3831,12 +3844,13 @@ qpcnode_destroy(qpcnode_t *qpnode) {
qpcache_t *qpdb = qpnode->qpdb;
DNS_SLABTOP_FOREACH(top, qpnode->data) {
dns_slabheader_t *down = NULL, *down_next = NULL;
for (down = top->header; down != NULL; down = down_next) {
down_next = down->down;
dns_slabheader_destroy(&down);
dns_slabheader_t *header = NULL, *header_next = NULL;
cds_list_for_each_entry_safe(header, header_next, &top->headers,
headers_link)
{
cds_list_del(&header->headers_link);
dns_slabheader_destroy(&header);
}
top->header = NULL;
if (ISC_LINK_LINKED(top, link)) {
ISC_SIEVE_UNLINK(qpdb->buckets[qpnode->locknum].sieve,

View file

@ -198,7 +198,9 @@ struct qpznode {
atomic_bool wild;
atomic_bool delegating;
atomic_bool dirty;
dns_slabtop_t *data;
struct cds_list_head types_list;
struct cds_list_head *data;
};
struct qpzonedb {
@ -631,6 +633,8 @@ static qpznode_t *
new_qpznode(qpzonedb_t *qpdb, const dns_name_t *name, dns_namespace_t nspace) {
qpznode_t *newdata = isc_mem_get(qpdb->common.mctx, sizeof(*newdata));
*newdata = (qpznode_t){
.types_list = CDS_LIST_HEAD_INIT(newdata->types_list),
.data = &newdata->types_list,
.methods = &qpznode_methods,
.name = DNS_NAME_INITEMPTY,
.nspace = nspace,
@ -801,16 +805,48 @@ qpznode_acquire(qpznode_t *node DNS__DB_FLARG) {
qpznode_erefs_increment(node DNS__DB_FLARG_PASS);
}
static dns_slabheader_t *
first_header(dns_slabtop_t *top) {
dns_slabheader_t *header = NULL;
cds_list_for_each_entry(header, &top->headers, headers_link) {
return header;
}
return NULL;
}
static dns_slabheader_t *
next_header(dns_slabheader_t *header) {
return cds_list_entry((header)->headers_link.next, dns_slabheader_t,
headers_link);
}
static dns_slabheader_t *
first_existing_header(dns_slabtop_t *top, uint32_t serial) {
dns_slabheader_t *header = NULL;
cds_list_for_each_entry(header, &top->headers, headers_link) {
if (header->serial <= serial && !IGNORE(header)) {
if (EXISTS(header)) {
return header;
}
break;
}
}
return NULL;
}
static void
clean_multiple_headers(dns_slabtop_t *top) {
dns_slabheader_t *parent = top->header;
dns_slabheader_t *header = NULL, *header_down = NULL;
dns_slabheader_t *parent = first_header(top);
if (parent == NULL) {
return;
}
for (header = parent->down; header != NULL; header = header_down) {
header_down = header->down;
dns_slabheader_t *header = next_header(parent), *header_next = NULL;
cds_list_for_each_entry_safe_from(header, header_next, &top->headers,
headers_link) {
INSIST(header->serial <= parent->serial);
if (header->serial == parent->serial || IGNORE(header)) {
parent->down = header->down;
cds_list_del(&header->headers_link);
dns_slabheader_destroy(&header);
} else {
parent = header;
@ -820,23 +856,26 @@ clean_multiple_headers(dns_slabtop_t *top) {
static void
check_top_header(dns_slabtop_t *top) {
dns_slabheader_t *header = top->header;
if (IGNORE(header)) {
top->header = header->down;
dns_slabheader_t *header = first_header(top);
if (header != NULL && IGNORE(header)) {
cds_list_del(&header->headers_link);
dns_slabheader_destroy(&header);
}
}
static bool
clean_multiple_versions(dns_slabtop_t *top, uint32_t least_serial) {
dns_slabheader_t *parent = top->header;
dns_slabheader_t *header = NULL, *header_down = NULL;
bool multiple = false;
dns_slabheader_t *parent = first_header(top);
if (parent == NULL) {
return false;
}
for (header = parent->down; header != NULL; header = header_down) {
header_down = header->down;
bool multiple = false;
dns_slabheader_t *header = next_header(parent), *header_next = NULL;
cds_list_for_each_entry_safe_from(header, header_next, &top->headers,
headers_link) {
if (header->serial < least_serial) {
parent->down = header->down;
cds_list_del(&header->headers_link);
dns_slabheader_destroy(&header);
} else {
multiple = true;
@ -848,7 +887,6 @@ clean_multiple_versions(dns_slabtop_t *top, uint32_t least_serial) {
static void
clean_zone_node(qpznode_t *node, uint32_t least_serial) {
dns_slabtop_t *top_prev = NULL;
bool still_dirty = false;
/*
@ -857,8 +895,6 @@ clean_zone_node(qpznode_t *node, uint32_t least_serial) {
REQUIRE(least_serial != 0);
DNS_SLABTOP_FOREACH(top, node->data) {
INSIST(top->header != NULL);
/*
* First, we clean up any instances of multiple rdatasets
* with the same serial number, or that have the IGNORE
@ -872,12 +908,8 @@ clean_zone_node(qpznode_t *node, uint32_t least_serial) {
*/
check_top_header(top);
if (top->header == NULL) {
if (top_prev != NULL) {
top_prev->next = top->next;
} else {
node->data = top->next;
}
if (first_header(top) == NULL) {
cds_list_del(&top->types_link);
dns_slabtop_destroy(node->mctx, &top);
} else {
/*
@ -891,8 +923,6 @@ clean_zone_node(qpznode_t *node, uint32_t least_serial) {
*/
still_dirty = clean_multiple_versions(top,
least_serial);
top_prev = top;
}
}
if (!still_dirty) {
@ -942,7 +972,7 @@ qpznode_release(qpznode_t *node, uint32_t least_serial,
}
/* Handle easy and typical case first. */
if (!node->dirty && node->data != NULL) {
if (!node->dirty && !cds_list_empty(node->data)) {
goto unref;
}
@ -1016,28 +1046,6 @@ bindrdataset(qpzonedb_t *qpdb, qpznode_t *node, dns_slabheader_t *header,
}
}
static dns_slabheader_t *
first_header(dns_slabtop_t *top, uint32_t serial) {
for (dns_slabheader_t *header = top->header; header != NULL;
header = header->down)
{
if (header->serial <= serial && !IGNORE(header)) {
return header;
}
}
return NULL;
}
static dns_slabheader_t *
first_existing_header(dns_slabtop_t *top, uint32_t serial) {
dns_slabheader_t *header = first_header(top, serial);
if (header != NULL && EXISTS(header)) {
return header;
}
return NULL;
}
static void
setnsec3parameters(dns_db_t *db, qpz_version_t *version) {
qpznode_t *node = NULL;
@ -1296,17 +1304,8 @@ rollback_node(qpznode_t *node, uint32_t serial) {
* will be cleaned up; until that time, they will be ignored.
*/
DNS_SLABTOP_FOREACH(top, node->data) {
dns_slabheader_t *header = top->header;
if (header->serial == serial) {
DNS_SLABHEADER_SETATTR(header,
DNS_SLABHEADERATTR_IGNORE);
make_dirty = true;
}
for (header = header->down; header != NULL;
header = header->down)
{
dns_slabheader_t *header = NULL;
cds_list_for_each_entry(header, &top->headers, headers_link) {
if (header->serial == serial) {
DNS_SLABHEADER_SETATTR(
header, DNS_SLABHEADERATTR_IGNORE);
@ -1825,12 +1824,14 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
* IGNORE rdatasets between the top of the chain and the first real
* data. We skip over them.
*/
dns_slabheader_t *header = NULL, *header_prev = NULL;
dns_slabheader_t *header = NULL;
if (foundtop != NULL) {
header = foundtop->header;
while (header != NULL && IGNORE(header)) {
header_prev = header;
header = header->down;
dns_slabheader_t *tmp = NULL;
cds_list_for_each_entry(tmp, &top->headers, headers_link) {
if (!IGNORE(tmp)) {
header = tmp;
break;
}
}
}
@ -1898,11 +1899,10 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
}
}
INSIST(version->serial >= foundtop->header->serial);
INSIST(version->serial >= header->serial);
INSIST(foundtop->typepair == newheader->typepair);
if (loading) {
newheader->down = NULL;
if (RESIGN(newheader)) {
resigninsert(newheader);
/* resigndelete not needed here */
@ -1915,7 +1915,9 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
* loading, we MUST clean up 'header' now.
*/
newheader->top = foundtop;
foundtop->header = newheader;
cds_list_del(&header->headers_link);
cds_list_add(&newheader->headers_link,
&foundtop->headers);
maybe_update_recordsandsize(false, version, header,
nodename->length);
@ -1927,14 +1929,9 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
header DNS__DB_FLARG_PASS);
}
if (header_prev != NULL) {
header_prev->down = newheader;
} else {
foundtop->header = newheader;
}
newheader->top = foundtop;
newheader->down = header;
cds_list_add(&newheader->headers_link,
&foundtop->headers);
node->dirty = true;
if (changed != NULL) {
@ -1970,10 +1967,11 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
* we INSIST on it.
*/
INSIST(!loading);
INSIST(version->serial >= foundtop->header->serial);
newheader->top = foundtop;
newheader->down = foundtop->header;
foundtop->header = newheader;
cds_list_add(&newheader->headers_link,
&foundtop->headers);
if (changed != NULL) {
changed->dirty = true;
}
@ -1994,20 +1992,19 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
node->mctx, newheader->typepair);
newheader->top = newtop;
newtop->header = newheader;
cds_list_add(&newheader->headers_link,
&newtop->headers);
if (prio_type(newheader->typepair)) {
/* This is a priority type, prepend it */
newtop->next = node->data;
node->data = newtop;
cds_list_add(&newtop->types_link, node->data);
} else if (priotop != NULL) {
/* Append after the priority headers */
newtop->next = priotop->next;
priotop->next = newtop;
cds_list_add(&newtop->types_link,
&priotop->types_link);
} else {
/* There were no priority headers */
newtop->next = node->data;
node->data = newtop;
cds_list_add(&newtop->types_link, node->data);
}
}
}
@ -2135,7 +2132,6 @@ loading_addrdataset(void *arg, const dns_name_t *name,
newheader = (dns_slabheader_t *)region.base;
dns_slabheader_reset(newheader, (dns_dbnode_t *)node);
newheader->ttl = rdataset->ttl;
atomic_store(&newheader->trust, rdataset->trust);
newheader->serial = 1;
@ -3964,25 +3960,30 @@ rdatasetiter_next(dns_rdatasetiter_t *iterator DNS__DB_FLARG) {
qpz_version_t *version = (qpz_version_t *)qrditer->common.version;
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
isc_rwlock_t *nlock = qpzone_get_lock(node);
dns_slabtop_t *next = NULL;
dns_slabtop_t *from = NULL;
if (qrditer->currenttop == NULL) {
return ISC_R_NOMORE;
}
next = qrditer->currenttop->next;
qrditer->currenttop = NULL;
qrditer->current = NULL;
NODE_RDLOCK(nlock, &nlocktype);
from = cds_list_entry(qrditer->currenttop->types_link.next,
dns_slabtop_t, types_link);
qrditer->currenttop = NULL;
qrditer->current = NULL;
/*
* Find the start of the header chain for the next type.
*/
DNS_SLABTOP_FOREACH(top, next) {
qrditer->current = first_existing_header(top, version->serial);
if (qrditer->current != NULL) {
qrditer->currenttop = top;
break;
if (from != NULL) {
DNS_SLABTOP_FOREACH_FROM(top, node->data, from) {
qrditer->current =
first_existing_header(top, version->serial);
if (qrditer->current != NULL) {
qrditer->currenttop = top;
break;
}
}
}
@ -4766,9 +4767,12 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
*/
dns_slabheader_t *header = NULL;
if (foundtop != NULL) {
header = foundtop->header;
while (header != NULL && IGNORE(header)) {
header = header->down;
dns_slabheader_t *tmp = NULL;
cds_list_for_each_entry(tmp, &foundtop->headers, headers_link) {
if (!IGNORE(tmp)) {
header = tmp;
break;
}
}
}
if (header != NULL && EXISTS(header)) {
@ -4834,13 +4838,11 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
/*
* If we're here, we want to link newheader at the top.
*/
INSIST(version->serial >= foundtop->header->serial);
maybe_update_recordsandsize(false, version, header,
nodename->length);
newheader->top = foundtop;
newheader->down = foundtop->header;
foundtop->header = newheader;
cds_list_add(&newheader->headers_link, &foundtop->headers);
node->dirty = true;
changed->dirty = true;
@ -5291,12 +5293,13 @@ static dns_dbnode_methods_t qpznode_methods = (dns_dbnode_methods_t){
static void
destroy_qpznode(qpznode_t *node) {
DNS_SLABTOP_FOREACH(top, node->data) {
dns_slabheader_t *down = NULL, *down_next = NULL;
for (down = top->header; down != NULL; down = down_next) {
down_next = down->down;
dns_slabheader_destroy(&down);
dns_slabheader_t *header = NULL, *header_next = NULL;
cds_list_for_each_entry_safe(header, header_next, &top->headers,
headers_link)
{
cds_list_del(&header->headers_link);
dns_slabheader_destroy(&header);
}
top->header = NULL;
dns_slabtop_destroy(node->mctx, &top);
}

View file

@ -323,6 +323,7 @@ dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
}
*new = (dns_slabheader_t){
.headers_link = CDS_LIST_HEAD_INIT(new->headers_link),
.typepair = typepair,
.trust = rdataset->trust,
.ttl = rdataset->ttl,
@ -1179,6 +1180,8 @@ dns_slabtop_t *
dns_slabtop_new(isc_mem_t *mctx, dns_typepair_t typepair) {
dns_slabtop_t *top = isc_mem_get(mctx, sizeof(*top));
*top = (dns_slabtop_t){
.types_link = CDS_LIST_HEAD_INIT(top->types_link),
.headers = CDS_LIST_HEAD_INIT(top->headers),
.typepair = typepair,
.link = ISC_LINK_INITIALIZER,
};

View file

@ -30,6 +30,7 @@
#include <urcu-pointer.h>
#include <urcu/compiler.h>
#include <urcu/list.h>
#include <urcu/rculfhash.h>
#include <urcu/rculist.h>
#include <urcu/wfstack.h>