This is the mail archive of the cygwin mailing list for the Cygwin project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

1.7.14-2: pthread_cancel and pthread_kill not working as expected


Greetings everyone,

while porting a project from Linux to Cygwin I noticed that pthread_cancel
was having trouble cancelling threads blocked on semaphores and reads
from stdin.

Another user had a similar problem a while ago:
http://sourceware.org/ml/cygwin/2011-01/msg00374.html

According to the follow-up this was fixed for network sockets, but
Windows seems to tread networks socket different from everything else
and the problem might not apply in my case.

The user also mentioned trying pthread_kill to "wake" blocked threads
so I tried that as well, but that resulted in completely different
threads in my program waking up.

My question is now whether I'm using pthread_cancel and pthread_kill
incorrectly or if there's some functionality missing in the certainly
not trivial Cygwin implementation of those functions.

A program to demonstrate the problems is attached. It contains six
different tests that behave slightly different depending on which
function the "simplethread" function blocks on.

You can find the expected and actual results that I got for the six
tests at the bottom of this mail. The expected results correspond to
what the readily available man-pages for pthread_cancel and pthread_kill
state and I verified the tests on a Linux system.

tl;dr version of the results:
- Using pthread_cancel on a blocked thread with deferred cancel blocks
  the calling thread.
- Threads blocked on semaphores, reads from stdin or pause() aren't
  cancelled.
- Signals seem to wake the wrong threads in some cases.

I have no explanation for this, so any ideas and suggestions are welcome.

Cheers,
Otto


Test 1:
  Leave threads at deferred cancel and cancel each thread.
Expected result:
  All threads are cancelled.
Actual result:
  Blocking on semapthore: Main thread hangs on first call to pthread_cancel().
  Blocking on pause(): Same as semaphore.
  Blocking on read(): Not deterministic: Main thread hangs on random call
    to pthread_cancel().

Test 2:
  Set cancel type to asynchronous and cancel each thread.
Expected result:
  All threads are cancelled.
Actual result:
  Independent of what the threads are blocked on, nothing is cancelled.

Test 3:
  Send SIGUSR1 once to each thread.
Expected result:
  Each thread executes the signal handler once.
Actual result:
  Blocking on semaphore:
    Poking thread 2 (0x80020410)
    Thread 0 encountered an error: Interrupted system call (0x80010298)
    Poking thread 1 (0x80020370)
    Thread 1 executes signal handler (0x80020370)
    Thread 1 encountered an error: Interrupted system call (0x80020370)
    Poking thread 0 (0x80010298)
    Thread 2 executes signal handler (0x80020410)
    Thread 2 encountered an error: Interrupted system call (0x80020410)
    Woken threads don't correspond to signalled threads, not all of them
    execute the signal handler.
  Blocking on pause(): Same as semaphore.
  Blocking on read(): One thread executes the signal handler, the other
    two don't. Thread chosen seemingly at random. This could be a
    side-effect of all threads reading from stdin, but I still seems
    broken.

Test 4:
  Send SIGUSR1 to one thread repeatedly (doesn't exit by design).
Expected result:
  The target thread executes the signal handler each time.
Actual result:
  Blocking on semaphore: Independent of the targeted thread, thread 0
    wakes up once and executes the signal handler if thread 0 was
    signalled. No thread cares about further signals.
  Blocking on pause(): Same as semaphore.
  Blocking on read(): Not deterministic: Targeted thread either executes
    the signal handler every time or not at all.

Test 5:
  Cancel each thread asynchronously and poke them with SIGUSR1 afterwards.
Expected result:
  If a targeted thread isn't cancelled by pthread_cancel(), the signal
  should wake it up so that the cancel event can be handled.
Actual result:
  Blocking on semaphore:
    Killing thread 2 (0x80020410)
    Poking thread 2 (0x80020410)
    Thread 0 encountered an error: Interrupted system call (0x80010298)
    Killing thread 1 (0x80020370)
    Poking thread 1 (0x80020370)
    Thread 2 exiting (0x80020410)
    Killing thread 0 (0x80010298)
    Poking thread 0 (0x80010298)
    Thread 1 exiting (0x80020370)
    Responding threads don't correspond to signalled threads, wrong
    threads exit if they have been cancelled already.
  Blocking on pause(): Same as semaphore.
  Blocking on read(): Not deterministic: One thread exits when poked,
    the other two stay.

Test 6:
  Cancel each thread asynchronously and poke them with SIGUSR1 until they
  die.
Expected result:
  If a targeted thread isn't cancelled by pthread_cancel(), the signal
  should wake it up so that the cancel event can be handled. If the first
  signal "misses", one of the following should hit.
Actual result:
  Blocking on semaphore:
    Killing and poking thread 2 will result in one response to the signal
    from thread 0, then nothing.
    Killing and poking thread 1 will result in one resposne to the signal
    from thread 0, then nothing.
    Killing and poking thread 0 will result in one response to the signal
    from thread 1, then nothing.
  Blocking on pause(): Same as semaphore.
  Blocking on read(): Not deterministic: Either the first cancelled and
    poked thread exits fine and the other two don't care or all three
    don't care.
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define TEST 1
// Test 1: Deferred cancel: Main thread hangs on call to pthread_cancel()
// Test 2: Async cancel sent to each thread
// Test 3: Signal sent to each thread once
// Test 4: Signal sent to one thread repeatedly (doesn't exit by design).
// Test 5: Async cancel and signal sent to each thread once
// Test 6: Async cancel sent once and signal sent repeatedly to each thread until they die

pthread_t tids[3];
sem_t semaphore;

static void cleanup_handler(void *arg) {
  int *intptr = (int*)arg;
  pthread_t self = pthread_self();
  fprintf(stderr, "Thread %i exiting (%p)\n", *intptr, self);
}

static void* simplethread(void *arg) {
  int *intptr = (int*)arg;
  pthread_t self = pthread_self();
  fprintf(stderr, "Thread %i starting (%p)\n", *intptr, self);
  char buffer[1] __attribute((unused));

  pthread_cleanup_push(&cleanup_handler, intptr);

  int oldtype;
#if TEST != 1
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);
  fprintf(stderr, "Changing canceltype from %i to %i\n", oldtype, PTHREAD_CANCEL_ASYNCHRONOUS);
#endif
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldtype);
  fprintf(stderr, "Changing cancelstate from %i to %i\n", oldtype, PTHREAD_CANCEL_ENABLE);

  while (1) {
    if (sem_wait(&semaphore) != 0) {
    //if (pause() == -1) {
    //if (read(STDIN_FILENO, buffer, 1) <= 0) {
      fprintf(stderr, "Thread %i encountered an error: %s (%p)\n",
          *intptr, strerror(errno), self);
    } else {
      fprintf(stderr, "Thread %i woke up just fine\n", *intptr);
    }
  }

  pthread_cleanup_pop(1);
  return NULL;
}

