From ab689196c9623c238cd7e3919be209001a37dc2a Mon Sep 17 00:00:00 2001 From: Björn Persson Date: Wed, 16 Jan 2013 23:40:05 +0100 Subject: Added the thread wrapper module. --- milter_api.gpr | 7 +- thread_wrapper/build_thread_wrapper.gpr | 20 ++++ thread_wrapper/pthread_create_locator.c | 22 +++++ thread_wrapper/pthread_create_locator.h | 12 +++ thread_wrapper/thread_wrapper.c | 159 ++++++++++++++++++++++++++++++++ 5 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 thread_wrapper/build_thread_wrapper.gpr create mode 100644 thread_wrapper/pthread_create_locator.c create mode 100644 thread_wrapper/pthread_create_locator.h create mode 100644 thread_wrapper/thread_wrapper.c diff --git a/milter_api.gpr b/milter_api.gpr index b3b47cc..741cfe4 100644 --- a/milter_api.gpr +++ b/milter_api.gpr @@ -1,5 +1,5 @@ -- Projects that use the Ada Milter API should import this file. --- Copyright 2009 - 2012 B. Persson, Bjorn@Rombobeorn.se +-- Copyright 2009 - 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 @@ -12,4 +12,9 @@ project Milter_API is for Library_Dir use Directories.Libdir; for Library_ALI_Dir use Directories.Libdir & "/adamilter"; for Externally_Built use "true"; + + package Linker is + for Linker_Options use ("-ladamilter_thread_wrapper", "-ldl"); + end Linker; + end Milter_API; diff --git a/thread_wrapper/build_thread_wrapper.gpr b/thread_wrapper/build_thread_wrapper.gpr new file mode 100644 index 0000000..78325b4 --- /dev/null +++ b/thread_wrapper/build_thread_wrapper.gpr @@ -0,0 +1,20 @@ +-- Use this project file to compile the thread wrapper into a static library. +-- Copyright 2013 B. Persson, Bjorn@Rombobeorn.se +-- +-- This project file 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. + + +with "directories"; + +library project Build_Thread_Wrapper is + + Destdir := external("DESTDIR", ""); + + for Library_Name use "adamilter_thread_wrapper"; + for Library_Kind use "static"; + for Library_Dir use Destdir & Directories.Libdir; + for Languages use ("C"); + +end Build_Thread_Wrapper; diff --git a/thread_wrapper/pthread_create_locator.c b/thread_wrapper/pthread_create_locator.c new file mode 100644 index 0000000..df5eef7 --- /dev/null +++ b/thread_wrapper/pthread_create_locator.c @@ -0,0 +1,22 @@ +// 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. + + +// _GNU_SOURCE must be defined to make RTLD_NEXT available. Keeping this in a +// separate file avoids any surprising effects that _GNU_SOURCE might have on +// other header files that thread_wrapper.c includes. + + +#include "pthread_create_locator.h" + +#define _GNU_SOURCE +#include + + +void* dlsym_next_pthread_create() { + return dlsym(RTLD_NEXT, "pthread_create"); +} diff --git a/thread_wrapper/pthread_create_locator.h b/thread_wrapper/pthread_create_locator.h new file mode 100644 index 0000000..04a1567 --- /dev/null +++ b/thread_wrapper/pthread_create_locator.h @@ -0,0 +1,12 @@ +// 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. + + +void* dlsym_next_pthread_create(); +// dlsym_next_pthread_create returns the address of the next symbol named +// "pthread_create" in the library search order - that is the first one in a +// dynamic library as this code must be in the main executable. 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 +#include +#include +#include +#include +#include +#include + +// 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); + } +} -- cgit v1.2.3