Rombobjörn

summaryrefslogtreecommitdiff
path: root/thread_wrapper/thread_wrapper.c
diff options
context:
space:
mode:
Diffstat (limited to 'thread_wrapper/thread_wrapper.c')
-rw-r--r--thread_wrapper/thread_wrapper.c159
1 files changed, 159 insertions, 0 deletions
diff --git a/thread_wrapper/thread_wrapper.c b/thread_wrapper/thread_wrapper.c
new file mode 100644
index 0000000..86eaa3b
--- /dev/null
+++ b/thread_wrapper/thread_wrapper.c
@@ -0,0 +1,159 @@
+// Ada Milter API thread wrapper
+// Copyright 2013 B. Persson, Bjorn@Rombobeorn.se
+//
+// This library is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License version 3, as published
+// by the Free Software Foundation.
+
+
+// This module wraps any threads that are started from anywhere in the program,
+// making them call GNAT.Threads.Register_Thread when they start and
+// GNAT.Threads.Unregister_Thread when they terminate. Although Libgnat
+// registers foreign threads automatically on GNU/Linux, it leaks memory when
+// the threads terminate. Calling Unregister_Thread plugs the leak.
+//
+// Initially nothing special is done to threads, so that Ada tasks such as
+// Libgnat's Interrupt_Manager task can be started during elaboration, even
+// before Libgnat is ready to handle calls to Register_Thread. The wrapping
+// begins after start_wrapping_threads is called.
+//
+// To make the wrapping of threads possible this module contains a function
+// named pthread_create which will be called instead of the one in the Pthreads
+// library. This function in turn calls the real pthread_create, but instructs
+// it to have the thread run the thread start routine wrap_thread instead of
+// its normal start routine. wrap_thread calls Register_Thread, arranges for
+// Unregister_Thread to be called when the thread terminates, and then calls
+// the normal start routine.
+//
+// For this to work the Pthreads library must be linked in dynamically, but
+// this module must be statically linked into the main executable to ensure
+// that this pthread_create is found before the one in the Pthreads library.
+
+
+#include "pthread_create_locator.h"
+
+#include <dlfcn.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <syslog.h>
+#include <errno.h>
+
+// declarations copied from g-thread.ads:
+
+extern void *__gnat_register_thread();
+
+extern void __gnat_unregister_thread();
+
+
+typedef struct thread_data {
+ void* (*start_routine)(void*);
+ void* arg;
+} thread_data;
+// A thread_data object holds a thread start routine and an argument to be
+// passed to it.
+
+static int (*real_pthread_create)(pthread_t*, const pthread_attr_t*,
+ void* (*)(void*), void*) = NULL;
+
+static bool wrap_threads = false;
+
+
+void start_wrapping_threads()
+ // start_wrapping_threads shall be called before any threads that need
+ // wrapping are started, but after tasking packages have been elaborated so
+ // that Libgnat is ready to handle calls to Register_Thread.
+{
+ wrap_threads = true;
+}
+
+
+static void cleanup_handler(void* const dummy __attribute__ ((unused)))
+ // cleanup_handler is a wrapper around Unregister_Thread. It exists because
+ // clean-up handlers are specified to take a void pointer argument and
+ // Unregister_Thread doesn't expect one.
+{
+ __gnat_unregister_thread();
+}
+
+
+static void* wrap_thread(void* const wrapped_thread_data)
+ // wrap_thread is the thread start routine of all wrapped threads. It
+ // performs the wrapping and calls the routine that wrapped_thread_data
+ // holds.
+{
+ // Copy the argument to a local variable and deallocate it so that it won't
+ // leak regardless of how the thread terminates.
+ const thread_data wrapped = *(thread_data*)wrapped_thread_data;
+ void* output;
+
+ free(wrapped_thread_data);
+ // Register the thread and set up a clean-up handler to unregister it. The
+ // clean-up handler will be called regardless of how the thread terminates,
+ // except if the thread sets its cancelability type to asynchronous, which
+ // would be quite wrong for a thread that doesn't have intimate knowledge of
+ // all the code it executes.
+ __gnat_register_thread();
+ pthread_cleanup_push(cleanup_handler, NULL);
+ // Execute the wrapped thread.
+ output = wrapped.start_routine(wrapped.arg);
+ pthread_cleanup_pop(true);
+ return output;
+}
+
+
+int pthread_create(pthread_t* const thread, const pthread_attr_t* const attr,
+ void* (* const start_routine)(void*), void* const arg)
+{
+ if(real_pthread_create == NULL) {
+ // This is the first call to this function, so the real pthread_create
+ // needs to be located. This also means that there are no other threads
+ // yet, so there's no need for synchronization.
+
+ const char* error;
+
+ // Ensure that there's no undetected error left from other calls to the
+ // dynamic loader. Complain if there is one.
+ error = dlerror();
+ if(error != NULL) {
+ syslog(LOG_ERR,
+ "pthread_create wrapper: "
+ "somebody neglected this error from the dynamic loader: %s",
+ error);
+ }
+
+ // Look up the real pthread_create.
+ *(const void**)(&real_pthread_create) = dlsym_next_pthread_create();
+ // According to the Linux man page for dlsym, which cites POSIX.1-2003,
+ // the cast from a function pointer pointer to a void pointer pointer is
+ // necessary because C99 leaves casting from a void pointer to a function
+ // pointer undefined.
+ error = dlerror();
+ if(error != NULL) {
+ syslog(LOG_ERR, "pthread_create wrapper: dlsym failed: %s", error);
+ return ELIBACC; // "Cannot access a needed shared library"
+ } else if(real_pthread_create == NULL) {
+ syslog(LOG_ERR,
+ "pthread_create wrapper: pthread_create could not be found.");
+ return ELIBACC;
+ }
+ }
+ if(wrap_threads) {
+ // Tell the real pthread_create to start the thread with wrap_thread and
+ // pass start_routine and its argument as the argument to wrap_thread.
+ thread_data* const wrapped = malloc(sizeof(thread_data));
+ if(wrapped == NULL) {
+ // malloc failed. Return EAGAIN, which is the documented code for
+ // insufficient resources to create another thread.
+ return EAGAIN;
+ }
+ wrapped->start_routine = start_routine;
+ wrapped->arg = arg;
+ return real_pthread_create(thread, attr, wrap_thread, wrapped);
+ } else {
+ // Wrapping hasn't been enabled yet. Pass the arguments unmodified to the
+ // real pthread_create.
+ return real_pthread_create(thread, attr, start_routine, arg);
+ }
+}