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