Index: Python/ceval.c =================================================================== --- Python/ceval.c (revision 53554) +++ Python/ceval.c (working copy) @@ -825,6 +825,13 @@ a thread switch */ _Py_Ticker = 0; } + /* check for signals (signals no longer 'add + * pending calls' since the 'pending calls' + * system does not appear to be async safe. */ + if (PyOS_InterruptOccurred()) { + why = WHY_EXCEPTION; + goto on_error; + } #ifdef WITH_THREAD if (interpreter_lock) { /* Give another thread a chance */ Index: Python/sigcheck.c =================================================================== --- Python/sigcheck.c (revision 53554) +++ Python/sigcheck.c (working copy) @@ -17,3 +17,6 @@ PyErr_SetNone(PyExc_KeyboardInterrupt); return -1; } + +int Py_signal_pipe = -1; + Index: Include/pyerrors.h =================================================================== --- Include/pyerrors.h (revision 53554) +++ Include/pyerrors.h (working copy) @@ -239,6 +239,11 @@ PyAPI_FUNC(int) PyErr_CheckSignals(void); PyAPI_FUNC(void) PyErr_SetInterrupt(void); +/* Read end of the pipe used to communicate signals to the + * main thread / main loop; Equal to -1 if the platform doesn't + * support signals. */ +PyAPI_DATA(int) Py_signal_pipe; + /* Support for adding program text to SyntaxErrors */ PyAPI_FUNC(void) PyErr_SyntaxLocation(const char *, int); PyAPI_FUNC(PyObject *) PyErr_ProgramText(const char *, int); Index: Doc/lib/libsignal.tex =================================================================== --- Doc/lib/libsignal.tex (revision 53554) +++ Doc/lib/libsignal.tex (working copy) @@ -59,6 +59,13 @@ means that signals can't be used as a means of inter-thread communication. Use locks instead. +\item +In all operating systems signal handling is never completely reliable. +For instance, two identical signals delivered to a process in a short +period of time can be merged into a single signal handler call. In +addition, a burst of many consecutive signals in a short period of +time might cause some of the latter signals to be lost. + \end{itemize} The variables defined in the \module{signal} module are: @@ -143,6 +150,11 @@ for a description of frame objects, see the reference manual section on the standard type hierarchy or see the attribute descriptions in the \refmodule{inspect} module). + + \warning{When installing a new signal handler, it is possible + (though unlikely) that the new handler will be called to handle + some signals that were triggered when an older handler was + installed.} \end{funcdesc} \subsection{Example} Index: Doc/api/exceptions.tex =================================================================== --- Doc/api/exceptions.tex (revision 53554) +++ Doc/api/exceptions.tex (working copy) @@ -333,6 +333,27 @@ may not be cleared if it was previously set. \end{cfuncdesc} +\begin{cvardesc}{int}{Py_signal_pipe} + File descriptor corresponding to the read end of a pipe used to + communicate signals to the main thread / main loop. It is equal to + \code{-1} on platforms that do not support signals. + + This file descriptor can be used in \cfunction{poll()} based main + loops defined in 3rd party extension modules to find out when they + should call \cfunction{PyErr_CheckSignals()} to handle incoming + signals. The alternative of using a timeout to periodically check + for signals should be avoided, as it causes processes to needlessly + wake up many times per second, thus consuming more energy in modern + CPUs. + + \warning{Extensions should never read from this descriptor; only + poll it. The only correct way to use it is to poll + \var{Py_signal_pipe} for an ``input condition'', and call + \cfunction{PyErr_CheckSignals()} when such condition occurs.} + + \versionadded{2.6} +\end{cvardesc} + \begin{cfuncdesc}{void}{PyErr_SetInterrupt}{} This function simulates the effect of a \constant{SIGINT}\ttindex{SIGINT} signal arriving --- the next time Index: Modules/signalmodule.c =================================================================== --- Modules/signalmodule.c (revision 53554) +++ Modules/signalmodule.c (working copy) @@ -11,6 +11,8 @@ #endif #include +#include +#include #ifndef SIG_ERR #define SIG_ERR ((PyOS_sighandler_t)(-1)) @@ -33,6 +35,10 @@ # endif #endif +#include +#if NSIG > UCHAR_MAX +# error Too many signals to fit in an unsigned char! +#endif /* NOTES ON THE INTERACTION BETWEEN SIGNALS AND THREADS @@ -70,12 +76,17 @@ static pid_t main_pid; #endif +/* Pipe used to communicate signals to the main thread / main loop */ +int Py_signal_pipe = -1; /* read end */ +static int Py_signal_pipe_w; /* write end */ + static struct { - int tripped; PyObject *func; } Handlers[NSIG]; -static int is_tripped = 0; /* Speed up sigcheck() when none tripped */ +static volatile sig_atomic_t is_tripped = 0; /* Speed up signal + * checking when none tripped */ +static volatile int interrupt_occurred = 0; static PyObject *DefaultHandler; static PyObject *IgnoreHandler; @@ -92,6 +103,7 @@ static PyObject * signal_default_int_handler(PyObject *self, PyObject *args) { + interrupt_occurred = 1; PyErr_SetNone(PyExc_KeyboardInterrupt); return NULL; } @@ -103,31 +115,25 @@ It raises KeyboardInterrupt."); -static int -checksignals_witharg(void * unused) -{ - return PyErr_CheckSignals(); -} - static void signal_handler(int sig_num) { + unsigned char signum_c = sig_num; #ifdef WITH_THREAD -#ifdef WITH_PTH +# ifdef WITH_PTH if (PyThread_get_thread_ident() != main_thread) { pth_raise(*(pth_t *) main_thread, sig_num); return; } +# endif #endif - /* See NOTES section above */ - if (getpid() == main_pid) { -#endif - is_tripped++; - Handlers[sig_num].tripped = 1; - Py_AddPendingCall(checksignals_witharg, NULL); -#ifdef WITH_THREAD - } -#endif + /* If writing to the pipe fails, there is nothing we can + * safely do from a signal handler, so we just ignore + * the return value. The possibility of missing signals + * during a hypothetical signal burst is documented. */ + write(Py_signal_pipe_w, &signum_c, sizeof(signum_c)); + is_tripped = 1; + #ifdef SIGCHLD if (sig_num == SIGCHLD) { /* To avoid infinite recursion, this signal remains @@ -219,7 +225,6 @@ return NULL; } old_handler = Handlers[sig_num].func; - Handlers[sig_num].tripped = 0; Py_INCREF(obj); Handlers[sig_num].func = obj; return old_handler; @@ -309,6 +314,7 @@ { PyObject *m, *d, *x; int i; + int filedes[2], fd_flags; #ifdef WITH_THREAD main_thread = PyThread_get_thread_ident(); @@ -336,16 +342,37 @@ goto finally; Py_DECREF(x); +#define set_nonblock(fd) \ + if ((fd_flags = fcntl(fd, F_GETFL, 0)) == -1) { \ + PyErr_SetFromErrno(PyExc_RuntimeError); \ + return; \ + } \ + fd_flags |= O_NONBLOCK; \ + if (fcntl(fd, F_SETFL, fd_flags) == -1) { \ + PyErr_SetFromErrno(PyExc_RuntimeError); \ + return; \ + } + + if (Py_signal_pipe == -1) { + if (pipe(filedes)) { + PyErr_SetFromErrno(PyExc_RuntimeError); + return; + } + set_nonblock(filedes[0]); + set_nonblock(filedes[1]); + Py_signal_pipe = filedes[0]; + Py_signal_pipe_w = filedes[1]; + } +#undef set_nonblock + x = IntHandler = PyDict_GetItemString(d, "default_int_handler"); if (!x) goto finally; Py_INCREF(IntHandler); - Handlers[0].tripped = 0; for (i = 1; i < NSIG; i++) { void (*t)(int); t = PyOS_getsig(i); - Handlers[i].tripped = 0; if (t == SIG_DFL) Handlers[i].func = DefaultHandler; else if (t == SIG_IGN) @@ -571,7 +598,6 @@ for (i = 1; i < NSIG; i++) { func = Handlers[i].func; - Handlers[i].tripped = 0; Handlers[i].func = NULL; if (i != SIGINT && func != NULL && func != Py_None && func != DefaultHandler && func != IgnoreHandler) @@ -592,27 +618,35 @@ int PyErr_CheckSignals(void) { - int i; PyObject *f; + unsigned char signum; - if (!is_tripped) - return 0; + if (!is_tripped) + return 0; + #ifdef WITH_THREAD if (PyThread_get_thread_ident() != main_thread) return 0; #endif + if (!(f = (PyObject *)PyEval_GetFrame())) f = Py_None; - for (i = 1; i < NSIG; i++) { - if (Handlers[i].tripped) { + while (is_tripped) + { + is_tripped = 0; + /* Notice: assuming read() acts as memory barrier */ + while (read(Py_signal_pipe, &signum, sizeof(signum)) == sizeof(signum)) + { PyObject *result = NULL; - PyObject *arglist = Py_BuildValue("(iO)", i, f); - Handlers[i].tripped = 0; + PyObject *arglist = Py_BuildValue("(iO)", signum, f); + PyObject *func = Handlers[signum].func; + if (func == NULL) + continue; + if (arglist) { - result = PyEval_CallObject(Handlers[i].func, - arglist); + result = PyEval_CallObject(func, arglist); Py_DECREF(arglist); } if (!result) @@ -621,7 +655,6 @@ Py_DECREF(result); } } - is_tripped = 0; return 0; } @@ -632,9 +665,9 @@ void PyErr_SetInterrupt(void) { - is_tripped++; - Handlers[SIGINT].tripped = 1; - Py_AddPendingCall((int (*)(void *))PyErr_CheckSignals, NULL); + unsigned char signum = SIGINT; + write(Py_signal_pipe_w, &signum, sizeof(signum)); + is_tripped = 1; } void @@ -653,15 +686,11 @@ int PyOS_InterruptOccurred(void) { - if (Handlers[SIGINT].tripped) { -#ifdef WITH_THREAD - if (PyThread_get_thread_ident() != main_thread) - return 0; -#endif - Handlers[SIGINT].tripped = 0; - return 1; - } - return 0; + int intr; + PyErr_CheckSignals(); + intr = interrupt_occurred; + interrupt_occurred = 0; + return intr; } void