Bug 7510

Summary: Various problems with pthreads and fork/wait/signal handling
Product: [Retired] Red Hat Linux Reporter: martin
Component: glibcAssignee: Jakub Jelinek <jakub>
Status: CLOSED CURRENTRELEASE QA Contact:
Severity: medium Docs Contact:
Priority: medium    
Version: 6.1CC: lowen
Target Milestone: ---   
Target Release: ---   
Hardware: i386   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2003-01-24 23:44:06 UTC Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description martin 1999-12-02 15:28:14 UTC
On 'Other UNIXs' (namely AIX 4.2 and HP-UX 11.0) the following program
'works' - that is, it runs to completion without using CTRL-C at the
terminal. On linux it hangs.

The basic idea is is this:

    main():
        Block SIGCHLD using pthread_sigmask/sigprocmask

        Initialize a pthread condition and its mutex

        Start thread_sig_handler thread

        fork()

        (See CHLD for child. This is the parent)

        Wait on the pthread condition

        Wait on end of thread_sig_handler

        exit

    CHLD:
        execl("/bin/sh", "sh", "-c", "echo HI THERE", NULL);

    thread_sig_handler:
        sigwait on SIGCHLD (plus SIGQUIT and SIGINT for safety)

        post the pthread condition

        return

i.e.:

So, basically, one thread is set up as a pseudo signal handler for SIGCHLD
while another thread (or the main thread) does a fork/exec, the parent
waiting until the signal handler has done its bit (signalled by a pthread
condition variable), the child running something that will eventually end,
generating a SIGCHLD signal.

On Linux the handler thread never gets woken up (until CTRL-C).

I tried a modified version too - add a signal handler that does a
pthread_kill(handler_thread, SIGCHLD). That wakes it up alright - but the
handler thread then attempts a waitpid - and receives ECHILD. So it cannot
access the child process.

This is the initial problem. I also have others with pthreads - like an
execl from a forked child seems to kill the signale handler thread in some
circumstances, the nature of which I cannot discover! -

Thirdly note that getpid from a thread does NOT return the 'process id' as
one would hope in a merely threaded program - it returns a completely
different process ID!

In general, it seems that pthreads/fork/execl/wait do not work together on
Linux - and that pthreads is still a bit buggy! and they are essential to
the AIX/HP-UX application that I am porting!

Compile this source to test the original problem it:
....................

/*
 *  sigwaittest.c - a pthreads facility tester!
 *
 * This program tests the ability of a pthreads implementation to handle
 * SIGCHLD correctly - some don't. e.g. AIX and HP-UX send the SIGCHLD
 * signal to any thread that waits on SIGCHLD (correct), whereas Linux 2.2
 * and Solaris 2.5 send it directly to the forking thread (wrong, I think);
 *
 * To compile:
 *
 *  Linux:      cc   -o sigwaittest -lpthread  sigwaittest.c
 *  HP-UX:      cc   -o sigwaittest -lpthread  sigwaittest.c
 *  AIX:        cc_r -o sigwaittest -lpthreads sigwaittest.c
 *  SOLARIS:    CC   -o sigwaittest -lpthread -DSOLARIS
 *                      -D_POSIX_PTHREAD_SEMANTICS sigwaittest.c
 *
 * To run:
 *
 *   Enter
 *     sigwaittest<ENTER>
 *
 *   You SHOULD get the following if it works:
 *
 *      Main: entry - pid=<pid>
 *      Main: Blocking SIGCHLD
 *      Main: Initializing condition
 *      Main: Initializing thread attributes:
 *   Main: kicking off thread
 *      FORKER: forking
 *      CHILD: entry - pid=<pid>
 *      THREAD HANDLER: entry - pid=<pid>
 *      FORKER: got child <pid>!
 *      FORKER: Waiting on condition
 *      THREAD HANDLER: Waiting on sig_chld
 *          <A few seconds wait>
 *      SHL: quiting
 *      THREAD HANDLER: Caught signal <signal number> (SIGCHLD)
 *      THREAD HANDLER: Getting child info
 *      THREAD HANDLER: wait pid=<pid>, stat=0 (errno=0/<err msg>)
 *      THREAD HANDLER: Setting condition
 *      THREAD HANDLER: quiting
 *      FORKER: Condition set; quiting
 *      Main: quiting
 *
 *  or this if it fails:
 *
 *      Main: entry - pid=<pid>
 *      Main: Blocking SIGCHLD
 *      Main: Initializing condition
 *      Main: Initializing thread attributes:
 *      Main: kicking off thread
 *      FORKER: forking
 *      FORKER: got child <pid>!
 *      CHILD: entry - pid=<pid>
 *      FORKER: Waiting on condition
 *      THREAD HANDLER: entry - pid=<pid>
 *      THREAD HANDLER: Waiting on sig_chld
 *          <A few seconds wait>
 *      SHL: quiting
 *          <An infinite wait. Hit CTRL-C to release it>
 *      THREAD HANDLER: Caught signal <signal number> (NOT SIGCHLD)
 *      THREAD HANDLER: Setting condition
 *      THREAD HANDLER: quiting
 *      FORKER: Condition set; quiting
 *      Main: quiting
 *
 *  Note that the order of some messages may differ since we are talking
 *  about multpile threads of execution here.
 */
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
extern char *sys_errlist[];
#if SOLARIS
/*
 * SOLARIS differences
 */
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#define SHL "/sbin/sh"

