mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-11-04 01:38:20 +00:00 
			
		
		
		
	Creating any threads before we fork() into the background (if `-d` is given) is an extremely dangerous footgun; the threads are created in the parent and terminated when that exits. This is extra dangerous because while testing, you'd often run the daemon in foreground without `-d`, and everything works as expected. Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
		
			
				
	
	
		
			322 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			322 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Utilities and interfaces for managing POSIX threads within FRR.
 | 
						|
 * Copyright (C) 2017  Cumulus Networks, Inc.
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or modify
 | 
						|
 * it under the terms of the GNU General Public License as published by
 | 
						|
 * the Free Software Foundation; either version 2 of the License, or
 | 
						|
 * (at your option) any later version.
 | 
						|
 *
 | 
						|
 * This program 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
 | 
						|
 * General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License along
 | 
						|
 * with this program; see the file COPYING; if not, write to the Free Software
 | 
						|
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 | 
						|
 */
 | 
						|
 | 
						|
#include <zebra.h>
 | 
						|
#include <pthread.h>
 | 
						|
#ifdef HAVE_PTHREAD_NP_H
 | 
						|
#include <pthread_np.h>
 | 
						|
#endif
 | 
						|
#include <sched.h>
 | 
						|
 | 
						|
#include "frr_pthread.h"
 | 
						|
#include "memory.h"
 | 
						|
#include "linklist.h"
 | 
						|
#include "zlog.h"
 | 
						|
#include "libfrr.h"
 | 
						|
#include "libfrr_trace.h"
 | 
						|
 | 
						|
DEFINE_MTYPE_STATIC(LIB, FRR_PTHREAD, "FRR POSIX Thread");
 | 
						|
DEFINE_MTYPE_STATIC(LIB, PTHREAD_PRIM, "POSIX sync primitives");
 | 
						|
 | 
						|
/* default frr_pthread start/stop routine prototypes */
 | 
						|
static void *fpt_run(void *arg);
 | 
						|
static int fpt_halt(struct frr_pthread *fpt, void **res);
 | 
						|
 | 
						|
/* misc sigs */
 | 
						|
static void frr_pthread_destroy_nolock(struct frr_pthread *fpt);
 | 
						|
 | 
						|
/* default frr_pthread attributes */
 | 
						|
const struct frr_pthread_attr frr_pthread_attr_default = {
 | 
						|
	.start = fpt_run,
 | 
						|
	.stop = fpt_halt,
 | 
						|
};
 | 
						|
 | 
						|
/* list to keep track of all frr_pthreads */
 | 
						|
static pthread_mutex_t frr_pthread_list_mtx = PTHREAD_MUTEX_INITIALIZER;
 | 
						|
static struct list *frr_pthread_list;
 | 
						|
 | 
						|
/* ------------------------------------------------------------------------ */
 | 
						|
 | 
						|
