lib: atomlist & atomsort

These two are lock-free linked list implementations, the plain one is
primarily intended for queues while the sorted one is for general data
storage.

Signed-off-by: David Lamparter <equinox@diac24.net>
This commit is contained in:
David Lamparter 2016-11-08 18:11:20 +01:00 committed by David Lamparter
parent 80911bc26a
commit bcea0c0fde
8 changed files with 1121 additions and 0 deletions

348
lib/atomlist.c Normal file
View file

@ -0,0 +1,348 @@
/*
* Copyright (c) 2016-2018 David Lamparter, for NetDEF, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "atomlist.h"
void atomlist_add_head(struct atomlist_head *h, struct atomlist_item *item)
{
atomptr_t prevval;
atomptr_t i = atomptr_i(item);
atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed);
/* updating ->last is possible here, but makes the code considerably
* more complicated... let's not.
*/
prevval = ATOMPTR_NULL;
item->next = ATOMPTR_NULL;
/* head-insert atomically
* release barrier: item + item->next writes must be completed
*/
while (!atomic_compare_exchange_weak_explicit(&h->first, &prevval, i,
memory_order_release, memory_order_relaxed))
atomic_store_explicit(&item->next, prevval,
memory_order_relaxed);
}
void atomlist_add_tail(struct atomlist_head *h, struct atomlist_item *item)
{
atomptr_t prevval = ATOMPTR_NULL;
atomptr_t i = atomptr_i(item);
atomptr_t hint;
struct atomlist_item *prevptr;
_Atomic atomptr_t *prev;
item->next = ATOMPTR_NULL;
atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed);
/* place new item into ->last
* release: item writes completed; acquire: DD barrier on hint
*/
hint = atomic_exchange_explicit(&h->last, i, memory_order_acq_rel);
while (1) {
if (atomptr_p(hint) == NULL)
prev = &h->first;
else
prev = &atomlist_itemp(hint)->next;
do {
prevval = atomic_load_explicit(prev,
memory_order_consume);
prevptr = atomlist_itemp(prevval);
if (prevptr == NULL)
break;
prev = &prevptr->next;
} while (prevptr);
/* last item is being deleted - start over */
if (atomptr_l(prevval)) {
hint = ATOMPTR_NULL;
continue;
}
/* no barrier - item->next is NULL and was so in xchg above */
if (!atomic_compare_exchange_strong_explicit(prev, &prevval, i,
memory_order_consume,
memory_order_consume)) {
hint = prevval;
continue;
}
break;
}
}
static void atomlist_del_core(struct atomlist_head *h,
struct atomlist_item *item,
_Atomic atomptr_t *hint,
atomptr_t next)
{
_Atomic atomptr_t *prev = hint ? hint : &h->first, *upd;
atomptr_t prevval, updval;
struct atomlist_item *prevptr;
/* drop us off "last" if needed. no r/w to barrier. */
prevval = atomptr_i(item);
atomic_compare_exchange_strong_explicit(&h->last, &prevval,
ATOMPTR_NULL,
memory_order_relaxed, memory_order_relaxed);
atomic_fetch_sub_explicit(&h->count, 1, memory_order_relaxed);
/* the following code should be identical (except sort<>list) to
* atomsort_del_hint()
*/
while (1) {
upd = NULL;
updval = ATOMPTR_LOCK;
do {
prevval = atomic_load_explicit(prev,
memory_order_consume);
/* track the beginning of a chain of deleted items
* this is neccessary to make this lock-free; we can
* complete deletions started by other threads.
*/
if (!atomptr_l(prevval)) {
updval = prevval;
upd = prev;
}
prevptr = atomlist_itemp(prevval);
if (prevptr == item)
break;
prev = &prevptr->next;
} while (prevptr);
if (prevptr != item)
/* another thread completed our deletion */
return;
if (!upd || atomptr_l(updval)) {
/* failed to find non-deleted predecessor...
* have to try again
*/
prev = &h->first;
continue;
}
if (!atomic_compare_exchange_strong_explicit(upd, &updval,
next, memory_order_consume,
memory_order_consume)) {
/* prev doesn't point to item anymore, something
* was inserted. continue at same position forward.
*/
continue;
}
break;
}
}
void atomlist_del_hint(struct atomlist_head *h, struct atomlist_item *item,
_Atomic atomptr_t *hint)
{
atomptr_t next;
/* mark ourselves in-delete - full barrier */
next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK,
memory_order_acquire);
assert(!atomptr_l(next)); /* delete race on same item */
atomlist_del_core(h, item, hint, next);
}
struct atomlist_item *atomlist_pop(struct atomlist_head *h)
{
struct atomlist_item *item;
atomptr_t next;
/* grab head of the list - and remember it in replval for the
* actual delete below. No matter what, the head of the list is
* where we start deleting because either it's our item, or it's
* some delete-marked items and then our item.
*/
next = atomic_load_explicit(&h->first, memory_order_consume);
do {
item = atomlist_itemp(next);
if (!item)
return NULL;
/* try to mark deletion */
next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK,
memory_order_acquire);
} while (atomptr_l(next));
/* if loop is taken: delete race on same item (another pop or del)
* => proceed to next item
* if loop exited here: we have our item selected and marked
*/
atomlist_del_core(h, item, &h->first, next);
return item;
}
struct atomsort_item *atomsort_add(struct atomsort_head *h,
struct atomsort_item *item, int (*cmpfn)(
const struct atomsort_item *,
const struct atomsort_item *))
{
_Atomic atomptr_t *prev;
atomptr_t prevval;
atomptr_t i = atomptr_i(item);
struct atomsort_item *previtem;
int cmpval;
do {
prev = &h->first;
do {
prevval = atomic_load_explicit(prev,
memory_order_acquire);
previtem = atomptr_p(prevval);
if (!previtem || (cmpval = cmpfn(previtem, item)) > 0)
break;
if (cmpval == 0)
return previtem;
prev = &previtem->next;
} while (1);
if (atomptr_l(prevval))
continue;
item->next = prevval;
if (atomic_compare_exchange_strong_explicit(prev, &prevval, i,
memory_order_release, memory_order_relaxed))
break;
} while (1);
atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed);
return NULL;
}
static void atomsort_del_core(struct atomsort_head *h,
struct atomsort_item *item, _Atomic atomptr_t *hint,
atomptr_t next)
{
_Atomic atomptr_t *prev = hint ? hint : &h->first, *upd;
atomptr_t prevval, updval;
struct atomsort_item *prevptr;
atomic_fetch_sub_explicit(&h->count, 1, memory_order_relaxed);
/* the following code should be identical (except sort<>list) to
* atomlist_del_core()
*/
while (1) {
upd = NULL;
updval = ATOMPTR_LOCK;
do {
prevval = atomic_load_explicit(prev,
memory_order_consume);
/* track the beginning of a chain of deleted items
* this is neccessary to make this lock-free; we can
* complete deletions started by other threads.
*/
if (!atomptr_l(prevval)) {
updval = prevval;
upd = prev;
}
prevptr = atomsort_itemp(prevval);
if (prevptr == item)
break;
prev = &prevptr->next;
} while (prevptr);
if (prevptr != item)
/* another thread completed our deletion */
return;
if (!upd || atomptr_l(updval)) {
/* failed to find non-deleted predecessor...
* have to try again
*/
prev = &h->first;
continue;
}
if (!atomic_compare_exchange_strong_explicit(upd, &updval,
next, memory_order_relaxed,
memory_order_relaxed)) {
/* prev doesn't point to item anymore, something
* was inserted. continue at same position forward.
*/
continue;
}
break;
}
}
void atomsort_del_hint(struct atomsort_head *h, struct atomsort_item *item,
_Atomic atomptr_t *hint)
{
atomptr_t next;
/* mark ourselves in-delete - full barrier */
next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK,
memory_order_seq_cst);
assert(!atomptr_l(next)); /* delete race on same item */
atomsort_del_core(h, item, hint, next);
}
struct atomsort_item *atomsort_pop(struct atomsort_head *h)
{
struct atomsort_item *item;
atomptr_t next;
/* grab head of the list - and remember it in replval for the
* actual delete below. No matter what, the head of the list is
* where we start deleting because either it's our item, or it's
* some delete-marked items and then our item.
*/
next = atomic_load_explicit(&h->first, memory_order_consume);
do {
item = atomsort_itemp(next);
if (!item)
return NULL;
/* try to mark deletion */
next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK,
memory_order_acquire);
} while (atomptr_l(next));
/* if loop is taken: delete race on same item (another pop or del)
* => proceed to next item
* if loop exited here: we have our item selected and marked
*/
atomsort_del_core(h, item, &h->first, next);
return item;
}

