diff options
Diffstat (limited to 'lib/frr_pthread.c')
-rw-r--r-- | lib/frr_pthread.c | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/lib/frr_pthread.c b/lib/frr_pthread.c new file mode 100644 index 0000000..dd675bb --- /dev/null +++ b/lib/frr_pthread.c @@ -0,0 +1,319 @@ +/* + * 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 void fpt_dummy(struct thread *thread) +{ +} + +/* poison pill task to end event loop */ +static void fpt_finish(struct thread *thread) +{ + struct frr_pthread *fpt = THREAD_ARG(thread); + + atomic_store_explicit(&fpt->running, false, memory_order_relaxed); +} + +/* 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; +} |