mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2025-03-22 06:17:04 +00:00
Read-write spinlocks
This commit is contained in:
parent
8f4a784e14
commit
4104d668d9
@ -291,4 +291,9 @@ static inline u64 u64_hash0(u64 v, u32 p, u64 acc)
|
|||||||
static inline u32 u64_hash(u64 v)
|
static inline u32 u64_hash(u64 v)
|
||||||
{ return hash_value(u64_hash0(v, HASH_PARAM, 0)); }
|
{ return hash_value(u64_hash0(v, HASH_PARAM, 0)); }
|
||||||
|
|
||||||
|
|
||||||
|
/* Yield for a little while. Use only in special cases. */
|
||||||
|
void birdloop_yield(void);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -72,7 +72,4 @@ void birdloop_remove_socket(struct birdloop *, struct birdsock *);
|
|||||||
|
|
||||||
void birdloop_init(void);
|
void birdloop_init(void);
|
||||||
|
|
||||||
/* Yield for a little while. Use only in special cases. */
|
|
||||||
void birdloop_yield(void);
|
|
||||||
|
|
||||||
#endif /* _BIRD_IO_LOOP_H_ */
|
#endif /* _BIRD_IO_LOOP_H_ */
|
||||||
|
146
lib/locking.h
146
lib/locking.h
@ -9,6 +9,7 @@
|
|||||||
#ifndef _BIRD_LOCKING_H_
|
#ifndef _BIRD_LOCKING_H_
|
||||||
#define _BIRD_LOCKING_H_
|
#define _BIRD_LOCKING_H_
|
||||||
|
|
||||||
|
#include "lib/birdlib.h"
|
||||||
#include "lib/macro.h"
|
#include "lib/macro.h"
|
||||||
#include "lib/rcu.h"
|
#include "lib/rcu.h"
|
||||||
|
|
||||||
@ -83,7 +84,150 @@ extern DOMAIN(the_bird) the_bird_domain;
|
|||||||
|
|
||||||
#define ASSERT_THE_BIRD_LOCKED ({ if (!the_bird_locked()) bug("The BIRD lock must be locked here: %s:%d", __FILE__, __LINE__); })
|
#define ASSERT_THE_BIRD_LOCKED ({ if (!the_bird_locked()) bug("The BIRD lock must be locked here: %s:%d", __FILE__, __LINE__); })
|
||||||
|
|
||||||
/* Unwind stored lock state helpers */
|
/*
|
||||||
|
* RW spinlocks
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define RWS_READ_PENDING_POS 0
|
||||||
|
#define RWS_READ_ACTIVE_POS 20
|
||||||
|
#define RWS_WRITE_PENDING_POS 40
|
||||||
|
#define RWS_WRITE_ACTIVE_POS 56
|
||||||
|
|
||||||
|
#define RWS_READ_PENDING (1ULL << RWS_READ_PENDING_POS)
|
||||||
|
#define RWS_READ_ACTIVE (1ULL << RWS_READ_ACTIVE_POS)
|
||||||
|
#define RWS_WRITE_PENDING (1ULL << RWS_WRITE_PENDING_POS)
|
||||||
|
#define RWS_WRITE_ACTIVE (1ULL << RWS_WRITE_ACTIVE_POS)
|
||||||
|
|
||||||
|
#define RWS_READ_PENDING_MASK (RWS_READ_ACTIVE - 1)
|
||||||
|
#define RWS_READ_ACTIVE_MASK ((RWS_WRITE_PENDING - 1) & ~(RWS_READ_ACTIVE - 1))
|
||||||
|
#define RWS_WRITE_PENDING_MASK ((RWS_WRITE_ACTIVE - 1) & ~(RWS_WRITE_PENDING - 1))
|
||||||
|
#define RWS_WRITE_ACTIVE_MASK (~(RWS_WRITE_ACTIVE - 1))
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u64 _Atomic spin;
|
||||||
|
} rw_spinlock;
|
||||||
|
|
||||||
|
#define MAX_RWS_AT_ONCE 32
|
||||||
|
extern _Thread_local rw_spinlock *rw_spinlocks_taken[MAX_RWS_AT_ONCE];
|
||||||
|
extern _Thread_local btime rw_spinlocks_time[MAX_RWS_AT_ONCE];
|
||||||
|
extern _Thread_local u32 rw_spinlocks_taken_cnt;
|
||||||
|
extern _Thread_local u32 rw_spinlocks_taken_write;
|
||||||
|
|
||||||
|
/* Borrowed from lib/timer.h */
|
||||||
|
btime current_time_now(void);
|
||||||
|
|
||||||
|
#ifdef DEBUGGING
|
||||||
|
static inline void rws_mark(rw_spinlock *p, _Bool write, _Bool lock)
|
||||||
|
{
|
||||||
|
if (lock) {
|
||||||
|
ASSERT_DIE(rw_spinlocks_taken_cnt < MAX_RWS_AT_ONCE);
|
||||||
|
if (write)
|
||||||
|
rw_spinlocks_taken_write |= (1 << rw_spinlocks_taken_cnt);
|
||||||
|
else
|
||||||
|
rw_spinlocks_taken_write &= ~(1 << rw_spinlocks_taken_cnt);
|
||||||
|
rw_spinlocks_time[rw_spinlocks_taken_cnt] = current_time_now();
|
||||||
|
rw_spinlocks_taken[rw_spinlocks_taken_cnt++] = p;
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ASSERT_DIE(rw_spinlocks_taken_cnt > 0);
|
||||||
|
ASSERT_DIE(rw_spinlocks_taken[--rw_spinlocks_taken_cnt] == p);
|
||||||
|
ASSERT_DIE(!(rw_spinlocks_taken_write & (1 << rw_spinlocks_taken_cnt)) == !write);
|
||||||
|
btime tdif = current_time_now() - rw_spinlocks_time[rw_spinlocks_taken_cnt];
|
||||||
|
if (tdif > 1 S_)
|
||||||
|
log(L_WARN "Spent an alarming time %t s in spinlock %p (%s); "
|
||||||
|
"if this happens often to you, please contact the developers.",
|
||||||
|
tdif, p, write ? "write" : "read");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define rws_mark(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline void rws_init(rw_spinlock *p)
|
||||||
|
{
|
||||||
|
atomic_store_explicit(&p->spin, 0, memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void rws_read_lock(rw_spinlock *p)
|
||||||
|
{
|
||||||
|
u64 old = atomic_fetch_add_explicit(&p->spin, RWS_READ_PENDING, memory_order_acquire);
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
/* Wait until all writers end */
|
||||||
|
while (old & (RWS_WRITE_PENDING_MASK | RWS_WRITE_ACTIVE_MASK))
|
||||||
|
{
|
||||||
|
birdloop_yield();
|
||||||
|
old = atomic_load_explicit(&p->spin, memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert to active */
|
||||||
|
old = atomic_fetch_add_explicit(&p->spin, RWS_READ_ACTIVE - RWS_READ_PENDING, memory_order_acq_rel);
|
||||||
|
|
||||||
|
if (old & RWS_WRITE_ACTIVE_MASK)
|
||||||
|
/* Oh but some writer was faster */
|
||||||
|
old = atomic_fetch_sub_explicit(&p->spin, RWS_READ_ACTIVE - RWS_READ_PENDING, memory_order_acq_rel);
|
||||||
|
else
|
||||||
|
/* No writers, approved */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rws_mark(p, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void rws_read_unlock(rw_spinlock *p)
|
||||||
|
{
|
||||||
|
rws_mark(p, 0, 0);
|
||||||
|
u64 old = atomic_fetch_sub_explicit(&p->spin, RWS_READ_ACTIVE, memory_order_release);
|
||||||
|
ASSERT_DIE(old & RWS_READ_ACTIVE_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void rws_write_lock(rw_spinlock *p)
|
||||||
|
{
|
||||||
|
u64 old = atomic_fetch_add_explicit(&p->spin, RWS_WRITE_PENDING, memory_order_acquire);
|
||||||
|
|
||||||
|
/* Wait until all active readers end */
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
while (old & (RWS_READ_ACTIVE_MASK | RWS_WRITE_ACTIVE_MASK))
|
||||||
|
{
|
||||||
|
birdloop_yield();
|
||||||
|
old = atomic_load_explicit(&p->spin, memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark self as active */
|
||||||
|
u64 updated = atomic_fetch_or_explicit(&p->spin, RWS_WRITE_ACTIVE, memory_order_acquire);
|
||||||
|
|
||||||
|
/* And it's us */
|
||||||
|
if (!(updated & RWS_WRITE_ACTIVE))
|
||||||
|
{
|
||||||
|
if (updated & RWS_READ_ACTIVE_MASK)
|
||||||
|
/* But some reader was faster */
|
||||||
|
atomic_fetch_and_explicit(&p->spin, ~RWS_WRITE_ACTIVE, memory_order_release);
|
||||||
|
else
|
||||||
|
/* No readers, approved */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* It's us, then we aren't actually pending */
|
||||||
|
u64 updated = atomic_fetch_sub_explicit(&p->spin, RWS_WRITE_PENDING, memory_order_acquire);
|
||||||
|
ASSERT_DIE(updated & RWS_WRITE_PENDING_MASK);
|
||||||
|
rws_mark(p, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void rws_write_unlock(rw_spinlock *p)
|
||||||
|
{
|
||||||
|
rws_mark(p, 1, 0);
|
||||||
|
u64 old = atomic_fetch_and_explicit(&p->spin, ~RWS_WRITE_ACTIVE, memory_order_release);
|
||||||
|
ASSERT_DIE(old & RWS_WRITE_ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unwind stored lock state helpers
|
||||||
|
*/
|
||||||
struct locking_unwind_status {
|
struct locking_unwind_status {
|
||||||
struct lock_order *desired;
|
struct lock_order *desired;
|
||||||
enum {
|
enum {
|
||||||
|
@ -77,6 +77,96 @@ t_locking(void)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define RWS_DATASIZE 333
|
||||||
|
#define RWS_THREADS 128
|
||||||
|
|
||||||
|
struct rws_test_data {
|
||||||
|
int data[RWS_DATASIZE];
|
||||||
|
rw_spinlock rws[RWS_DATASIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
static void *
|
||||||
|
rwspin_thread_run(void *_rtd)
|
||||||
|
{
|
||||||
|
struct rws_test_data *d = _rtd;
|
||||||
|
|
||||||
|
for (_Bool sorted = 0; !sorted++; )
|
||||||
|
{
|
||||||
|
for (int i=0; (i<RWS_DATASIZE-1) && sorted; i++)
|
||||||
|
{
|
||||||
|
rws_read_lock(&d->rws[i]);
|
||||||
|
rws_read_lock(&d->rws[i+1]);
|
||||||
|
|
||||||
|
ASSERT_DIE(d->data[i] >= 0);
|
||||||
|
ASSERT_DIE(d->data[i+1] >= 0);
|
||||||
|
if (d->data[i] > d->data[i+1])
|
||||||
|
sorted = 0;
|
||||||
|
|
||||||
|
rws_read_unlock(&d->rws[i+1]);
|
||||||
|
rws_read_unlock(&d->rws[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0; (i<RWS_DATASIZE-1); i++)
|
||||||
|
{
|
||||||
|
rws_write_lock(&d->rws[i]);
|
||||||
|
rws_write_lock(&d->rws[i+1]);
|
||||||
|
|
||||||
|
int first = d->data[i];
|
||||||
|
int second = d->data[i+1];
|
||||||
|
|
||||||
|
ASSERT_DIE(first >= 0);
|
||||||
|
ASSERT_DIE(second >= 0);
|
||||||
|
|
||||||
|
d->data[i] = d->data[i+1] = -1;
|
||||||
|
|
||||||
|
if (first > second)
|
||||||
|
{
|
||||||
|
d->data[i] = second;
|
||||||
|
d->data[i+1] = first;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d->data[i] = first;
|
||||||
|
d->data[i+1] = second;
|
||||||
|
}
|
||||||
|
|
||||||
|
rws_write_unlock(&d->rws[i+1]);
|
||||||
|
rws_write_unlock(&d->rws[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
t_rwspin(void)
|
||||||
|
{
|
||||||
|
struct rws_test_data d;
|
||||||
|
|
||||||
|
/* Setup an array to sort */
|
||||||
|
for (int i=0; i<RWS_DATASIZE; i++)
|
||||||
|
d.data[i] = RWS_DATASIZE-i-1;
|
||||||
|
|
||||||
|
/* Spinlock for every place */
|
||||||
|
for (int i=0; i<RWS_DATASIZE; i++)
|
||||||
|
rws_init(&d.rws[i]);
|
||||||
|
|
||||||
|
/* Start the threads */
|
||||||
|
pthread_t thr[RWS_THREADS];
|
||||||
|
for (int i=0; i<RWS_THREADS; i++)
|
||||||
|
bt_assert(pthread_create(&thr[i], NULL, rwspin_thread_run, &d) == 0);
|
||||||
|
|
||||||
|
/* Wait for the threads */
|
||||||
|
for (int i=0; i<RWS_THREADS; i++)
|
||||||
|
bt_assert(pthread_join(thr[i], NULL) == 0);
|
||||||
|
|
||||||
|
for (int i=0; i<RWS_DATASIZE; i++)
|
||||||
|
bt_assert(d.data[i] == i);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
@ -84,6 +174,7 @@ main(int argc, char **argv)
|
|||||||
bt_bird_init();
|
bt_bird_init();
|
||||||
|
|
||||||
bt_test_suite(t_locking, "Testing locks");
|
bt_test_suite(t_locking, "Testing locks");
|
||||||
|
bt_test_suite(t_rwspin, "Testing rw spinlock");
|
||||||
|
|
||||||
return bt_exit_value();
|
return bt_exit_value();
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ static inline void rcu_read_lock(void)
|
|||||||
static inline void rcu_read_unlock(void)
|
static inline void rcu_read_unlock(void)
|
||||||
{
|
{
|
||||||
/* Just decrement the nesting counter; when unlocked, nobody cares */
|
/* Just decrement the nesting counter; when unlocked, nobody cares */
|
||||||
atomic_fetch_sub(&this_rcu_thread.ctl, RCU_NEST_CNT);
|
atomic_fetch_sub_explicit(&this_rcu_thread.ctl, RCU_NEST_CNT, memory_order_acq_rel);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline _Bool rcu_read_active(void)
|
static inline _Bool rcu_read_active(void)
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#define _BIRD_BIRD_H_
|
#define _BIRD_BIRD_H_
|
||||||
|
|
||||||
#include "lib/birdlib.h"
|
#include "lib/birdlib.h"
|
||||||
|
#include "lib/locking.h"
|
||||||
#include "lib/ip.h"
|
#include "lib/ip.h"
|
||||||
#include "lib/net.h"
|
#include "lib/net.h"
|
||||||
|
|
||||||
|
@ -35,6 +35,11 @@
|
|||||||
* Locking subsystem
|
* Locking subsystem
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
_Thread_local rw_spinlock *rw_spinlocks_taken[MAX_RWS_AT_ONCE];
|
||||||
|
_Thread_local btime rw_spinlocks_time[MAX_RWS_AT_ONCE];
|
||||||
|
_Thread_local u32 rw_spinlocks_taken_cnt;
|
||||||
|
_Thread_local u32 rw_spinlocks_taken_write;
|
||||||
|
|
||||||
_Thread_local struct lock_order locking_stack = {};
|
_Thread_local struct lock_order locking_stack = {};
|
||||||
_Thread_local struct domain_generic **last_locked = NULL;
|
_Thread_local struct domain_generic **last_locked = NULL;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user