347
lib/atomlist.h Normal file
View file

@ -0,0 +1,347 @@
/*
* Copyright (c) 2016-2019 David Lamparter, for NetDEF, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _FRR_ATOMLIST_H
#define _FRR_ATOMLIST_H
#include "typesafe.h"
#include "frratomic.h"
/* pointer with lock/deleted/invalid bit in lowest bit
*
* for atomlist/atomsort, "locked" means "this pointer can't be updated, the
* item is being deleted". it is permissible to assume the item will indeed
* be deleted (as there are no replace/etc. ops in this).
*
* in general, lowest 2/3 bits on 32/64bit architectures are available for
* uses like this; the only thing that will really break this is putting an
* atomlist_item in a struct with "packed" attribute. (it'll break
* immediately and consistently.) -- don't do that.
*
* ATOMPTR_USER is currently unused (and available for atomic hash or skiplist
* implementations.)
*/
typedef uintptr_t atomptr_t;
#define ATOMPTR_MASK (UINTPTR_MAX - 3)
#define ATOMPTR_LOCK (1)
#define ATOMPTR_USER (2)
#define ATOMPTR_NULL (0)
static inline atomptr_t atomptr_i(void *val)
{
atomptr_t atomval = (atomptr_t)val;
assert(!(atomval & ATOMPTR_LOCK));
return atomval;
}
static inline void *atomptr_p(atomptr_t val)
{
return (void *)(val & ATOMPTR_MASK);
}
static inline bool atomptr_l(atomptr_t val)
{
return (bool)(val & ATOMPTR_LOCK);
}
static inline bool atomptr_u(atomptr_t val)
{
return (bool)(val & ATOMPTR_USER);
}
/* the problem with, find(), find_gteq() and find_lt() on atomic lists is that
* they're neither an "acquire" nor a "release" operation; the element that
* was found is still on the list and doesn't change ownership. Therefore,
* an atomic transition in ownership state can't be implemented.
*
* Contrast this with add() or pop(): both function calls atomically transfer
* ownership of an item to or from the list, which makes them "acquire" /
* "release" operations.
*
* What can be implemented atomically is a "find_pop()", i.e. try to locate an
* item and atomically try to remove it if found. It's not currently
* implemented but can be added when needed.
*
* Either way - for find(), generally speaking, if you need to use find() on
* a list then the whole thing probably isn't well-suited to atomic
* implementation and you'll need to have extra locks around to make it work
* correctly.
*/
#ifdef WNO_ATOMLIST_UNSAFE_FIND
# define atomic_find_warn
#else
# define atomic_find_warn __attribute__((_DEPRECATED( \
"WARNING: find() on atomic lists cannot be atomic by principle; " \
"check code to make sure usage pattern is OK and if it is, use " \
"#define WNO_ATOMLIST_UNSAFE_FIND")))
#endif
/* single-linked list, unsorted/arbitrary.
* can be used as queue with add_tail / pop
*
* all operations are lock-free, but not neccessarily wait-free. this means
* that there is no state where the system as a whole stops making process,
* but it *is* possible that a *particular* thread is delayed by some time.
*
* the only way for this to happen is for other threads to continuously make
* updates. an inactive / blocked / deadlocked other thread cannot cause such
* delays, and to cause such delays a thread must be heavily hitting the list -
* it's a rather theoretical concern.
*/
/* don't use these structs directly */
struct atomlist_item {
_Atomic atomptr_t next;
};
#define atomlist_itemp(val) ((struct atomlist_item *)atomptr_p(val))
struct atomlist_head {
_Atomic atomptr_t first, last;
_Atomic size_t count;
};
/* use as:
*
* PREDECL_ATOMLIST(namelist)
* struct name {
* struct namelist_item nlitem;
* }
* DECLARE_ATOMLIST(namelist, struct name, nlitem)
*/
#define PREDECL_ATOMLIST(prefix) \
struct prefix ## _head { struct atomlist_head ah; }; \
struct prefix ## _item { struct atomlist_item ai; };
#define INIT_ATOMLIST(var) { }
#define DECLARE_ATOMLIST(prefix, type, field) \
macro_inline void prefix ## _add_head(struct prefix##_head *h, type *item) \
{ atomlist_add_head(&h->ah, &item->field.ai); } \
macro_inline void prefix ## _add_tail(struct prefix##_head *h, type *item) \
{ atomlist_add_tail(&h->ah, &item->field.ai); } \
macro_inline void prefix ## _del_hint(struct prefix##_head *h, type *item, \
_Atomic atomptr_t *hint) \
{ atomlist_del_hint(&h->ah, &item->field.ai, hint); } \
macro_inline void prefix ## _del(struct prefix##_head *h, type *item) \
{ atomlist_del_hint(&h->ah, &item->field.ai, NULL); } \
macro_inline type *prefix ## _pop(struct prefix##_head *h) \
{ char *p = (char *)atomlist_pop(&h->ah); \
return p ? (type *)(p - offsetof(type, field)) : NULL; } \
macro_inline type *prefix ## _first(struct prefix##_head *h) \
{ char *p = atomptr_p(atomic_load_explicit(&h->ah.first, \
memory_order_acquire)); \
return p ? (type *)(p - offsetof(type, field)) : NULL; } \
macro_inline type *prefix ## _next(struct prefix##_head *h, type *item) \
{ char *p = atomptr_p(atomic_load_explicit(&item->field.ai.next, \
memory_order_acquire)); \
return p ? (type *)(p - offsetof(type, field)) : NULL; } \
macro_inline type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
{ return item ? prefix##_next(h, item) : NULL; } \
macro_inline size_t prefix ## _count(struct prefix##_head *h) \
{ return atomic_load_explicit(&h->ah.count, memory_order_relaxed); } \
/* ... */
/* add_head:
* - contention on ->first pointer
* - return implies completion
*/
void atomlist_add_head(struct atomlist_head *h, struct atomlist_item *item);
/* add_tail:
* - concurrent add_tail can cause wait but has progress guarantee
* - return does NOT imply completion. completion is only guaranteed after
* all other add_tail operations that started before this add_tail have
* completed as well.
*/
void atomlist_add_tail(struct atomlist_head *h, struct atomlist_item *item);
/* del/del_hint:
*
* OWNER MUST HOLD REFERENCE ON ITEM TO BE DELETED, ENSURING NO OTHER THREAD
* WILL TRY TO DELETE THE SAME ITEM. DELETING INCLUDES pop().
*
* as with all deletions, threads that started reading earlier may still hold
* pointers to the deleted item. completion is however guaranteed for all
* reads starting later.
*/
void atomlist_del_hint(struct atomlist_head *h, struct atomlist_item *item,
_Atomic atomptr_t *hint);
/* pop:
*
* as with all deletions, threads that started reading earlier may still hold
* pointers to the deleted item. completion is however guaranteed for all
* reads starting later.
*/
struct atomlist_item *atomlist_pop(struct atomlist_head *h);
struct atomsort_item {
_Atomic atomptr_t next;
};
#define atomsort_itemp(val) ((struct atomsort_item *)atomptr_p(val))
struct atomsort_head {
_Atomic atomptr_t first;
_Atomic size_t count;
};
#define _PREDECL_ATOMSORT(prefix) \
struct prefix ## _head { struct atomsort_head ah; }; \
struct prefix ## _item { struct atomsort_item ai; };
#define INIT_ATOMSORT_UNIQ(var) { }
#define INIT_ATOMSORT_NONUNIQ(var) { }
#define _DECLARE_ATOMSORT(prefix, type, field, cmpfn_nuq, cmpfn_uq) \
macro_inline void prefix ## _init(struct prefix##_head *h) \
{ \
memset(h, 0, sizeof(*h)); \
} \
macro_inline void prefix ## _fini(struct prefix##_head *h) \
{ \
assert(h->ah.count == 0); \
memset(h, 0, sizeof(*h)); \
} \
macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \
{ \
struct atomsort_item *p; \
p = atomsort_add(&h->ah, &item->field.ai, cmpfn_uq); \
return container_of_null(p, type, field.ai); \
} \
macro_inline type *prefix ## _first(struct prefix##_head *h) \
{ \
struct atomsort_item *p; \
p = atomptr_p(atomic_load_explicit(&h->ah.first, \
memory_order_acquire)); \
return container_of_null(p, type, field.ai); \
} \
macro_inline type *prefix ## _next(struct prefix##_head *h, type *item) \
{ \
struct atomsort_item *p; \
p = atomptr_p(atomic_load_explicit(&item->field.ai.next, \
memory_order_acquire)); \
return container_of_null(p, type, field.ai); \
} \
macro_inline type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
{ \
return item ? prefix##_next(h, item) : NULL; \
} \
atomic_find_warn \
macro_inline type *prefix ## _find_gteq(struct prefix##_head *h, \
const type *item) \
{ \
type *p = prefix ## _first(h); \
while (p && cmpfn_nuq(&p->field.ai, &item->field.ai) < 0) \
p = prefix ## _next(h, p); \
return p; \
} \
atomic_find_warn \
macro_inline type *prefix ## _find_lt(struct prefix##_head *h, \
const type *item) \
{ \
type *p = prefix ## _first(h), *prev = NULL; \
while (p && cmpfn_nuq(&p->field.ai, &item->field.ai) < 0) \
p = prefix ## _next(h, (prev = p)); \
return prev; \
} \
macro_inline void prefix ## _del_hint(struct prefix##_head *h, type *item, \
_Atomic atomptr_t *hint) \
{ \
atomsort_del_hint(&h->ah, &item->field.ai, hint); \
} \
macro_inline void prefix ## _del(struct prefix##_head *h, type *item) \
{ \
atomsort_del_hint(&h->ah, &item->field.ai, NULL); \
} \
macro_inline size_t prefix ## _count(struct prefix##_head *h) \
{ \
return atomic_load_explicit(&h->ah.count, memory_order_relaxed); \
} \
macro_inline type *prefix ## _pop(struct prefix##_head *h) \
{ \
struct atomsort_item *p = atomsort_pop(&h->ah); \
return p ? container_of(p, type, field.ai) : NULL; \
} \
/* ... */
#define PREDECL_ATOMSORT_UNIQ(prefix) \
_PREDECL_ATOMSORT(prefix)
#define DECLARE_ATOMSORT_UNIQ(prefix, type, field, cmpfn) \
\
macro_inline int prefix ## __cmp(const struct atomsort_item *a, \
const struct atomsort_item *b) \
{ \
return cmpfn(container_of(a, type, field.ai), \
container_of(b, type, field.ai)); \
} \
\
_DECLARE_ATOMSORT(prefix, type, field, \
prefix ## __cmp, prefix ## __cmp) \
\
atomic_find_warn \
macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \
{ \
type *p = prefix ## _first(h); \
int cmpval = 0; \
while (p && (cmpval = cmpfn(p, item)) < 0) \
p = prefix ## _next(h, p); \
if (!p || cmpval > 0) \
return NULL; \
return p; \
} \
/* ... */
#define PREDECL_ATOMSORT_NONUNIQ(prefix) \
_PREDECL_ATOMSORT(prefix)
#define DECLARE_ATOMSORT_NONUNIQ(prefix, type, field, cmpfn) \
\
macro_inline int prefix ## __cmp(const struct atomsort_item *a, \
const struct atomsort_item *b) \
{ \
return cmpfn(container_of(a, type, field.ai), \
container_of(b, type, field.ai)); \
} \
macro_inline int prefix ## __cmp_uq(const struct atomsort_item *a, \
const struct atomsort_item *b) \
{ \
int cmpval = cmpfn(container_of(a, type, field.ai), \
container_of(b, type, field.ai)); \
if (cmpval) \
return cmpval; \
if (a < b) \
return -1; \
if (a > b) \
return 1; \
return 0; \
} \
\
_DECLARE_ATOMSORT(prefix, type, field, \
prefix ## __cmp, prefix ## __cmp_uq) \
/* ... */
struct atomsort_item *atomsort_add(struct atomsort_head *h,
struct atomsort_item *item, int (*cmpfn)(
const struct atomsort_item *,
const struct atomsort_item *));
void atomsort_del_hint(struct atomsort_head *h,
struct atomsort_item *item, _Atomic atomptr_t *hint);
struct atomsort_item *atomsort_pop(struct atomsort_head *h);
#endif /* _FRR_ATOMLIST_H */

View file

@ -32,6 +32,7 @@ extern "C" {
# define _FALLTHROUGH __attribute__((fallthrough));
#endif
# define _CONSTRUCTOR(x) constructor(x)
# define _DEPRECATED(x) deprecated(x)
#elif defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)
# define _RET_NONNULL , returns_nonnull
@ -41,6 +42,9 @@ extern "C" {
# define _DESTRUCTOR(x) destructor(x)
# define _ALLOC_SIZE(x) alloc_size(x)
#endif
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
# define _DEPRECATED(x) deprecated(x)
#endif
#if __GNUC__ >= 7
# define _FALLTHROUGH __attribute__((fallthrough));
#endif
@ -68,6 +72,9 @@ extern "C" {
#ifndef _FALLTHROUGH
#define _FALLTHROUGH
#endif
#ifndef _DEPRECATED
#define _DEPRECATED(x) deprecated
#endif
/* for helper functions defined inside macros */
#define macro_inline static inline __attribute__((unused))

View file

@ -7,6 +7,7 @@ lib_libfrr_la_LIBADD = $(LIBCAP) $(UNWIND_LIBS) $(LIBYANG_LIBS)
lib_libfrr_la_SOURCES = \
lib/agg_table.c \
lib/atomlist.c \
lib/bfd.c \
lib/buffer.c \
lib/checksum.c \
@ -133,6 +134,7 @@ lib/northbound_cli.lo: lib/northbound_cli_clippy.c
pkginclude_HEADERS += \
lib/agg_table.h \
lib/atomlist.h \
lib/bfd.h \
lib/bitfield.h \
lib/buffer.h \

1
tests/.gitignore vendored
View file

@ -20,6 +20,7 @@
/lib/cli/test_commands_defun.c
/lib/northbound/test_oper_data
/lib/cxxcompat
/lib/test_atomlist
/lib/test_buffer
/lib/test_checksum
/lib/test_graph

404
tests/lib/test_atomlist.c Normal file
View file

@ -0,0 +1,404 @@
/*
* Copyright (c) 2016-2018 David Lamparter, for NetDEF, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include "atomlist.h"
#include "seqlock.h"
#include "monotime.h"
/*
* maybe test:
* - alist_del_hint
* - alist_next_safe
* - asort_del_hint
* - asort_next_safe
*/
static struct seqlock sqlo;
PREDECL_ATOMLIST(alist)
PREDECL_ATOMSORT_UNIQ(asort)
struct item {
uint64_t val1;
struct alist_item chain;
struct asort_item sortc;
uint64_t val2;
};
DECLARE_ATOMLIST(alist, struct item, chain)
static int icmp(const struct item *a, const struct item *b);
DECLARE_ATOMSORT_UNIQ(asort, struct item, sortc, icmp)
static int icmp(const struct item *a, const struct item *b)
{
if (a->val1 > b->val1)
return 1;
if (a->val1 < b->val1)
return -1;
return 0;
}
#define NITEM 10000
struct item itm[NITEM];
static struct alist_head ahead;
static struct asort_head shead;
#define NTHREADS 4
static struct testthread {
pthread_t pt;
struct seqlock sqlo;
size_t counter, nullops;
} thr[NTHREADS];
struct testrun {
struct testrun *next;
int lineno;
const char *desc;
ssize_t prefill;
bool sorted;
void (*func)(unsigned int offset);
};
struct testrun *runs = NULL;
#define NOCLEAR -1
#define deftestrun(name, _desc, _prefill, _sorted) \
static void trfunc_##name(unsigned int offset); \
struct testrun tr_##name = { \
.desc = _desc, \
.lineno = __LINE__, \
.prefill = _prefill, \
.func = &trfunc_##name, \
.sorted = _sorted }; \
static void __attribute__((constructor)) trsetup_##name(void) \
{ \
struct testrun **inspos = &runs; \
while (*inspos && (*inspos)->lineno < tr_##name.lineno) \
inspos = &(*inspos)->next; \
tr_##name.next = *inspos; \
*inspos = &tr_##name; \
} \
static void trfunc_##name(unsigned int offset) \
{ \
size_t i = 0, n = 0;
#define endtestrun \
thr[offset].counter = i; \
thr[offset].nullops = n; \
}
deftestrun(add, "add vs. add", 0, false)
for (; i < NITEM / NTHREADS; i++)
alist_add_head(&ahead, &itm[i * NTHREADS + offset]);
endtestrun
deftestrun(del, "del vs. del", NOCLEAR, false)
for (; i < NITEM / NTHREADS / 10; i++)
alist_del(&ahead, &itm[i * NTHREADS + offset]);
endtestrun
deftestrun(addtail, "add_tail vs. add_tail", 0, false)
for (; i < NITEM / NTHREADS; i++)
alist_add_tail(&ahead, &itm[i * NTHREADS + offset]);
endtestrun
deftestrun(pop, "pop vs. pop", NOCLEAR, false)
for (; i < NITEM / NTHREADS; )
if (alist_pop(&ahead))
i++;
else
n++;
endtestrun
deftestrun(headN_vs_pop1, "add_head(N) vs. pop(1)", 1, false);
if (offset == 0) {
struct item *dr = NULL;
for (i = n = 0; i < NITEM; ) {
dr = alist_pop(&ahead);
if (dr)
i++;
else
n++;
}
} else {
for (i = offset; i < NITEM; i += NTHREADS)
alist_add_head(&ahead, &itm[i]);
i = 0;
}
endtestrun
deftestrun(head1_vs_popN, "add_head(1) vs. pop(N)", 0, false);
if (offset < NTHREADS - 1) {
struct item *dr = NULL;
for (i = n = 0; i < NITEM / NTHREADS; ) {
dr = alist_pop(&ahead);
if (dr)
i++;
else
n++;
}
} else {
for (i = 0; i < NITEM; i++)
alist_add_head(&ahead, &itm[i]);
i = 0;
}
endtestrun
deftestrun(headN_vs_popN, "add_head(N) vs. pop(N)", NTHREADS / 2, false)
if (offset < NTHREADS / 2) {
struct item *dr = NULL;
for (i = n = 0; i < NITEM * 2 / NTHREADS; ) {
dr = alist_pop(&ahead);
if (dr)
i++;
else
n++;
}
} else {
for (i = offset; i < NITEM; i += NTHREADS)
alist_add_head(&ahead, &itm[i]);
i = 0;
}
endtestrun
deftestrun(tailN_vs_pop1, "add_tail(N) vs. pop(1)", 1, false)
if (offset == 0) {
struct item *dr = NULL;
for (i = n = 0; i < NITEM - (NITEM / NTHREADS); ) {
dr = alist_pop(&ahead);
if (dr)
i++;
else
n++;
}
} else {
for (i = offset; i < NITEM; i += NTHREADS)
alist_add_tail(&ahead, &itm[i]);
i = 0;
}
endtestrun
deftestrun(tail1_vs_popN, "add_tail(1) vs. pop(N)", 0, false)
if (offset < NTHREADS - 1) {
struct item *dr = NULL;
for (i = n = 0; i < NITEM / NTHREADS; ) {
dr = alist_pop(&ahead);
if (dr)
i++;
else
n++;
}
} else {
for (i = 0; i < NITEM; i++)
alist_add_tail(&ahead, &itm[i]);
i = 0;
}
endtestrun
deftestrun(sort_add, "add_sort vs. add_sort", 0, true)
for (; i < NITEM / NTHREADS / 10; i++)
asort_add(&shead, &itm[i * NTHREADS + offset]);
endtestrun
deftestrun(sort_del, "del_sort vs. del_sort", NOCLEAR, true)
for (; i < NITEM / NTHREADS / 10; i++)
asort_del(&shead, &itm[i * NTHREADS + offset]);
endtestrun
deftestrun(sort_add_del, "add_sort vs. del_sort", NTHREADS / 2, true)
if (offset < NTHREADS / 2) {
for (; i < NITEM / NTHREADS / 10; i++)
asort_del(&shead, &itm[i * NTHREADS + offset]);
} else {
for (; i < NITEM / NTHREADS / 10; i++)
asort_add(&shead, &itm[i * NTHREADS + offset]);
}
endtestrun
static void *thr1func(void *arg)
{
struct testthread *p = arg;
unsigned int offset = (unsigned int)(p - &thr[0]);
seqlock_val_t sv;
struct testrun *tr;
for (tr = runs; tr; tr = tr->next) {
sv = seqlock_bump(&p->sqlo);
seqlock_wait(&sqlo, sv);
tr->func(offset);
}
seqlock_bump(&p->sqlo);
return NULL;
}
static void clear_list(size_t prefill)
{
size_t i;
memset(&ahead, 0, sizeof(ahead));
memset(&shead, 0, sizeof(shead));
memset(itm, 0, sizeof(itm));
for (i = 0; i < NITEM; i++) {
itm[i].val1 = itm[i].val2 = i;
if ((i % NTHREADS) < prefill) {
alist_add_tail(&ahead, &itm[i]);
asort_add(&shead, &itm[i]);
}
}
}
static void run_tr(struct testrun *tr)
{
const char *desc = tr->desc;
struct timeval tv;
int64_t delta;
seqlock_val_t sv;
size_t c = 0, s = 0, n = 0;
struct item *item, *prev, dummy;
printf("[%02u] %35s %s\n", seqlock_cur(&sqlo) >> 1, "", desc);
fflush(stdout);
if (tr->prefill != NOCLEAR)
clear_list(tr->prefill);
monotime(&tv);
sv = seqlock_bump(&sqlo);
for (size_t i = 0; i < NTHREADS; i++) {
seqlock_wait(&thr[i].sqlo, seqlock_cur(&sqlo));
s += thr[i].counter;
n += thr[i].nullops;
thr[i].counter = 0;
thr[i].nullops = 0;
}
delta = monotime_since(&tv, NULL);
if (tr->sorted) {
uint64_t prevval = 0;
for_each(asort, &shead, item) {
assert(item->val1 >= prevval);
prevval = item->val1;
c++;
}
assert(c == asort_count(&shead));
} else {
prev = &dummy;
for_each(alist, &ahead, item) {
assert(item != prev);
prev = item;
c++;
assert(c <= NITEM);
}
assert(c == alist_count(&ahead));
}
printf("\033[1A[%02u] %9"PRId64"us c=%5zu s=%5zu n=%5zu %s\n",
sv >> 1, delta, c, s, n, desc);
}
#ifdef BASIC_TESTS
static void dump(const char *lbl)
{
struct item *item, *safe;
size_t ctr = 0;
printf("dumping %s:\n", lbl);
for_each_safe(alist, &ahead, item) {
printf("%s %3zu %p %3"PRIu64" %3"PRIu64"\n", lbl, ctr++,
(void *)item, item->val1, item->val2);
}
}
static void basic_tests(void)
{
size_t i;
memset(&ahead, 0, sizeof(ahead));
memset(itm, 0, sizeof(itm));
for (i = 0; i < NITEM; i++)
itm[i].val1 = itm[i].val2 = i;
assert(alist_first(&ahead) == NULL);
dump("");
alist_add_head(&ahead, &itm[0]);
dump("");
alist_add_head(&ahead, &itm[1]);
dump("");
alist_add_tail(&ahead, &itm[2]);
dump("");
alist_add_tail(&ahead, &itm[3]);
dump("");
alist_del(&ahead, &itm[1]);
dump("");
printf("POP: %p\n", alist_pop(&ahead));
dump("");
printf("POP: %p\n", alist_pop(&ahead));
printf("POP: %p\n", alist_pop(&ahead));
printf("POP: %p\n", alist_pop(&ahead));
printf("POP: %p\n", alist_pop(&ahead));
dump("");
}
#else
#define basic_tests() do { } while (0)
#endif
int main(int argc, char **argv)
{
size_t i;
basic_tests();
seqlock_init(&sqlo);
seqlock_acquire_val(&sqlo, 1);
for (i = 0; i < NTHREADS; i++) {
seqlock_init(&thr[i].sqlo);
seqlock_acquire(&thr[i].sqlo, &sqlo);
thr[i].counter = 0;
thr[i].nullops = 0;
pthread_create(&thr[i].pt, NULL, thr1func, &thr[i]);
}
struct testrun *tr;
for (tr = runs; tr; tr = tr->next)
run_tr(tr);
for (i = 0; i < NTHREADS; i++)
pthread_join(thr[i].pt, NULL);
return 0;
}

View file

@ -0,0 +1,6 @@
import frrtest
class TestAtomlist(frrtest.TestMultiOut):
program = './test_atomlist'
TestAtomlist.exit_cleanly()

View file

@ -47,6 +47,7 @@ tests/ospf6d/test_lsdb-test_lsdb.$(OBJEXT): tests/ospf6d/test_lsdb_clippy.c
check_PROGRAMS = \
tests/lib/cxxcompat \
tests/lib/test_atomlist \
tests/lib/test_buffer \
tests/lib/test_checksum \
tests/lib/test_heavy_thread \
@ -190,6 +191,10 @@ tests_lib_northbound_test_oper_data_CPPFLAGS = $(TESTS_CPPFLAGS)
tests_lib_northbound_test_oper_data_LDADD = $(ALL_TESTS_LDADD)
tests_lib_northbound_test_oper_data_SOURCES = tests/lib/northbound/test_oper_data.c
nodist_tests_lib_northbound_test_oper_data_SOURCES = yang/frr-test-module.yang.c
tests_lib_test_atomlist_CFLAGS = $(TESTS_CFLAGS)
tests_lib_test_atomlist_CPPFLAGS = $(TESTS_CPPFLAGS)
tests_lib_test_atomlist_LDADD = $(ALL_TESTS_LDADD)
tests_lib_test_atomlist_SOURCES = tests/lib/test_atomlist.c
tests_lib_test_buffer_CFLAGS = $(TESTS_CFLAGS)
tests_lib_test_buffer_CPPFLAGS = $(TESTS_CPPFLAGS)
tests_lib_test_buffer_LDADD = $(ALL_TESTS_LDADD)
@ -306,6 +311,7 @@ EXTRA_DIST += \
tests/lib/northbound/test_oper_data.in \
tests/lib/northbound/test_oper_data.py \
tests/lib/northbound/test_oper_data.refout \
tests/lib/test_atomlist.py \
tests/lib/test_nexthop_iter.py \
tests/lib/test_ringbuf.py \
tests/lib/test_srcdest_table.py \