void frr_pthread_init(void)
 | 
						|
{
 | 
						|
	frr_with_mutex(&frr_pthread_list_mtx) {
 | 
						|
		frr_pthread_list = list_new();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void frr_pthread_finish(void)
 | 
						|
{
 | 
						|
	frr_pthread_stop_all();
 | 
						|
 | 
						|
	frr_with_mutex(&frr_pthread_list_mtx) {
 | 
						|
		struct listnode *n, *nn;
 | 
						|
		struct frr_pthread *fpt;
 | 
						|
 | 
						|
		for (ALL_LIST_ELEMENTS(frr_pthread_list, n, nn, fpt)) {
 | 
						|
			listnode_delete(frr_pthread_list, fpt);
 | 
						|
			frr_pthread_destroy_nolock(fpt);
 | 
						|
		}
 | 
						|
 | 
						|
		list_delete(&frr_pthread_list);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
struct frr_pthread *frr_pthread_new(const struct frr_pthread_attr *attr,
 | 
						|
				    const char *name, const char *os_name)
 | 
						|
{
 | 
						|
	struct frr_pthread *fpt = NULL;
 | 
						|
 | 
						|
	attr = attr ? attr : &frr_pthread_attr_default;
 | 
						|
 | 
						|
	fpt = XCALLOC(MTYPE_FRR_PTHREAD, sizeof(struct frr_pthread));
 | 
						|
	/* initialize mutex */
 | 
						|
	pthread_mutex_init(&fpt->mtx, NULL);
 | 
						|
	/* create new thread master */
 | 
						|
	fpt->master = thread_master_create(name);
 | 
						|
	/* set attributes */
 | 
						|
	fpt->attr = *attr;
 | 
						|
	name = (name ? name : "Anonymous thread");
 | 
						|
	fpt->name = XSTRDUP(MTYPE_FRR_PTHREAD, name);
 | 
						|
	if (os_name)
 | 
						|
		strlcpy(fpt->os_name, os_name, OS_THREAD_NAMELEN);
 | 
						|
	else
 | 
						|
		strlcpy(fpt->os_name, name, OS_THREAD_NAMELEN);
 | 
						|
	/* initialize startup synchronization primitives */
 | 
						|
	fpt->running_cond_mtx = XCALLOC(
 | 
						|
		MTYPE_PTHREAD_PRIM, sizeof(pthread_mutex_t));
 | 
						|
	fpt->running_cond = XCALLOC(MTYPE_PTHREAD_PRIM,
 | 
						|
				    sizeof(pthread_cond_t));
 | 
						|
	pthread_mutex_init(fpt->running_cond_mtx, NULL);
 | 
						|
	pthread_cond_init(fpt->running_cond, NULL);
 | 
						|
 | 
						|
	frr_with_mutex(&frr_pthread_list_mtx) {
 | 
						|
		listnode_add(frr_pthread_list, fpt);
 | 
						|
	}
 | 
						|
 | 
						|
	return fpt;
 | 
						|
}
 | 
						|
 | 
						|
static void frr_pthread_destroy_nolock(struct frr_pthread *fpt)
 | 
						|
{
 | 
						|
	thread_master_free(fpt->master);
 | 
						|
	pthread_mutex_destroy(&fpt->mtx);
 | 
						|
	pthread_mutex_destroy(fpt->running_cond_mtx);
 | 
						|
	pthread_cond_destroy(fpt->running_cond);
 | 
						|
	XFREE(MTYPE_FRR_PTHREAD, fpt->name);
 | 
						|
	XFREE(MTYPE_PTHREAD_PRIM, fpt->running_cond_mtx);
 | 
						|
	XFREE(MTYPE_PTHREAD_PRIM, fpt->running_cond);
 | 
						|
	XFREE(MTYPE_FRR_PTHREAD, fpt);
 | 
						|
}
 | 
						|
 | 
						|
void frr_pthread_destroy(struct frr_pthread *fpt)
 | 
						|
{
 | 
						|
	frr_with_mutex(&frr_pthread_list_mtx) {
 | 
						|
		listnode_delete(frr_pthread_list, fpt);
 | 
						|
	}
 | 
						|
 | 
						|
	frr_pthread_destroy_nolock(fpt);
 | 
						|
}
 | 
						|
 | 
						|
int frr_pthread_set_name(struct frr_pthread *fpt)
 | 
						|
{
 | 
						|
	int ret = 0;
 | 
						|
 | 
						|
#ifdef HAVE_PTHREAD_SETNAME_NP
 | 
						|
# ifdef GNU_LINUX
 | 
						|
	ret = pthread_setname_np(fpt->thread, fpt->os_name);
 | 
						|
# elif defined(__NetBSD__)
 | 
						|
	ret = pthread_setname_np(fpt->thread, fpt->os_name, NULL);
 | 
						|
# endif
 | 
						|
#elif defined(HAVE_PTHREAD_SET_NAME_NP)
 | 
						|
	pthread_set_name_np(fpt->thread, fpt->os_name);
 | 
						|
#endif
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static void *frr_pthread_inner(void *arg)
 | 
						|
{
 | 
						|
	struct frr_pthread *fpt = arg;
 | 
						|
 | 
						|
	rcu_thread_start(fpt->rcu_thread);
 | 
						|
	return fpt->attr.start(fpt);
 | 
						|
}
 | 
						|
 | 
						|
int frr_pthread_run(struct frr_pthread *fpt, const pthread_attr_t *attr)
 | 
						|
{
 | 
						|
	int ret;
 | 
						|
	sigset_t oldsigs, blocksigs;
 | 
						|
 | 
						|
	assert(frr_is_after_fork || !"trying to start thread before fork()");
 | 
						|
 | 
						|
	/* Ensure we never handle signals on a background thread by blocking
 | 
						|
	 * everything here (new thread inherits signal mask)
 | 
						|
	 */
 | 
						|
	sigfillset(&blocksigs);
 | 
						|
	pthread_sigmask(SIG_BLOCK, &blocksigs, &oldsigs);
 | 
						|
 | 
						|
	frrtrace(1, frr_libfrr, frr_pthread_run, fpt->name);
 | 
						|
 | 
						|
	fpt->rcu_thread = rcu_thread_prepare();
 | 
						|
	ret = pthread_create(&fpt->thread, attr, frr_pthread_inner, fpt);
 | 
						|
 | 
						|
	/* Restore caller's signals */
 | 
						|
	pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Per pthread_create(3), the contents of fpt->thread are undefined if
 | 
						|
	 * pthread_create() did not succeed. Reset this value to zero.
 | 
						|
	 */
 | 
						|
	if (ret < 0) {
 | 
						|
		rcu_thread_unprepare(fpt->rcu_thread);
 | 
						|
		memset(&fpt->thread, 0x00, sizeof(fpt->thread));
 | 
						|
	}
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
void frr_pthread_wait_running(struct frr_pthread *fpt)
 | 
						|
{
 | 
						|
	frr_with_mutex(fpt->running_cond_mtx) {
 | 
						|
		while (!fpt->running)
 | 
						|
			pthread_cond_wait(fpt->running_cond,
 | 
						|
					  fpt->running_cond_mtx);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void frr_pthread_notify_running(struct frr_pthread *fpt)
 | 
						|
{
 | 
						|
	frr_with_mutex(fpt->running_cond_mtx) {
 | 
						|
		fpt->running = true;
 | 
						|
		pthread_cond_signal(fpt->running_cond);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int frr_pthread_stop(struct frr_pthread *fpt, void **result)
 | 
						|
{
 | 
						|
	frrtrace(1, frr_libfrr, frr_pthread_stop, fpt->name);
 | 
						|
 | 
						|
	int ret = (*fpt->attr.stop)(fpt, result);
 | 
						|
	memset(&fpt->thread, 0x00, sizeof(fpt->thread));
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
void frr_pthread_stop_all(void)
 | 
						|
{
 | 
						|
	frr_with_mutex(&frr_pthread_list_mtx) {
 | 
						|
		struct listnode *n;
 | 
						|
		struct frr_pthread *fpt;
 | 
						|
		for (ALL_LIST_ELEMENTS_RO(frr_pthread_list, n, fpt)) {
 | 
						|
			if (atomic_load_explicit(&fpt->running,
 | 
						|
						 memory_order_relaxed))
 | 
						|
				frr_pthread_stop(fpt, NULL);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * ----------------------------------------------------------------------------
 | 
						|
 * Default Event Loop
 | 
						|
 * ----------------------------------------------------------------------------
 | 
						|
 */
 | 
						|
 | 
						|
/* dummy task for sleeper pipe */
 | 
						|
static int fpt_dummy(struct thread *thread)
 | 
						|
{
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* poison pill task to end event loop */
 | 
						|
static int fpt_finish(struct thread *thread)
 | 
						|
{
 | 
						|
	struct frr_pthread *fpt = THREAD_ARG(thread);
 | 
						|
 | 
						|
	atomic_store_explicit(&fpt->running, false, memory_order_relaxed);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* stop function, called from other threads to halt this one */
 | 
						|
static int fpt_halt(struct frr_pthread *fpt, void **res)
 | 
						|
{
 | 
						|
	thread_add_event(fpt->master, &fpt_finish, fpt, 0, NULL);
 | 
						|
	pthread_join(fpt->thread, res);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Entry pthread function & main event loop.
 | 
						|
 *
 | 
						|
 * Upon thread start the following actions occur:
 | 
						|
 *
 | 
						|
 * - frr_pthread's owner field is set to pthread ID.
 | 
						|
 * - All signals are blocked (except for unblockable signals).
 | 
						|
 * - Pthread's threadmaster is set to never handle pending signals
 | 
						|
 * - Poker pipe for poll() is created and queued as I/O source
 | 
						|
 * - The frr_pthread->running_cond condition variable is signalled to indicate
 | 
						|
 *   that the previous actions have completed. It is not safe to assume any of
 | 
						|
 *   the above have occurred before receiving this signal.
 | 
						|
 *
 | 
						|
 * After initialization is completed, the event loop begins running. Each tick,
 | 
						|
 * the following actions are performed before running the usual event system
 | 
						|
 * tick function:
 | 
						|
 *
 | 
						|
 * - Verify that the running boolean is set
 | 
						|
 * - Verify that there are no pending cancellation requests
 | 
						|
 * - Verify that there are tasks scheduled
 | 
						|
 *
 | 
						|
 * So long as the conditions are met, the event loop tick is run and the
 | 
						|
 * returned task is executed.
 | 
						|
 *
 | 
						|
 * If any of these conditions are not met, the event loop exits, closes the
 | 
						|
 * pipes and dies without running any cleanup functions.
 | 
						|
 */
 | 
						|
static void *fpt_run(void *arg)
 | 
						|
{
 | 
						|
	struct frr_pthread *fpt = arg;
 | 
						|
	fpt->master->owner = pthread_self();
 | 
						|
 | 
						|
	zlog_tls_buffer_init();
 | 
						|
 | 
						|
	int sleeper[2];
 | 
						|
	pipe(sleeper);
 | 
						|
	thread_add_read(fpt->master, &fpt_dummy, NULL, sleeper[0], NULL);
 | 
						|
 | 
						|
	fpt->master->handle_signals = false;
 | 
						|
 | 
						|
	frr_pthread_set_name(fpt);
 | 
						|
 | 
						|
	frr_pthread_notify_running(fpt);
 | 
						|
 | 
						|
	struct thread task;
 | 
						|
	while (atomic_load_explicit(&fpt->running, memory_order_relaxed)) {
 | 
						|
		pthread_testcancel();
 | 
						|
		if (thread_fetch(fpt->master, &task)) {
 | 
						|
			thread_call(&task);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	close(sleeper[1]);
 | 
						|
	close(sleeper[0]);
 | 
						|
 | 
						|
	zlog_tls_buffer_fini();
 | 
						|
 | 
						|
	return NULL;
 | 
						|
}
 |