Skip to content

Commit

Permalink
lib: add "seqlock" wait/broadcast primitive
Browse files Browse the repository at this point in the history
Manually tested rather extensively in addition to included unit tests,
should work as intended.

NB: The OpenBSD futex() code is "future"; it's not actually in OpenBSD
(yet?) and thus untested.

Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
  • Loading branch information
eqvinox committed Apr 18, 2019
1 parent 4cab68a commit 440d5fa
Show file tree
Hide file tree
Showing 7 changed files with 477 additions and 0 deletions.
74 changes: 74 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,80 @@ AC_CHECK_HEADERS([pthread_np.h],,, [
])
AC_CHECK_FUNCS([pthread_setname_np pthread_set_name_np])

needsync=true

AS_IF([$needsync], [
dnl Linux
AC_MSG_CHECKING([for Linux futex() support])
AC_LINK_IFELSE([AC_LANG_PROGRAM([
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <unistd.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/syscall.h>
#include <linux/futex.h>
int main(void);
],
[
{
return syscall(SYS_futex, NULL, FUTEX_WAIT, 0, NULL, NULL, 0);
}
])], [
AC_MSG_RESULT([yes])
AC_DEFINE(HAVE_SYNC_LINUX_FUTEX,,Have Linux futex support)
needsync=false
], [
AC_MSG_RESULT([no])
])
])

AS_IF([$needsync], [
dnl FreeBSD
AC_MSG_CHECKING([for FreeBSD _umtx_op() support])
AC_LINK_IFELSE([AC_LANG_PROGRAM([
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/umtx.h>
int main(void);
],
[
{
return _umtx_op(NULL, UMTX_OP_WAIT_UINT, 0, NULL, NULL);
}
])], [
AC_MSG_RESULT([yes])
AC_DEFINE(HAVE_SYNC_UMTX_OP,,Have FreeBSD _umtx_op() support)
needsync=false
], [
AC_MSG_RESULT([no])
])
])

AS_IF([$needsync], [
dnl OpenBSD patch (not upstream at the time of writing this)
dnl https://marc.info/?l=openbsd-tech&m=147299508409549&w=2
AC_MSG_CHECKING([for OpenBSD futex() support])
AC_LINK_IFELSE([AC_LANG_PROGRAM([
#include <sys/futex.h>
int main(void);
],
[
{
return futex(NULL, FUTEX_WAIT, 0, NULL, NULL, 0);
}
])], [
AC_MSG_RESULT([yes])
AC_DEFINE(HAVE_SYNC_OPENBSD_FUTEX,,Have OpenBSD futex support)
needsync=false
], [
AC_MSG_RESULT([no])
])
])

dnl Utility macro to avoid retyping includes all the time
m4_define([FRR_INCLUDES],
[#ifdef SUNOS_5
Expand Down
167 changes: 167 additions & 0 deletions lib/seqlock.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* "Sequence" lock primitive
*
* Copyright (C) 2015 David Lamparter <equinox@diac24.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/

#define _GNU_SOURCE

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <pthread.h>
#include <assert.h>

#include "seqlock.h"

#ifdef HAVE_SYNC_LINUX_FUTEX
/* Linux-specific - sys_futex() */
#include <sys/syscall.h>
#include <linux/futex.h>

static long sys_futex(void *addr1, int op, int val1, struct timespec *timeout,
void *addr2, int val3)
{
return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3);
}

#define wait_once(sqlo, val) \
sys_futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0)
#define wait_poke(sqlo) \
sys_futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0)

#elif defined(HAVE_SYNC_OPENBSD_FUTEX)
/* OpenBSD variant of the above. untested, not upstream in OpenBSD. */
#include <sys/syscall.h>
#include <sys/futex.h>

#define wait_once(sqlo, val) \
futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0)
#define wait_poke(sqlo) \
futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0)

#elif defined(HAVE_SYNC_UMTX_OP)
/* FreeBSD-specific: umtx_op() */
#include <sys/umtx.h>

#define wait_once(sqlo, val) \
_umtx_op((void *)&sqlo->pos, UMTX_OP_WAIT_UINT, val, NULL, NULL)
#define wait_poke(sqlo) \
_umtx_op((void *)&sqlo->pos, UMTX_OP_WAKE, INT_MAX, NULL, NULL)

#else
/* generic version. used on *BSD, Solaris and OSX.
*/

#define wait_init(sqlo) do { \
pthread_mutex_init(&sqlo->lock, NULL); \
pthread_cond_init(&sqlo->wake, NULL); \
} while (0)
#define wait_prep(sqlo) pthread_mutex_lock(&sqlo->lock)
#define wait_once(sqlo, val) pthread_cond_wait(&sqlo->wake, &sqlo->lock)
#define wait_done(sqlo) pthread_mutex_unlock(&sqlo->lock)
#define wait_poke(sqlo) do { \
pthread_mutex_lock(&sqlo->lock); \
pthread_cond_broadcast(&sqlo->wake); \
pthread_mutex_unlock(&sqlo->lock); \
} while (0)

#endif

#ifndef wait_init
#define wait_init(sqlo) /**/
#define wait_prep(sqlo) /**/
#define wait_done(sqlo) /**/
#endif /* wait_init */


void seqlock_wait(struct seqlock *sqlo, seqlock_val_t val)
{
seqlock_val_t cur, cal;

seqlock_assert_valid(val);

wait_prep(sqlo);
while (1) {
cur = atomic_load_explicit(&sqlo->pos, memory_order_acquire);
if (!(cur & 1))
break;
cal = cur - val - 1;
assert(cal < 0x40000000 || cal > 0xc0000000);
if (cal < 0x80000000)
break;

wait_once(sqlo, cur);
}
wait_done(sqlo);
}

