Rombobjörn

summaryrefslogtreecommitdiff
path: root/thread_wrapper/thread_wrapper.c
blob: 86eaa3b4a37e04b680810e193aaa79a4e4fccfd7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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);
   }
}