0

Code:

http://man7.org/tlpi/code/online/book/threads/strerror_tsd.c

static void                         /* Free thread-specific data buffer */
destructor(void *buf)
{
    free(buf);
}

static void                         /* One-time key creation function */
createKey(void)
{
    int s;

    /* Allocate a unique thread-specific data key and save the address
       of the destructor for thread-specific data buffers */

    s = pthread_key_create(&strerrorKey, destructor);
    if (s != 0)
        errExitEN(s, "pthread_key_create");
}

char *
strerror(int err)
{
    int s;
    char *buf;

    /* Make first caller allocate key for thread-specific data */

    s = pthread_once(&once, createKey);
    if (s != 0)
        errExitEN(s, "pthread_once");

    buf = pthread_getspecific(strerrorKey);
    if (buf == NULL) {          /* If first call from this thread, allocate
                                   buffer for thread, and save its location */
        buf = malloc(MAX_ERROR_LEN);
        if (buf == NULL)
            errExit("malloc");

        s = pthread_setspecific(strerrorKey, buf);
        if (s != 0)
            errExitEN(s, "pthread_setspecific");
    }

    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
    } else {
        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
        buf[MAX_ERROR_LEN - 1] = '\0';          /* Ensure null termination */
    }

    return buf;
}

http://man7.org/tlpi/code/online/dist/threads/strerror_test.c

static void *
threadFunc(void *arg)
{
    char *str;

    printf("Other thread about to call strerror()\n");
    str = strerror(EPERM);
    printf("Other thread: str (%p) = %s\n", str, str);

    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t t;
    int s;
    char *str;

    str = strerror(EINVAL);
    printf("Main thread has called strerror()\n");

    s = pthread_create(&t, NULL, threadFunc, NULL);
    if (s != 0)
        errExitEN(s, "pthread_create");

    s = pthread_join(t, NULL);
    if (s != 0)
        errExitEN(s, "pthread_join");

    /* If strerror() is not thread-safe, then the output of this printf() be
       the same as that produced by the analogous printf() in threadFunc() */

    printf("Main thread:  str (%p) = %s\n", str, str);

    exit(EXIT_SUCCESS);
}

Question:

int pthread_key_create(pthread_key_t *key, void (destructor)(void *));

The implementation of strerror uses thread-specific data. Upon termination of a thread that has a non-NULL value associated with key, the destructor function is automatically invoked by the Pthreads API and given that value as its argument. Based on my testing, the main thread calling to strerror will NOT trigger the destructor but the child thread will. I would like to know why?

Thank you

q0987
  • 34,938
  • 69
  • 242
  • 387
  • Title unclear. destructor not called in main thread? – Tony Tannous Mar 18 '20 at 22:18
  • when I said destructor, I mean the function passed to pthread_key_create(&strerrorKey, destructor) – q0987 Mar 18 '20 at 22:42
  • @q0987, I think Tony's question was about the intended meaning of "but the main thread". And if even if not, that's what *I* want to know. – John Bollinger Mar 18 '20 at 23:19
  • My second question would be about why you have any expectation that the destructor function would *ever* be called, and especially why you expect that a call to `strerror()` would produce such an effect. Neither your `main()` nor your `threadFunc()` appears to do anything that would have that result, whether directly or indirectly. I would expect a member with your rep to know very well that we need a [mre], and how to prepare a good one. – John Bollinger Mar 18 '20 at 23:32
  • @JohnBollinger thats exactly what I was asking. – Tony Tannous Mar 18 '20 at 23:43
  • I have updated my original question and hopefully, I addressed all your concerns. – q0987 Mar 19 '20 at 02:46

1 Answers1

3

Here is a simplified example:

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

static pthread_key_t key;

void destroy(void *data) 
{
  fprintf(stderr, "==> %s on thread %lu with argument %p\n", __func__, pthread_self(), data);
}

void *target(void *data)
{
  // comment out the following line and destroy will not be called from child thread
  pthread_setspecific(key, target);

  fprintf(stderr, "%s set key to %p on thread %lu\n", __func__, pthread_getspecific(key), pthread_self());
  return NULL;
}

int main(int argc, char *argv[])
{
  fprintf(stderr, "%s is on thread %lu\n", __func__, pthread_self());

  pthread_key_create(&key, destroy);
  pthread_setspecific(key, main);

  fprintf(stderr, "%s set key to %p\n", __func__, pthread_getspecific(key));

  pthread_t child;
  pthread_create(&child, NULL, target, NULL);

  fprintf(stderr, "main thread created thread %lu\n",  child);

  pthread_join(child, NULL);

  fprintf(stderr, "main thread joined child\n");

  // comment out the following line and destroy will not be called from main thread
  pthread_exit(main);
  return 0;
}

According to the man page for pthread_key_create

At thread exit, if a key value has a non-NULL destructor pointer, and the thread has a non-NULL value associated with that key, the value of the key is set to NULL, and then the function pointed to is called with the previously associated value as its sole argument.

In both the question code and the simplified example, each thread has set the key to a non-NULL value, so it seems like the destructor should be called on each thread.

But the way the main thread exits by default, the key specific destructors are not called. See pthread_create man page and question 24521968. Calling pthread_exit() at the end of main will invoke the destructor.