bool seqlock_check(struct seqlock *sqlo, seqlock_val_t val)
{
seqlock_val_t cur;

seqlock_assert_valid(val);

cur = atomic_load_explicit(&sqlo->pos, memory_order_acquire);
if (!(cur & 1))
return 1;
cur -= val;
assert(cur < 0x40000000 || cur > 0xc0000000);
return cur < 0x80000000;
}

void seqlock_acquire_val(struct seqlock *sqlo, seqlock_val_t val)
{
seqlock_assert_valid(val);

atomic_store_explicit(&sqlo->pos, val, memory_order_release);
wait_poke(sqlo);
}

void seqlock_release(struct seqlock *sqlo)
{
atomic_store_explicit(&sqlo->pos, 0, memory_order_release);
wait_poke(sqlo);
}

void seqlock_init(struct seqlock *sqlo)
{
sqlo->pos = 0;
wait_init(sqlo);
}


seqlock_val_t seqlock_cur(struct seqlock *sqlo)
{
return atomic_load_explicit(&sqlo->pos, memory_order_acquire);
}

seqlock_val_t seqlock_bump(struct seqlock *sqlo)
{
seqlock_val_t val;

val = atomic_fetch_add_explicit(&sqlo->pos, 2, memory_order_release);
wait_poke(sqlo);
return val;
}
106 changes: 106 additions & 0 deletions lib/seqlock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* "Sequence" lock primitive
*
* Copyright (C) 2015 David Lamparter <equinox@diac24.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/

#ifndef _SEQLOCK_H
#define _SEQLOCK_H

#include <stdbool.h>
#include <stdint.h>
#include <pthread.h>
#include "frratomic.h"

/*
* this locking primitive is intended to use in a 1:N setup.
*
* - one "counter" seqlock issuing increasing numbers
* - multiple seqlock users hold references on these numbers
*
* this is intended for implementing RCU reference-holding. There is one
* global counter, with threads locking a seqlock whenever they take a
* reference. A seqlock can also be idle/unlocked.
*
* The "counter" seqlock will always stay locked; the RCU cleanup thread
* continuously counts it up, waiting for threads to release or progress to a
* sequence number further ahead. If all threads are > N, references dropped
* in N can be free'd.
*
* generally, the lock function is:
*
* Thread-A Thread-B
*
* seqlock_acquire(a)
* | running seqlock_wait(b) -- a <= b
* seqlock_release() | blocked
* OR: seqlock_acquire(a') | -- a' > b
* (resumes)
*/

/* use sequentially increasing "ticket numbers". lowest bit will always
* be 1 to have a 'cleared' indication (i.e., counts 1,3,5,7,etc. )
*/
typedef _Atomic uint32_t seqlock_ctr_t;
typedef uint32_t seqlock_val_t;
#define seqlock_assert_valid(val) assert(val & 1)


struct seqlock {
/* always used */
seqlock_ctr_t pos;
/* used when futexes not available: (i.e. non-linux) */
pthread_mutex_t lock;
pthread_cond_t wake;
};


/* sqlo = 0 - init state: not held */
extern void seqlock_init(struct seqlock *sqlo);


/* while (sqlo <= val) - wait until seqlock->pos > val, or seqlock unheld */
extern void seqlock_wait(struct seqlock *sqlo, seqlock_val_t val);
extern bool seqlock_check(struct seqlock *sqlo, seqlock_val_t val);

static inline bool seqlock_held(struct seqlock *sqlo)
{
return !!atomic_load_explicit(&sqlo->pos, memory_order_relaxed);
}

/* sqlo - get seqlock position -- for the "counter" seqlock */
extern seqlock_val_t seqlock_cur(struct seqlock *sqlo);
/* sqlo++ - note: like x++, returns previous value, before bumping */
extern seqlock_val_t seqlock_bump(struct seqlock *sqlo);


/* sqlo = val - can be used on held seqlock. */
extern void seqlock_acquire_val(struct seqlock *sqlo, seqlock_val_t val);
/* sqlo = ref - standard pattern: acquire relative to other seqlock */
static inline void seqlock_acquire(struct seqlock *sqlo, struct seqlock *ref)
{
seqlock_acquire_val(sqlo, seqlock_cur(ref));
}

/* sqlo = 0 - set seqlock position to 0, marking as non-held */
extern void seqlock_release(struct seqlock *sqlo);
/* release should normally be followed by a bump on the "counter", if
* anything other than reading RCU items was done
*/

#endif /* _SEQLOCK_H */
2 changes: 2 additions & 0 deletions lib/subdir.am
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ lib_libfrr_la_SOURCES = \
lib/ringbuf.c \
lib/routemap.c \
lib/sbuf.c \
lib/seqlock.c \
lib/sha256.c \
lib/sigevent.c \
lib/skiplist.c \
Expand Down Expand Up @@ -193,6 +194,7 @@ pkginclude_HEADERS += \
lib/ringbuf.h \
lib/routemap.h \
lib/sbuf.h \
lib/seqlock.h \
lib/sha256.h \
lib/sigevent.h \
lib/skiplist.h \
Expand Down
1 change: 1 addition & 0 deletions tests/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
/lib/test_privs
/lib/test_ringbuf
/lib/test_segv
/lib/test_seqlock
/lib/test_sig
/lib/test_srcdest_table
/lib/test_stream
Expand Down
Loading

0 comments on commit 440d5fa

Please sign in to comment.