[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#414021: SIGSTOP then SIGCONT on a whole process incorrectly releases a thread from sigwait(3) for another signal



Package: linux-image-2.6-amd64
Version: 2.6.18+6

I've already tried to report this bug upstream at:
http://sources.redhat.com/bugzilla/show_bug.cgi?id=4169

but the maintainers of glibc say that this is a bug in the kernel and
not in NPTL.

So...

If a whole process, which has a thread waiting with sigwait(3), is stopped with
SIGSTOP, then when it is restarted with SIGCONT sigwait returns without writing
to *sig (second argument).  This appears to be in contradiction with
POSIX-1003.1 2004.

http://www.opengroup.org/onlinepubs/000095399/functions/sigwait.html

Steps to reproduce:

$ wget http://purposeful.co.uk/testcase.c
$ gcc -o testcase -pthread -lpthread testcase.c
$./testcase

The test program prints 'a' from one thread and 'b' from another.  After ten of
each the 'b' thread calls sigwait, and you get just 'a's.  When this happens,
hit Ctrl+Z.  Now restart the process (with fg).  The 'b' thread resumes straight
away.

Expected behaviour:

$ gcc -o testcase -pthread -lpthread -DWORKAROUND testcase.c
$./testcase

Now do the same.  The 'b' thread returns to its waiting state.  You get as more
of just 'a' until there have been 10 of them and then a mix again.

$ uname -a
Linux cheese 2.6.18-4-amd64 #1 SMP Wed Feb 21 14:29:38 UTC 2007 x86_64 GNU/Linux
$ dpkg -s libc6 | fgrep Version
Version: 2.3.6.ds1-13
/* testcase.c PUBLIC DOMAIN by Tom Vajzovic */
#include <pthread.h>

#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

#define MY_SIGSTOP  (SIGRTMIN + 8)
#define MY_SIGCONT  (SIGRTMIN + 9)

#define WASTE_TIME { int z; for( z= INT_MAX/15; z; --z ); }
#define REPS 30

void *thread_func( void *vptr_args );
void resume_handler( int arg );
void suspend_handler( int arg );

int main( int ac, char **av ){
  int i;
  struct sigaction sto_action;
  struct sigaction res_action;
  sigset_t blocked;
  pthread_t thread;
  
  sto_action. sa_handler= suspend_handler;
  sigfillset( & sto_action. sa_mask );
  sigdelset( & sto_action. sa_mask, MY_SIGCONT );
  sto_action. sa_flags= 0;

  res_action. sa_handler= resume_handler;
  sigfillset( & res_action. sa_mask );
  res_action. sa_flags= 0;

  if( sigaction( MY_SIGCONT, &res_action, NULL ) || sigaction( MY_SIGSTOP, &sto_action, NULL ) ){
    perror( "sigaction" );  
    exit( EXIT_FAILURE );
  }

  sigemptyset( &blocked );
  sigaddset( &blocked, MY_SIGSTOP );
  sigaddset( &blocked, MY_SIGCONT );

  pthread_sigmask( SIG_SETMASK, &blocked, NULL );

  pthread_create( &thread, NULL, &thread_func, NULL );

  for( i= 0; i < REPS; ++i ){
    fprintf( stderr, "a\n" );

    if( 10 == i )
      pthread_kill( thread, MY_SIGSTOP );

    if( 20 == i )
      pthread_kill( thread, MY_SIGCONT );
    
    WASTE_TIME;
  }

  pthread_join( thread, NULL );

  exit( EXIT_SUCCESS );
}

void *thread_func( void *vptr_args ){

  sigset_t blocked;
  int i;

  sigfillset( &blocked );
  sigdelset( &blocked, MY_SIGSTOP );

  pthread_sigmask( SIG_SETMASK, &blocked, NULL );

  for( i= 0; i < REPS; ++i ){
    fprintf( stdout, "  b\n" );

    WASTE_TIME;
  }

  pthread_exit( NULL );
}

void resume_handler( int arg ){
  return;
}

void suspend_handler( int arg ){
  sigset_t set;
  int sig= -1;

  sigemptyset( &set );
  sigaddset( &set, MY_SIGCONT );

#ifdef WORKAROUND
  /* define this for the correct behavior */
  while( -1 == sig )
#endif  
    sigwait( &set, &sig );
}

Reply to: