This is the mail archive of the libc-alpha@sources.redhat.com mailing list for the glibc 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]

Re: Cancellation & async signal handlers


Oops; I have a test program which I forgot to attach.

/*
 * $Id$
 * Scott Lamb <slamb@slamb.org>
 *
 * Tests cancellation during an async signal. SUSv3 does not precisely specify
 * the semantics for cancellation during async signal handlers, though it does
 * mention this:
 *
 *     "There is no notion of a cancellation cleanup-safe function. If an
 *     application has no cancellation points in its signal handlers, blocks
 *     any signal whose handler may have cancellation points while calling
 *     async-unsafe functions, or disables cancellation while calling
 *     async-unsafe functions, all functions may be safely called from
 *     cancellation cleanup routines."
 *
 * <http://www.opengroup.org/onlinepubs/007904975/functions/pthread_cleanup_push.html>
 *
 * I want threads to be cancelable in select() and to be able to note async
 * signals through the pipe write()-within-sighandler pattern. select() and
 * write() are both cancelable. select() is async signal-safe.
 * pthread_setcancelstate() and pthread_setcanceltype() are not async
 * signal-safe.
 *
 * My concern is that the async signal's write() may have the effect of
 * changing the cancellation type to asynchronous. Around the kernel system
 * call, this should be true anyway, but select() may have some setup and
 * teardown code, such as the pthread_setcanceltype() itself, which does not
 * expect asynchronous cancellation when canceltype == PTHREAD_DEFERRED.
 *
 * Thus this test. It creates a SIGUSR1 in the region of select() and then,
 * through a read() and write(), forces cancellation during the signal
 * handler. Unfortunately, I'm looking for a race, so it has to be run many
 * times to have a good chance of spotting the problem, if it exists.
 *
 * I suggest running this test like this:
 *
 *     $ while ./test_cancellation_in_sighandler; do date; done
 *
 * and letting it go for a while. If it stops advancing dates, you've found a
 * problem.
 *
 * Note that this has no reasonable hope of working unless
 * test_cancelpoint_(read|write|select) all succeed.
 */

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/select.h>

/* Stupid Darwin doesn't even have PTHREAD_CANCELED. Workaround. */
#define NOT_CANCELED ((void*)0x42)

enum PipeHalf {
    READ = 0,
    WRITE
};

int cancel_pipe[2], wake_select_pipe[2];

enum ErrorReturnType {
    DIRECT = 0,
    ERRNO
};

void sigusr1_handler(int signo) {
    char c = 26;
    int old_errno = errno;
    int retval;

    /* Tell the main thread that we are in the signal handler */
    if (write(cancel_pipe[WRITE], &c, sizeof(char)) != sizeof(char)) {
        write(2, "write failure in sigusr1_handler\n",
                sizeof("write failure in sigusr1_handler\n")-1);
        abort();
    }

    /* Wait for main thread to tell us that it has sent the cancellation
     * request. */
    while ((retval = read(cancel_pipe[READ], &c, sizeof(char))) == -1
           && errno == EINTR) ;
    if (retval != sizeof(char)) {
        write(2, "read failure in sigusr1_handler\n",
                 sizeof("read failure in sigusr1_handler\n")-1);
        abort();
    }

    /* Let the select() know we received a signal. */
    if (write(wake_select_pipe[WRITE], &c, sizeof(char)) != sizeof(char)) {
        write(2, "write failure in sigusr1_handler\n",
                sizeof("write failure in sigusr1_handler\n")-1);
        abort();
    }

    errno = old_errno;
}

void error_wrap(int retval, const char *funcname, enum ErrorReturnType type) {
    if (type == ERRNO && retval < 0) {
        fprintf(stderr, "%s returned %d (%s)\n",
                funcname, errno, strerror(errno));
        abort();
    } else if (type == DIRECT && retval != 0) {
        fprintf(stderr, "%s returned %d (%s)\n",
                funcname, retval, strerror(retval));
        abort();
    }
}

void* cancel_thread_main(void *ignored_argument) {
    fd_set readset;
    int retval;
    char c = 26;

    FD_ZERO(&readset);
    FD_SET(wake_select_pipe[READ], &readset);

    /* Fire the starting gun of the race */
    error_wrap(write(cancel_pipe[WRITE], &c, sizeof(char)), "write", ERRNO);

    /* And we're off. */
    while ((retval = select(wake_select_pipe[READ]+1, &readset,
                            NULL, NULL, NULL)) == -1 && errno == EINTR) ;
    return NOT_CANCELED;
}

int main(void) {
    pthread_t cancel_thread;
    void *retval;
    struct sigaction sa;
    char c = 26;

    /* Setup work */
    error_wrap(pipe(cancel_pipe), "pipe", ERRNO);
    error_wrap(pipe(wake_select_pipe), "pipe", ERRNO);
    sa.sa_handler = &sigusr1_handler;
    sa.sa_flags = 0;
    error_wrap(sigemptyset(&sa.sa_mask), "sigemptyset", ERRNO);
    error_wrap(sigaction(SIGUSR1, &sa, NULL), "sigaction", ERRNO);
    error_wrap(pthread_create(&cancel_thread, NULL, &cancel_thread_main, NULL),
               "pthread_create", DIRECT);

    /* Wait for the race to start */
    error_wrap(read(cancel_pipe[READ], &c, sizeof(char)), "read", ERRNO);

    /* Get into the signal handler */
    error_wrap(pthread_kill(cancel_thread, SIGUSR1), "pthread_kill", DIRECT);
    error_wrap(read(cancel_pipe[READ], &c, sizeof(char)), "read", ERRNO);

    /* Now we know it is waiting for us. Cancel and wake it. */
    error_wrap(pthread_cancel(cancel_thread), "pthread_cancel", DIRECT);
    error_wrap(write(cancel_pipe[WRITE], &c, sizeof(char)), "write", ERRNO);

    error_wrap(pthread_join(cancel_thread, &retval), "pthread_join", DIRECT);
    return (retval == NOT_CANCELED) ? EXIT_FAILURE : EXIT_SUCCESS;
}

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