mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-11-04 15:30:26 +00:00 
			
		
		
		
	Sometimes the easiest solution is hardest to find... the whole point of all this "static const", aliasing, & co. was to make "MTYPE_FOO" usable without adding the extra & as in "&MTYPE_FOO". Making it a size-1 array does that perfectly through the magic of ISO C array decay... Signed-off-by: David Lamparter <equinox@diac24.net>
		
			
				
	
	
		
			528 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			528 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2017-19  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.
 | 
						|
 */
 | 
						|
 | 
						|
/* implementation notes:  this is an epoch-based RCU implementation.  rcu_seq
 | 
						|
 * (global variable) counts the current epoch.  Threads hold a specific epoch
 | 
						|
 * in rcu_read_lock().  This is the oldest epoch a thread might be accessing
 | 
						|
 * data from.
 | 
						|
 *
 | 
						|
 * The rcu_seq global is only pushed forward on rcu_read_lock() and
 | 
						|
 * rcu_read_unlock() calls.  This makes things a tad more efficient since
 | 
						|
 * those are the only places it matters:
 | 
						|
 * - on rcu_read_lock, we don't want to hold an old epoch pointlessly
 | 
						|
 * - on rcu_read_unlock, we want to make sure we're not stuck on an old epoch
 | 
						|
 *   when heading into a long idle period where no thread holds RCU
 | 
						|
 *
 | 
						|
 * rcu_thread structures themselves are RCU-free'd.
 | 
						|
 *
 | 
						|
 * rcu_head structures are the most iffy;  normally for an ATOMLIST we would
 | 
						|
 * need to make sure we use rcu_free or pthread_rwlock to deallocate old items
 | 
						|
 * to prevent ABA or use-after-free problems.  However, our ATOMLIST code
 | 
						|
 * guarantees that if the list remains non-empty in all cases, we only need
 | 
						|
 * the "last" pointer to do an "add_tail()", i.e. we can't run into ABA/UAF
 | 
						|
 * issues - but we do need to keep at least 1 item on the list.
 | 
						|
 *
 | 
						|
 * (Search the atomlist code for all uses of "last")
 | 
						|
 */
 | 
						|
 | 
						|
#ifdef HAVE_CONFIG_H
 | 
						|
#include "config.h"
 | 
						|
#endif
 | 
						|
 | 
						|
#include <pthread.h>
 | 
						|
#ifdef HAVE_PTHREAD_NP_H
 | 
						|
#include <pthread_np.h>
 | 
						|
#endif
 | 
						|
#include <string.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <signal.h>
 | 
						|
 | 
						|
#include "frrcu.h"
 | 
						|
#include "seqlock.h"
 | 
						|
#include "atomlist.h"
 | 
						|
 | 
						|
DEFINE_MTYPE_STATIC(LIB, RCU_THREAD,    "RCU thread")
 | 
						|
DEFINE_MTYPE_STATIC(LIB, RCU_NEXT,      "RCU sequence barrier")
 | 
						|
 | 
						|
DECLARE_ATOMLIST(rcu_heads, struct rcu_head, head)
 | 
						|
 | 
						|
PREDECL_ATOMLIST(rcu_threads)
 | 
						|
struct rcu_thread {
 | 
						|
	struct rcu_threads_item head;
 | 
						|
 | 
						|
	struct rcu_head rcu_head;
 | 
						|
 | 
						|
	struct seqlock rcu;
 | 
						|
 | 
						|
	/* only accessed by thread itself, not atomic */
 | 
						|
	unsigned depth;
 | 
						|
};
 | 
						|
DECLARE_ATOMLIST(rcu_threads, struct rcu_thread, head)
 | 
						|
 | 
						|
static const struct rcu_action rcua_next  = { .type = RCUA_NEXT };
 | 
						|
static const struct rcu_action rcua_end   = { .type = RCUA_END };
 | 
						|
static const struct rcu_action rcua_close = { .type = RCUA_CLOSE };
 | 
						|
 | 
						|
struct rcu_next {
 | 
						|
	struct rcu_head head_free;
 | 
						|
	struct rcu_head head_next;
 | 
						|
};
 | 
						|
 | 
						|
