pthread_key_create destructor not getting called
Asked Answered
B

7

9

As per pthread_key_create man page we can associate a destructor to be called at thread shut down. My problem is that the destructor function I have registered is not being called. Gist of my code is as follows.

static pthread_key_t key;
static pthread_once_t tls_init_flag = PTHREAD_ONCE_INIT;

void destructor(void *t) {
  // thread local data structure clean up code here, which is not getting called
}

void create_key() {
  pthread_key_create(&key, destructor);
}

// This will be called from every thread
void set_thread_specific() {

  ts = new ts_stack; // Thread local data structure

  pthread_once(&tls_init_flag, create_key);
  pthread_setspecific(key, ts);
}

Any idea what might prevent this destructor being called? I am also using atexit() at moment to do some cleanup in the main thread. Is there any chance that is interfering with destructor function being called? I tried removing that as well. Still didn't work though. Also I am not clear if I should handle the main thread as a separate case with atexit. (It's a must to use atexit by the way, since I need to do some application specific cleanup at application exit)

Binky answered 2/7, 2014 at 2:28 Comment(0)
A
3

This is by design.

The main thread exits (by returning or calling exit()), and that doesn't use pthread_exit(). POSIX documents pthread_exit calling the thread-specific destructors.

You could add pthread_exit() at the end of main. Alternatively, you can use atexit to do your destruction. In that case, it would be clean to set the thread-specific value to NULL so in case the pthread_exit was invoked, the destruction wouldn't happen twice for that key.

UPDATE Actually, I've solved my immediate worries by simply adding this to my global unit test setup function:

::atexit([] { ::pthread_exit(0); });

So, in context of my global fixture class MyConfig:

struct MyConfig {
    MyConfig()   {
        GOOGLE_PROTOBUF_VERIFY_VERSION;
        ::atexit([] { ::pthread_exit(0); });
    }
    ~MyConfig()  { google::protobuf::ShutdownProtobufLibrary(); }
};

Some of the references used:


PS. Of course c++11 introduced <thread> so you have better and more portable primitves to work with.

Anthracene answered 5/4, 2016 at 23:14 Comment(3)
Added a concise workaround that I can't currently think of having major downsides: ::atexit([] { ::pthread_exit(0); });Anthracene
Note that POSIX states The functions registered by a call to atexit() must return to ensure that all registered functions are called. So if you use pthread_exit() in an atexit-registered function, any remaining functions on the atexit stack may not be called.Outworn
@AlexanderKlauer very good point. I'll have to revisit this thought when I touch the code againAnthracene
O
2

It's already in sehe's answer, just to present the key points in a compact way:

  • pthread_key_create() destructor calls are triggered by a call to pthread_exit().
  • If the start routine of a thread returns, the behaviour is as if pthread_exit() was called (i. e., destructor calls are triggered).
  • However, if main() returns, the behaviour is as if exit() was called — no destructor calls are triggered.

This is explained in http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_create.html. See also C++17 6.6.1p5 or C11 5.1.2.2.3p1.

Outworn answered 31/1, 2018 at 12:57 Comment(0)
A
0

I wrote a quick test and the only thing I changed was moving the create_key call of yours outside of the set_thread_specific.

That is, I called it within the main thread.

I then saw my destroy get called when the thread routine exited.

Actress answered 2/7, 2014 at 2:44 Comment(0)
F
0

I call destructor() manually at the end of main():

void * ThreadData = NULL;

if ((ThreadData = pthread_getspecific(key)) != NULL)
        destructor(ThreadData);

Of course key should be properly initialized earlier in main() code. PS. Calling Pthread_Exit() at the end to main() seems to hang entire application...

Flee answered 10/11, 2016 at 14:58 Comment(0)
S
0

Your initial thought of handling the main thread as a separate case with atexit worked best for me.

Be ware that pthread_exit(0) overwrites the exit value of the process. For example, the following program will exit with status of zero even though main() returns with number three:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

class ts_stack {
public:
  ts_stack () {
    printf ("init\n");
  }
  ~ts_stack () {
    printf ("done\n");
  }
};

static void cleanup (void);

static pthread_key_t key;
static pthread_once_t tls_init_flag = PTHREAD_ONCE_INIT;

void destructor(void *t) {
  // thread local data structure clean up code here, which is not getting called
  delete (ts_stack*) t;
}

void create_key() {
  pthread_key_create(&key, destructor);
  atexit(cleanup);
}

// This will be called from every thread
void set_thread_specific() {
  ts_stack *ts = new ts_stack (); // Thread local data structure

  pthread_once(&tls_init_flag, create_key);
  pthread_setspecific(key, ts);
}

static void cleanup (void) {
  pthread_exit(0); // <-- Calls destructor but sets exit status to zero as a side effect!
}

int main (int argc, char *argv[]) {
  set_thread_specific();
  return 3; // Attempt to exit with status of 3
}
Socioeconomic answered 29/5, 2017 at 22:13 Comment(0)
P
0

I had similar issue as yours: pthread_setspecific sets a key, but the destructor never gets called. To fix it we simply switched to thread_local in C++. You could also do something like if that change is too complicated:

For example, assume you have some class ThreadData that you want some action to be done on when the thread finishes execution. You define the destructor something on these lines:

void destroy_my_data(ThreadlData* t) {
   delete t;
}

When your thread starts, you allocate memory for ThreadData* instance and assign a destructor to it like this:

ThreadData* my_data = new ThreadData;
thread_local ThreadLocalDestructor<ThreadData> tld;
tld.SetDestructorData(my_data, destroy_my_data);
pthread_setspecific(key, my_data)

Notice that ThreadLocalDestructor is defined as thread_local. We rely on C++11 mechanism that when the thread exits, the destructor of ThreadLocalDestructor will be automatically called, and ~ThreadLocalDestructor is implemented to call function destroy_my_data.

Here is the implementation of ThreadLocalDestructor:

template <typename T>
class ThreadLocalDestructor
{
public:
    ThreadLocalDestructor() : m_destr_func(nullptr), m_destr_data(nullptr)
    {
    }

    ~ThreadLocalDestructor()
    {
        if (m_destr_func) {
            m_destr_func(m_destr_data);
        }
    }
    void SetDestructorData(void (*destr_func)(T*), T* destr_data)
    {
        m_destr_data = destr_data;
        m_destr_func = destr_func;
    }

private:
    void (*m_destr_func)(T*);
    T* m_destr_data;
};
Pol answered 20/5, 2019 at 9:9 Comment(0)
N
0

Many answers propose to make a call to pthread_exit(), but by doing so the main thread does not call exit() or return, and therefore the functions registered with atexit() are not called.

Maybe this will help you:

static pthread_key_t key;
static pthread_once_t tls_init_flag = PTHREAD_ONCE_INIT;

void destructor(void *t) {
  // thread local data structure clean up code here, which is not getting called
}

void cleanup(void) {
  ts = pthread_getspecific(key);
  if (ts != NULL) {                   // if "main" thread has specific data
    destructor(ts);                   // destroy data
    pthread_setspecific(key, NULL);   // unset specific data
  }

  // if key is in a library module (so set_thread_specific is too)
  // delete key at program exit.
  // pthread_key_delete(key);
}

void create_key() {
  pthread_key_create(&key, destructor);
  atexit(cleanup);                    // register cleanup function for "main" thread.
}

// This will be called from every thread
void set_thread_specific() {

  ts = new ts_stack; // Thread local data structure

  pthread_once(&tls_init_flag, create_key);
  pthread_setspecific(key, ts);
}
Nd answered 2/1 at 18:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.