This is the mail archive of the
libc-alpha@sources.redhat.com
mailing list for the glibc project.
Re: Cancellation & async signal handlers
- From: Scott Lamb <slamb at slamb dot org>
- To: Scott Lamb <slamb at slamb dot org>
- Cc: libc-alpha at sources dot redhat dot com
- Date: Thu, 05 Feb 2004 13:36:07 -0600
- Subject: Re: Cancellation & async signal handlers
- References: <40229A45.9080503@slamb.org>
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;
}