#define rcu_free_internal(mtype, ptr, field)                                   \
 | 
						|
	do {                                                                   \
 | 
						|
		typeof(ptr) _ptr = (ptr);                                      \
 | 
						|
		struct rcu_head *_rcu_head = &_ptr->field;                     \
 | 
						|
		static const struct rcu_action _rcu_action = {                 \
 | 
						|
			.type = RCUA_FREE,                                     \
 | 
						|
			.u.free = {                                            \
 | 
						|
				.mt = mtype,                                   \
 | 
						|
				.offset = offsetof(typeof(*_ptr), field),      \
 | 
						|
			},                                                     \
 | 
						|
		};                                                             \
 | 
						|
		_rcu_head->action = &_rcu_action;                              \
 | 
						|
		rcu_heads_add_tail(&rcu_heads, _rcu_head);                     \
 | 
						|
	} while (0)
 | 
						|
 | 
						|
/* primary global RCU position */
 | 
						|
static struct seqlock rcu_seq;
 | 
						|
/* this is set to rcu_seq whenever something is added on the RCU queue.
 | 
						|
 * rcu_read_lock() and rcu_read_unlock() will then bump rcu_seq up one step.
 | 
						|
 */
 | 
						|
static _Atomic seqlock_val_t rcu_dirty;
 | 
						|
 | 
						|
static struct rcu_threads_head rcu_threads;
 | 
						|
static struct rcu_heads_head rcu_heads;
 | 
						|
 | 
						|
/* main thread & RCU sweeper have pre-setup rcu_thread structures.  The
 | 
						|
 * reasons are different:
 | 
						|
 *
 | 
						|
 * - rcu_thread_main is there because the main thread isn't started like
 | 
						|
 *   other threads, it's implicitly created when the program is started.  So
 | 
						|
 *   rcu_thread_main matches up implicitly.
 | 
						|
 *
 | 
						|
 * - rcu_thread_rcu isn't actually put on the rcu_threads list (makes no
 | 
						|
 *   sense really), it only exists so we can call RCU-using functions from
 | 
						|
 *   the RCU thread without special handling in rcu_read_lock/unlock.
 | 
						|
 */
 | 
						|
static struct rcu_thread rcu_thread_main;
 | 
						|
static struct rcu_thread rcu_thread_rcu;
 | 
						|
 | 
						|
static pthread_t rcu_pthread;
 | 
						|
static pthread_key_t rcu_thread_key;
 | 
						|
static bool rcu_active;
 | 
						|
 | 
						|
static void rcu_start(void);
 | 
						|
static void rcu_bump(void);
 | 
						|
 | 
						|
/*
 | 
						|
 * preinitialization for main thread
 | 
						|
 */
 | 
						|
static void rcu_thread_end(void *rcu_thread);
 | 
						|
 | 
						|
static void rcu_preinit(void) __attribute__((constructor));
 | 
						|
static void rcu_preinit(void)
 | 
						|
{
 | 
						|
	struct rcu_thread *rt;
 | 
						|
 | 
						|
	rt = &rcu_thread_main;
 | 
						|
	rt->depth = 1;
 | 
						|
	seqlock_init(&rt->rcu);
 | 
						|
	seqlock_acquire_val(&rt->rcu, SEQLOCK_STARTVAL);
 | 
						|
 | 
						|
	pthread_key_create(&rcu_thread_key, rcu_thread_end);
 | 
						|
	pthread_setspecific(rcu_thread_key, rt);
 | 
						|
 | 
						|
	rcu_threads_add_tail(&rcu_threads, rt);
 | 
						|
 | 
						|
	/* RCU sweeper's rcu_thread is a dummy, NOT added to rcu_threads */
 | 
						|
	rt = &rcu_thread_rcu;
 | 
						|
	rt->depth = 1;
 | 
						|
 | 
						|
	seqlock_init(&rcu_seq);
 | 
						|
	seqlock_acquire_val(&rcu_seq, SEQLOCK_STARTVAL);
 | 
						|
}
 | 
						|
 | 
						|