#else

#include <sys/wait.h>
#include <sys/signal.h>

#if _AIX
/*
 * AIX differences
 */
#define pthread_sigmask(__A,__B,__C) sigthreadmask(__A,__B,__C)
#define PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED
#define SHL "/usr/bin/sh"

#elif _HPUX_SOURCE
/*
 * HP-UX
 */
#define SHL "/usr/bin/sh"

#else
/*
 * LINUX
 */
#define SHL "/bin/sh"

#endif
#endif

pthread_mutex_t cond_mutex;
pthread_cond_t  cond;
int done = 0;

void *thread_handler_routine(void *should_i);
void  forker_routine(void);

int main()
{
    pthread_t pt1;
    sigset_t set, old;
    pthread_attr_t pta;

    printf("Main: entry - pid=%i\n", getpid());

    sigfillset(&set);
    sigaddset(&set, SIGCHLD);

    printf("Main: Blocking SIGCHLD\n");

    pthread_sigmask(SIG_BLOCK, &set, &old);

    printf("Main: Initializing condition\n");

    pthread_cond_init( &cond,       NULL);
    pthread_mutex_init(&cond_mutex, NULL);

    printf("Main: Initializing thread attributes:\n");
    pthread_attr_init(&pta);
    pthread_attr_setdetachstate(&pta, PTHREAD_CREATE_JOINABLE);

    printf("Main: kicking off thread\n");

    pthread_create(&pt1, &pta, thread_handler_routine, (void *)0);
    forker_routine();

    pthread_join(pt1, NULL);

    printf("Main: quiting\n");

   return 0;
}


void forker_routine(void)
{

    pid_t chld;

    printf("FORKER: forking\n");

    if ((chld = fork()) == 0)
    {
        printf("CHILD: entry - pid=%i\n", getpid());
        execl(SHL, "sh", "-c", "sleep 10; echo SHELL: quiting", NULL);
        printf("CHILD: quiting\n");
        exit(0);
    }
    else
    {
        printf("FORKER: got child %i!\n", chld);
        printf("FORKER: Waiting on condition\n");

        pthread_mutex_lock(&cond_mutex);
        if (!done)
            pthread_cond_wait(&cond, &cond_mutex);
        pthread_mutex_unlock(&cond_mutex);

        printf("FORKER: Condition set; quiting\n");
        return;
    }
}


void *thread_handler_routine(void *parm)
{
    pid_t pid;
    sigset_t set, old;
    int sig, status;

    printf("THREAD HANDLER: entry - pid=%i\n", getpid());

    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigaddset(&set, SIGQUIT); /* for safety! */
    sigaddset(&set, SIGINT);  /* for safety! */

    printf("THREAD HANDLER: Waiting on sig_chld\n");

    sigwait(&set, &sig);

    printf("THREAD HANDLER: Caught signal %i (%s)\n",
        sig, (sig == SIGCHLD) ? "SIGCHLD" : "NOT SIGCHLD");

    if (sig == SIGCHLD)
    {
        printf("THREAD HANDLER: Getting child info\n");
        errno=0;
        while ((pid = waitpid(-1, &status, WNOHANG)) == -1
                && errno == EINTR);

        printf("THREAD HANDLER: wait: pid=%i, stat=%i (errno=%i/%s)\n",
          pid, status, errno, sys_errlist[errno]);
    }

    printf("THREAD HANDLER: Setting condition\n");

    pthread_mutex_lock(&cond_mutex);
    done = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&cond_mutex);

    printf("THREAD HANDLER: quiting\n");

    return NULL;
}

---------------------------------

Comment 1 Lamar Owen 1999-12-07 16:41:59 UTC
GNU PR# 1273: http://www-gnats.gnu.org:8080/cgi-bin/wwwgnats.pl/full/1273
GNU PR# 1305: http://www-gnats.gnu.org:8080/cgi-bin/wwwgnats.pl/full/1305

The short of it is a need to upgrade to 2.1.3 for certain programs.  The
multithreaded webserver AOLserver also has problems with glibc 2.1.2, whereas it
did not have problems with 2.1.1.

--
Lamar Owen
WGCR Internet Radio

Comment 2 Cristian Gafton 2000-05-22 14:53:59 UTC
assign to jakub

Comment 3 Stephen John Smoogen 2003-01-24 23:44:06 UTC
Bug 7510 seems to have been fixed in newer versions of glibc that came out after
6.1. You SHOULD test this against the 8.0.93 Phoebe beta since it incorporates a
new threading model in glibc. It will also have more eyes on fixing it in that
release.