From b878f05a6e62779ddf2364d5bb51821cb6d6d572 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 26 Nov 2018 15:09:10 -0500 Subject: [PATCH] Add a thread utility class and thread list MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit As part of the re-work of the order of the teardown of the sessiond's threads, this utility is introduced to track running threads and unify the mechanisms through which they are launched and shutdown. This makes it easier to reason about (and track) the order in which threads are launched and shutdown. The lttng_thread class allows threads to be implemented by defining the following methods: - An entry point ("main" function), - A shutdown method, - A clean-up method. Since the sessiond's threads use a variety of techniques to initiate their teardown (through an explicit command in a queue, using the global "quit" pipe, or through a futex + variable), it is more practical to let them define a shutdown method which notifies the thread to shutdown than to impose a standard mechanism. A clean-up method is meant to work around situations where the ownership of data structures shared between the main thread and a worker thread can be ambiguous (mostly in error paths). The clean-up method is invoked when the last reference to a thread is released. While some threads need to be shutdown in a particular order, most of them can be shutdown in bulk. The lttng_thread utility maintains a global thread list which allows for a generic path through which threads can be shutdown using the lttng_thread_list_shutdown_orphans() function. The lttng_thread_shutdown() method, in return, allows the user (most likely the main thread) to explicitly teardown threads which must be shutdown in a specific order before issuing the bulk lttng_thread_list_shutdown_orphans() call. Note that lttng_thread objects are reference counted. The thread list holds a reference to each thread until it is shutdown. Hence, it is safe to hold a reference to a thread, invoke its shutdown method, and then invoke lttng_thread_list_shutdown_orphans(). Signed-off-by: Jérémie Galarneau --- src/bin/lttng-sessiond/Makefile.am | 3 +- src/bin/lttng-sessiond/thread.c | 212 +++++++++++++++++++++++++++++ src/bin/lttng-sessiond/thread.h | 72 ++++++++++ 3 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 src/bin/lttng-sessiond/thread.c create mode 100644 src/bin/lttng-sessiond/thread.h diff --git a/src/bin/lttng-sessiond/Makefile.am b/src/bin/lttng-sessiond/Makefile.am index cfcdc5e86..6330cdccc 100644 --- a/src/bin/lttng-sessiond/Makefile.am +++ b/src/bin/lttng-sessiond/Makefile.am @@ -43,7 +43,8 @@ lttng_sessiond_SOURCES = utils.c utils.h \ ready.c \ globals.c \ thread-utils.c \ - process-utils.c + process-utils.c \ + thread.c thread.h if HAVE_LIBLTTNG_UST_CTL lttng_sessiond_SOURCES += trace-ust.c ust-registry.c ust-app.c \ diff --git a/src/bin/lttng-sessiond/thread.c b/src/bin/lttng-sessiond/thread.c new file mode 100644 index 000000000..7e16b69ed --- /dev/null +++ b/src/bin/lttng-sessiond/thread.c @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2018 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License, version 2 only, as + * published by the Free Software Foundation. + * + * 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; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "thread.h" +#include +#include +#include +#include +#include +#include + +static struct thread_list { + struct cds_list_head head; + pthread_mutex_t lock; +} thread_list = { + .head = CDS_LIST_HEAD_INIT(thread_list.head), + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +struct lttng_thread { + struct urcu_ref ref; + struct cds_list_head node; + pthread_t thread; + const char *name; + /* Main thread function */ + lttng_thread_entry_point entry; + /* + * Thread-specific shutdown method. Allows threads to implement their + * own shutdown mechanism as some of them use a structured message + * passed through a command queue and some rely on a dedicated "quit" + * pipe. + */ + lttng_thread_shutdown_cb shutdown; + lttng_thread_cleanup_cb cleanup; + /* Thread implementation-specific data. */ + void *data; +}; + +static +void lttng_thread_destroy(struct lttng_thread *thread) +{ + if (thread->cleanup) { + thread->cleanup(thread->data); + } + free(thread); +} + +static +void lttng_thread_release(struct urcu_ref *ref) +{ + lttng_thread_destroy(container_of(ref, struct lttng_thread, ref)); +} + +static +void *launch_thread(void *data) +{ + void *ret; + struct lttng_thread *thread = (struct lttng_thread *) data; + + DBG("Launching \"%s\" thread", thread->name); + ret = thread->entry(thread->data); + DBG("Thread \"%s\" has returned", thread->name); + return ret; +} + +struct lttng_thread *lttng_thread_create(const char *name, + lttng_thread_entry_point entry, + lttng_thread_shutdown_cb shutdown, + lttng_thread_cleanup_cb cleanup, + void *thread_data) +{ + int ret; + struct lttng_thread *thread; + + thread = zmalloc(sizeof(*thread)); + if (!thread) { + goto error; + } + + urcu_ref_init(&thread->ref); + CDS_INIT_LIST_HEAD(&thread->node); + /* + * Thread names are assumed to be statically allocated strings. + * It is unnecessary to copy this attribute. + */ + thread->name = name; + thread->entry = entry; + thread->shutdown = shutdown; + thread->cleanup = cleanup; + thread->data = thread_data; + + pthread_mutex_lock(&thread_list.lock); + /* + * Add the thread at the head of the list to shutdown threads in the + * opposite order of their creation. A reference is taken for the + * thread list which will be released on shutdown of the thread. + */ + cds_list_add(&thread->node, &thread_list.head); + (void) lttng_thread_get(thread); + + ret = pthread_create(&thread->thread, default_pthread_attr(), + launch_thread, thread); + if (ret) { + PERROR("Failed to create \"%s\" thread", thread->name); + goto error_pthread_create; + } + + pthread_mutex_unlock(&thread_list.lock); + return thread; + +error_pthread_create: + cds_list_del(&thread->node); + /* Release list reference. */ + lttng_thread_put(thread); + pthread_mutex_unlock(&thread_list.lock); +error: + /* Release initial reference. */ + lttng_thread_put(thread); + return NULL; +} + +bool lttng_thread_get(struct lttng_thread *thread) +{ + return urcu_ref_get_unless_zero(&thread->ref); +} + +void lttng_thread_put(struct lttng_thread *thread) +{ + assert(thread->ref.refcount); + urcu_ref_put(&thread->ref, lttng_thread_release); +} + +const char *lttng_thread_get_name(const struct lttng_thread *thread) +{ + return thread->name; +} + +static +bool _lttng_thread_shutdown(struct lttng_thread *thread) +{ + int ret; + void *status; + bool result = true; + + DBG("Shutting down \"%s\" thread", thread->name); + if (thread->shutdown) { + result = thread->shutdown(thread->data); + if (!result) { + result = false; + goto end; + } + } + + ret = pthread_join(thread->thread, &status); + if (ret) { + PERROR("Failed to join \"%s\" thread", thread->name); + result = false; + } + /* Release the list's reference to the thread. */ + cds_list_del(&thread->node); + lttng_thread_put(thread); +end: + return result; +} + +bool lttng_thread_shutdown(struct lttng_thread *thread) +{ + bool result; + + pthread_mutex_lock(&thread_list.lock); + result = _lttng_thread_shutdown(thread); + pthread_mutex_unlock(&thread_list.lock); + return result; +} + +void lttng_thread_list_shutdown_orphans(void) +{ + struct lttng_thread *thread, *tmp; + + pthread_mutex_lock(&thread_list.lock); + cds_list_for_each_entry_safe(thread, tmp, &thread_list.head, node) { + bool result; + const long ref = uatomic_read(&thread->ref.refcount); + + if (ref != 1) { + /* + * Other external references to the thread exist, skip. + */ + continue; + } + + result = _lttng_thread_shutdown(thread); + if (!result) { + ERR("Failed to shutdown thread \"%s\"", thread->name); + } + } + pthread_mutex_unlock(&thread_list.lock); +} diff --git a/src/bin/lttng-sessiond/thread.h b/src/bin/lttng-sessiond/thread.h new file mode 100644 index 000000000..3b85d0be9 --- /dev/null +++ b/src/bin/lttng-sessiond/thread.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License, version 2 only, as + * published by the Free Software Foundation. + * + * 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; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#ifndef THREAD_H +#define THREAD_H + +struct lttng_thread; + +/* Main function of the new thread. */ +typedef void *(*lttng_thread_entry_point)(void *); + +/* Callback invoked to initiate the shutdown a thread. */ +typedef bool (*lttng_thread_shutdown_cb)(void *); + +/* + * Callback invoked to clean-up the thread data. + * Invoked when the thread is destroyed to ensure there is no + * race between a use by the "thread shutdown callback" and + * a use by the thread itself. + */ +typedef void (*lttng_thread_cleanup_cb)(void *); + +/* + * Returns a reference to the newly-created thread. + * The shutdown and cleanup callbacks are optional. + */ +struct lttng_thread *lttng_thread_create(const char *name, + lttng_thread_entry_point entry, + lttng_thread_shutdown_cb shutdown, + lttng_thread_cleanup_cb cleanup, + void *thread_data); + +bool lttng_thread_get(struct lttng_thread *thread); +void lttng_thread_put(struct lttng_thread *thread); + +const char *lttng_thread_get_name(const struct lttng_thread *thread); + +/* + * Explicitly shutdown a thread. This function returns once the + * thread has returned and been joined. + * + * It is invalid to call this function more than once on a thread. + * + * Returns true on success, false on error. + */ +bool lttng_thread_shutdown(struct lttng_thread *thread); + +/* + * Shutdown all orphaned threads (threads to which no external reference + * exist). + * + * Returns once all orphaned threads have been joined. + */ +void lttng_thread_list_shutdown_orphans(void); + +#endif /* THREAD_H */ -- 2.34.1