summaryrefslogtreecommitdiffstats
path: root/lib/frr_pthread.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/frr_pthread.c319
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;
+}