static void sigusr1_handler(int signal __attribute((unused))) {
  pthread_t self = pthread_self();
  int tnum = 0;
  while (tnum < 3) {
    if (tids[tnum] == self) {
      break;
    }
    tnum++;
  }

  fprintf(stderr, "Thread %i executes signal handler (%p)\n", tnum, self);
}

static void install_handler(void) {
  struct sigaction act;
  act.sa_handler = &sigusr1_handler;
  sigemptyset(&(act.sa_mask));
  act.sa_flags = 0;

  if (sigaction(SIGUSR1, &act, NULL) != 0) {
    fprintf(stderr, "Can't set signal handler: %s\n", strerror(errno));
    exit(1);
  }

  sigset_t sset;
  sigemptyset(&sset);
  sigaddset(&sset, SIGUSR1);
  if (sigprocmask(SIG_UNBLOCK, &sset, NULL) != 0) {
    fprintf(stderr, "Can't unblock SIGUSR1: %s\n", strerror(errno));
  }
}

int main() {
  int i;
  int result;

  sem_init(&semaphore, 0, 0);
  install_handler();

  for (i=0; i<3; i++) {
    int *intptr = (int*)malloc(sizeof(int));
    *intptr = i;
    result = pthread_create(tids+i, NULL, &simplethread, intptr);
    if (result != 0) {
      fprintf(stderr, "Can't create thread: %s\n", strerror(result));
      return 1;
    }
  }

  sleep(1);
  install_handler();
  fprintf(stderr, "\n");

  int mainint = 42;
  pthread_cleanup_push(&cleanup_handler, &mainint);

  for (i=2; i>=0; i--) {
#if TEST == 1 || TEST == 2 || TEST == 5 || TEST == 6
    fprintf(stderr, "Killing thread %i (%p)\n", i, tids[i]);
    result = pthread_cancel(tids[i]);
    if (result != 0) {
      fprintf(stderr, "Error during pthread_cancel: %s\n", strerror(result));
    }
    sleep(1);
#endif
#if TEST == 3 || TEST == 4 | TEST == 5 || TEST == 6
    do {
      fprintf(stderr, "Poking thread %i (%p)\n", i, tids[i]);
      result = pthread_kill(tids[i], SIGUSR1);
      if (result != 0) {
        fprintf(stderr, "Error during pthread_kill: %s\n", strerror(result));
      }
      sleep(1);
      result = pthread_kill(tids[i], 0);
      if (result == 0) {
        fprintf(stderr, "Thread %i is still there (%p)\n", i, tids[i]);
        sleep(1);
      } else {
        fprintf(stderr, "Thread %i is gone (%p)\n", i, tids[i]);
        break;
      }
#endif
#if TEST == 3 || TEST == 5
    } while(0);
#elif TEST == 4 || TEST == 6
    } while(1);
#endif
  }

  sleep(1);

  fprintf(stderr, "\n");
  for (i=0; i<3; i++) {
    result = pthread_kill(tids[i], 0);
    if (result == 0) {
      fprintf(stderr, "Thread %i is still there (%p)\n", i, tids[i]);
    } else {
      fprintf(stderr, "Thread %i is gone (%p)\n", i, tids[i]);
    }
  }

  pthread_cleanup_pop(0);
  return 0;
}

Attachment: cygcheck.out
Description: Text document

--
Problem reports:       http://cygwin.com/problems.html
FAQ:                   http://cygwin.com/faq/
Documentation:         http://cygwin.com/docs.html
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]