static struct rcu_thread *rcu_self(void)
 | 
						|
{
 | 
						|
	return (struct rcu_thread *)pthread_getspecific(rcu_thread_key);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * thread management (for the non-main thread)
 | 
						|
 */
 | 
						|
struct rcu_thread *rcu_thread_prepare(void)
 | 
						|
{
 | 
						|
	struct rcu_thread *rt, *cur;
 | 
						|
 | 
						|
	rcu_assert_read_locked();
 | 
						|
 | 
						|
	if (!rcu_active)
 | 
						|
		rcu_start();
 | 
						|
 | 
						|
	cur = rcu_self();
 | 
						|
	assert(cur->depth);
 | 
						|
 | 
						|
	/* new thread always starts with rcu_read_lock held at depth 1, and
 | 
						|
	 * holding the same epoch as the parent (this makes it possible to
 | 
						|
	 * use RCU for things passed into the thread through its arg)
 | 
						|
	 */
 | 
						|
	rt = XCALLOC(MTYPE_RCU_THREAD, sizeof(*rt));
 | 
						|
	rt->depth = 1;
 | 
						|
 | 
						|
	seqlock_init(&rt->rcu);
 | 
						|
	seqlock_acquire(&rt->rcu, &cur->rcu);
 | 
						|
 | 
						|
	rcu_threads_add_tail(&rcu_threads, rt);
 | 
						|
 | 
						|
	return rt;
 | 
						|
}
 | 
						|
 | 
						|
void rcu_thread_start(struct rcu_thread *rt)
 | 
						|
{
 | 
						|
	pthread_setspecific(rcu_thread_key, rt);
 | 
						|
}
 | 
						|
 | 
						|
void rcu_thread_unprepare(struct rcu_thread *rt)
 | 
						|
{
 | 
						|
	if (rt == &rcu_thread_rcu)
 | 
						|
		return;
 | 
						|
 | 
						|
	rt->depth = 1;
 | 
						|
	seqlock_acquire(&rt->rcu, &rcu_seq);
 | 
						|
 | 
						|
	rcu_bump();
 | 
						|
	if (rt != &rcu_thread_main)
 | 
						|
		/* this free() happens after seqlock_release() below */
 | 
						|
		rcu_free_internal(MTYPE_RCU_THREAD, rt, rcu_head);
 | 
						|
 | 
						|
	rcu_threads_del(&rcu_threads, rt);
 | 
						|
	seqlock_release(&rt->rcu);
 | 
						|
}
 | 
						|
 | 
						|
static void rcu_thread_end(void *rtvoid)
 | 
						|
{
 | 
						|
	struct rcu_thread *rt = rtvoid;
 | 
						|
	rcu_thread_unprepare(rt);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * main RCU control aspects
 | 
						|
 */
 | 
						|
 | 
						|
static void rcu_bump(void)
 | 
						|
{
 | 
						|
	struct rcu_next *rn;
 | 
						|
 | 
						|
	rn = XMALLOC(MTYPE_RCU_NEXT, sizeof(*rn));
 | 
						|
 | 
						|
	/* note: each RCUA_NEXT item corresponds to exactly one seqno bump.
 | 
						|
	 * This means we don't need to communicate which seqno is which
 | 
						|
	 * RCUA_NEXT, since we really don't care.
 | 
						|
	 */
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Important race condition:  while rcu_heads_add_tail is executing,
 | 
						|
	 * there is an intermediate point where the rcu_heads "last" pointer
 | 
						|
	 * already points to rn->head_next, but rn->head_next isn't added to
 | 
						|
	 * the list yet.  That means any other "add_tail" calls append to this
 | 
						|
	 * item, which isn't fully on the list yet.  Freeze this thread at
 | 
						|
	 * that point and look at another thread doing a rcu_bump.  It adds
 | 
						|
	 * these two items and then does a seqlock_bump.  But the rcu_heads
 | 
						|
	 * list is still "interrupted" and there's no RCUA_NEXT on the list
 | 
						|
	 * yet (from either the frozen thread or the second thread).  So
 | 
						|
	 * rcu_main() might actually hit the end of the list at the
 | 
						|
	 * "interrupt".
 | 
						|
	 *
 | 
						|
	 * This situation is prevented by requiring that rcu_read_lock is held
 | 
						|
	 * for any calls to rcu_bump, since if we're holding the current RCU
 | 
						|
	 * epoch, that means rcu_main can't be chewing on rcu_heads and hit
 | 
						|
	 * that interruption point.  Only by the time the thread has continued
 | 
						|
	 * to rcu_read_unlock() - and therefore completed the add_tail - the
 | 
						|
	 * RCU sweeper gobbles up the epoch and can be sure to find at least
 | 
						|
	 * the RCUA_NEXT and RCUA_FREE items on rcu_heads.
 | 
						|
	 */
 | 
						|
	rn->head_next.action = &rcua_next;
 | 
						|
	rcu_heads_add_tail(&rcu_heads, &rn->head_next);
 | 
						|
 | 
						|
	/* free rn that we allocated above.
 | 
						|
	 *
 | 
						|
	 * This is INTENTIONALLY not built into the RCUA_NEXT action.  This
 | 
						|
	 * ensures that after the action above is popped off the queue, there
 | 
						|
	 * is still at least 1 item on the RCU queue.  This means we never
 | 
						|
	 * delete the last item, which is extremely important since it keeps
 | 
						|
	 * the atomlist ->last pointer alive and well.
 | 
						|
	 *
 | 
						|
	 * If we were to "run dry" on the RCU queue, add_tail may run into the
 | 
						|
	 * "last item is being deleted - start over" case, and then we may end
 | 
						|
	 * up accessing old RCU queue items that are already free'd.
 | 
						|
	 */
 | 
						|
	rcu_free_internal(MTYPE_RCU_NEXT, rn, head_free);
 | 
						|
 | 
						|
	/* Only allow the RCU sweeper to run after these 2 items are queued.
 | 
						|
	 *
 | 
						|
	 * If another thread enqueues some RCU action in the intermediate
 | 
						|
	 * window here, nothing bad happens - the queued action is associated
 | 
						|
	 * with a larger seq# than strictly necessary.  Thus, it might get
 | 
						|
	 * executed a bit later, but that's not a problem.
 | 
						|
	 *
 | 
						|
	 * If another thread acquires the read lock in this window, it holds
 | 
						|
	 * the previous epoch, but its RCU queue actions will be in the next
 | 
						|
	 * epoch.  This isn't a problem either, just a tad inefficient.
 | 
						|
	 */
 | 
						|
	seqlock_bump(&rcu_seq);
 | 
						|
}
 | 
						|
 | 
						|
static void rcu_bump_maybe(void)
 | 
						|
{
 | 
						|
	seqlock_val_t dirty;
 | 
						|
 | 
						|
	dirty = atomic_load_explicit(&rcu_dirty, memory_order_relaxed);
 | 
						|
	/* no problem if we race here and multiple threads bump rcu_seq;
 | 
						|
	 * bumping too much causes no issues while not bumping enough will
 | 
						|
	 * result in delayed cleanup
 | 
						|
	 */
 | 
						|
	if (dirty == seqlock_cur(&rcu_seq))
 | 
						|
		rcu_bump();
 | 
						|
}
 | 
						|
 | 
						|
void rcu_read_lock(void)
 | 
						|
{
 | 
						|
	struct rcu_thread *rt = rcu_self();
 | 
						|
 | 
						|
	assert(rt);
 | 
						|
	if (rt->depth++ > 0)
 | 
						|
		return;
 | 
						|
 | 
						|
	seqlock_acquire(&rt->rcu, &rcu_seq);
 | 
						|
	/* need to hold RCU for bump ... */
 | 
						|
	rcu_bump_maybe();
 | 
						|
	/* ... but no point in holding the old epoch if we just bumped */
 | 
						|
	seqlock_acquire(&rt->rcu, &rcu_seq);
 | 
						|
}
 | 
						|
 | 
						|
void rcu_read_unlock(void)
 | 
						|
{
 | 
						|
	struct rcu_thread *rt = rcu_self();
 | 
						|
 | 
						|
	assert(rt && rt->depth);
 | 
						|
	if (--rt->depth > 0)
 | 
						|
		return;
 | 
						|
	rcu_bump_maybe();
 | 
						|
	seqlock_release(&rt->rcu);
 | 
						|
}
 | 
						|
 | 
						|
void rcu_assert_read_locked(void)
 | 
						|
{
 | 
						|
	struct rcu_thread *rt = rcu_self();
 | 
						|
	assert(rt && rt->depth && seqlock_held(&rt->rcu));
 | 
						|
}
 | 
						|
 | 
						|
void rcu_assert_read_unlocked(void)
 | 
						|
{
 | 
						|
	struct rcu_thread *rt = rcu_self();
 | 
						|
	assert(rt && !rt->depth && !seqlock_held(&rt->rcu));
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * RCU resource-release thread
 | 
						|
 */
 | 
						|
 | 
						|
static void *rcu_main(void *arg);
 | 
						|
 | 
						|
static void rcu_start(void)
 | 
						|
{
 | 
						|
	/* ensure we never handle signals on the RCU thread by blocking
 | 
						|
	 * everything here (new thread inherits signal mask)
 | 
						|
	 */
 | 
						|
	sigset_t oldsigs, blocksigs;
 | 
						|
 | 
						|
	sigfillset(&blocksigs);
 | 
						|
	pthread_sigmask(SIG_BLOCK, &blocksigs, &oldsigs);
 | 
						|
 | 
						|
	rcu_active = true;
 | 
						|
 | 
						|
	assert(!pthread_create(&rcu_pthread, NULL, rcu_main, NULL));
 | 
						|
 | 
						|
	pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
 | 
						|
 | 
						|
#ifdef HAVE_PTHREAD_SETNAME_NP
 | 
						|
# ifdef GNU_LINUX
 | 
						|
	pthread_setname_np(rcu_pthread, "RCU sweeper");
 | 
						|
# elif defined(__NetBSD__)
 | 
						|
	pthread_setname_np(rcu_pthread, "RCU sweeper", NULL);
 | 
						|
# endif
 | 
						|
#elif defined(HAVE_PTHREAD_SET_NAME_NP)
 | 
						|
	pthread_set_name_np(rcu_pthread, "RCU sweeper");
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
static void rcu_do(struct rcu_head *rh)
 | 
						|
{
 | 
						|
	struct rcu_head_close *rhc;
 | 
						|
	void *p;
 | 
						|
 | 
						|
	switch (rh->action->type) {
 | 
						|
	case RCUA_FREE:
 | 
						|
		p = (char *)rh - rh->action->u.free.offset;
 | 
						|
		if (rh->action->u.free.mt)
 | 
						|
			qfree(rh->action->u.free.mt, p);
 | 
						|
		else
 | 
						|
			free(p);
 | 
						|
		break;
 | 
						|
	case RCUA_CLOSE:
 | 
						|
		rhc = container_of(rh, struct rcu_head_close,
 | 
						|
				   rcu_head);
 | 
						|
		close(rhc->fd);
 | 
						|
		break;
 | 
						|
	case RCUA_CALL:
 | 
						|
		p = (char *)rh - rh->action->u.call.offset;
 | 
						|
		rh->action->u.call.fptr(p);
 | 
						|
		break;
 | 
						|
 | 
						|
	case RCUA_INVALID:
 | 
						|
	case RCUA_NEXT:
 | 
						|
	case RCUA_END:
 | 
						|
	default:
 | 
						|
		assert(0);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void rcu_watchdog(struct rcu_thread *rt)
 | 
						|
{
 | 
						|
#if 0
 | 
						|
	/* future work: print a backtrace for the thread that's holding up
 | 
						|
	 * RCU.  The only (good) way of doing that is to send a signal to the
 | 
						|
	 * other thread, save away the backtrace in the signal handler, and
 | 
						|
	 * block here until the signal is done processing.
 | 
						|
	 *
 | 
						|
	 * Just haven't implemented that yet.
 | 
						|
	 */
 | 
						|
	fprintf(stderr, "RCU watchdog %p\n", rt);
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
static void *rcu_main(void *arg)
 | 
						|
{
 | 
						|
	struct rcu_thread *rt;
 | 
						|
	struct rcu_head *rh = NULL;
 | 
						|
	bool end = false;
 | 
						|
	struct timespec maxwait;
 | 
						|
 | 
						|
	seqlock_val_t rcuval = SEQLOCK_STARTVAL;
 | 
						|
 | 
						|
	pthread_setspecific(rcu_thread_key, &rcu_thread_rcu);
 | 
						|
 | 
						|
	while (!end) {
 | 
						|
		seqlock_wait(&rcu_seq, rcuval);
 | 
						|
 | 
						|
		/* RCU watchdog timeout, TODO: configurable value */
 | 
						|
		clock_gettime(CLOCK_MONOTONIC, &maxwait);
 | 
						|
		maxwait.tv_nsec += 100 * 1000 * 1000;
 | 
						|
		if (maxwait.tv_nsec >= 1000000000) {
 | 
						|
			maxwait.tv_sec++;
 | 
						|
			maxwait.tv_nsec -= 1000000000;
 | 
						|
		}
 | 
						|
 | 
						|
		frr_each (rcu_threads, &rcu_threads, rt)
 | 
						|
			if (!seqlock_timedwait(&rt->rcu, rcuval, &maxwait)) {
 | 
						|
				rcu_watchdog(rt);
 | 
						|
				seqlock_wait(&rt->rcu, rcuval);
 | 
						|
			}
 | 
						|
 | 
						|
		while ((rh = rcu_heads_pop(&rcu_heads))) {
 | 
						|
			if (rh->action->type == RCUA_NEXT)
 | 
						|
				break;
 | 
						|
			else if (rh->action->type == RCUA_END)
 | 
						|
				end = true;
 | 
						|
			else
 | 
						|
				rcu_do(rh);
 | 
						|
		}
 | 
						|
 | 
						|
		rcuval += SEQLOCK_INCR;
 | 
						|
	}
 | 
						|
 | 
						|
	/* rcu_shutdown can only be called singlethreaded, and it does a
 | 
						|
	 * pthread_join, so it should be impossible that anything ended up
 | 
						|
	 * on the queue after RCUA_END
 | 
						|
	 */
 | 
						|
#if 1
 | 
						|
	assert(!rcu_heads_first(&rcu_heads));
 | 
						|
#else
 | 
						|
	while ((rh = rcu_heads_pop(&rcu_heads)))
 | 
						|
		if (rh->action->type >= RCUA_FREE)
 | 
						|
			rcu_do(rh);
 | 
						|
#endif
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
void rcu_shutdown(void)
 | 
						|
{
 | 
						|
	static struct rcu_head rcu_head_end;
 | 
						|
	struct rcu_thread *rt = rcu_self();
 | 
						|
	void *retval;
 | 
						|
 | 
						|
	if (!rcu_active)
 | 
						|
		return;
 | 
						|
 | 
						|
	rcu_assert_read_locked();
 | 
						|
	assert(rcu_threads_count(&rcu_threads) == 1);
 | 
						|
 | 
						|
	rcu_enqueue(&rcu_head_end, &rcua_end);
 | 
						|
 | 
						|
	rt->depth = 0;
 | 
						|
	seqlock_release(&rt->rcu);
 | 
						|
	seqlock_release(&rcu_seq);
 | 
						|
	rcu_active = false;
 | 
						|
 | 
						|
	/* clearing rcu_active is before pthread_join in case we hang in
 | 
						|
	 * pthread_join & get a SIGTERM or something - in that case, just
 | 
						|
	 * ignore the maybe-still-running RCU thread
 | 
						|
	 */
 | 
						|
	if (pthread_join(rcu_pthread, &retval) == 0) {
 | 
						|
		seqlock_acquire_val(&rcu_seq, SEQLOCK_STARTVAL);
 | 
						|
		seqlock_acquire_val(&rt->rcu, SEQLOCK_STARTVAL);
 | 
						|
		rt->depth = 1;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * RCU'd free functions
 | 
						|
 */
 | 
						|
 | 
						|
void rcu_enqueue(struct rcu_head *rh, const struct rcu_action *action)
 | 
						|
{
 | 
						|
	/* refer to rcu_bump() for why we need to hold RCU when adding items
 | 
						|
	 * to rcu_heads
 | 
						|
	 */
 | 
						|
	rcu_assert_read_locked();
 | 
						|
 | 
						|
	rh->action = action;
 | 
						|
 | 
						|
	if (!rcu_active) {
 | 
						|
		rcu_do(rh);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	rcu_heads_add_tail(&rcu_heads, rh);
 | 
						|
	atomic_store_explicit(&rcu_dirty, seqlock_cur(&rcu_seq),
 | 
						|
			      memory_order_relaxed);
 | 
						|
}
 | 
						|
 | 
						|
void rcu_close(struct rcu_head_close *rhc, int fd)
 | 
						|
{
 | 
						|
	rhc->fd = fd;
 | 
						|
	rcu_enqueue(&rhc->rcu_head, &rcua_close);
 | 
						|
}
 |