Pthread_kill () with invalid thread

I would like to determine if a specific thread exists'.

pthread_kill()

seems to be suitable for this task, at least according to its man page .

If sig is 0, then no signal is sent, but error checking is still in progress.

Or, as my system page shows:

If sig is 0, then no signal is sent, but error checking is still performed; this can be used to check if a stream id exists.

However, when I try to go uninitialized pthread_t

, the application necessarily has a SEGFAULT.


In this case, the following snippet from pthread_kill.c

(from my toolchain) does not do error checking and just tries to unlink threadid

(de-link is on pd->tid

).

int
__pthread_kill (threadid, signo)
     pthread_t threadid;
     int signo;
{
  struct pthread *pd = (struct pthread *) threadid;

  /* Make sure the descriptor is valid.  */
  if (DEBUGGING_P && INVALID_TD_P (pd))
    /* Not a valid thread handle.  */
    return ESRCH;

  /* Force load of pd->tid into local variable or register.  Otherwise
     if a thread exits between ESRCH test and tgkill, we might return
     EINVAL, because pd->tid would be cleared by the kernel.  */
  pid_t tid = atomic_forced_read (pd->tid);
  if (__builtin_expect (tid <= 0, 0))
    /* Not a valid thread handle.  */
    return ESRCH;

      

We cannot even rely on null being a good initializer because of the following:

# define DEBUGGING_P 0
/* Simplified test.  This will not catch all invalid descriptors but
   is better than nothing.  And if the test triggers the thread
   descriptor is guaranteed to be invalid.  */
# define INVALID_TD_P(pd) __builtin_expect ((pd)->tid <= 0, 0)

      

Also, I noticed the following on the linked man page (but not on my system):

POSIX.1-2008 recommends that if an implementation detects that a thread ID is in use after its expiration date, pthread_kill () should return an ESRCH error. The glibc implementation returns this error when an invalid thread identifier is encountered. Note also that POSIX says that trying to use a thread identifier that has reached the end of its life will result in undefined behavior , and trying to use an invalid thread identifier in a pthread_kill () call might, for example, cause a segmentation fault.


As R .. pointed out here , I'm asking for the dreaded undefined behavior.

It looks like the manual is really misleading, especially on my system.

  • Is there a good / reliable way to find out if a stream exists? (presumably not using pthread_kill()

    )
  • Is there a good value we can use to initialize type variables pthread_t

    , even if we have to catch them ourselves?

I suspect the answer is to use pthread_cleanup_push()

and hold a flag of is_running

my own, but would love to hear thoughts from others.

+3


source to share


1 answer


I think I came to this realization during my trip home, and I suspect many others may find it useful too ...

It would seem like I was treating the worker (thread) and the task (what the thread does) as the same, when in fact they are not.

As I have already established from the code snippets in the question, it is unreasonable to ask "does this thread exist" since it is pthread_t

most likely a pointer (this is certainly on my purpose). This is almost certainly the wrong question.

The same goes for process ids, file descriptors, malloc()

memory 'd etc ... they do not use unique and never duplicate identifiers and are therefore not unique "entities" that can be checked for their existence.

The suspicions I raised in the question are most likely correct - I will need to use something like a flag is_running

for a task (not a thread).



One approach I was thinking of is to use a semaphore initialized with one sem_trywait()

, sem_post()

and pthread_cleanup_push()

like in the example below (no cleanup for brevity).

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <semaphore.h>
#include <pthread.h>

struct my_task {
    sem_t can_start;
    pthread_t tid;

    /* task-related stuff */
};

void *my_task_worker(void *arg) {
    struct my_task *task = arg;

    pthread_cleanup_push(sem_post, &(task->can_start));

    fprintf(stderr, "--- task starting!\n");
    usleep(2500000);
    fprintf(stderr, "--- task ending!\n");

    pthread_cleanup_pop(1);

    return NULL;
}

void my_task_start(struct my_task *task) {
    int ret;

    ret = sem_trywait(&(task->can_start));
    if (ret != 0) {
        if (errno != EAGAIN) {
            perror("sem_trywait()");
            exit(1);
        }

        fprintf(stderr, ">>> task already running...\n");
        return;
    }

    ret = pthread_create(&(task->tid), NULL, my_task_worker, task);
    if (ret != 0) {
        perror("pthread_create()");
        exit(1);
    }

    fprintf(stderr, ">>> started task!\n");

    return;
}

int main(int argc, char *argv[]) {
    int ret;
    struct my_task task;
    int i;

    memset(&task, 0, sizeof(0));

    ret = sem_init(&(task.can_start), 0, 1);
    if (ret != 0)
    {
        perror("sem_init()");
        return 1;
    }

    for (i = 0; i < 10; i++) {
        my_task_start(&task);
        sleep(1);
    }

    return 0;
}

      

Output:

>>> started task!
--- task starting!
>>> task already running...
>>> task already running...
--- task ending!
>>> started task!
--- task starting!
>>> task already running...
>>> task already running...
--- task ending!
>>> started task!
--- task starting!
>>> task already running...
>>> task already running...
--- task ending!
>>> started task!
--- task starting!

      

0


source







All Articles