summaryrefslogtreecommitdiff
path: root/Python/ceval.c
diff options
context:
space:
mode:
authorAntoine Pitrou <pitrou@free.fr>2017-06-28 23:29:29 +0200
committerGitHub <noreply@github.com>2017-06-28 23:29:29 +0200
commitc08177a1ccad2ed0d50898c2731b518c631aed14 (patch)
treec4f495928530cdffb077bb5bf0cd69d96626f71f /Python/ceval.c
parent9f3bdcb643623e07497af2fc35f0496c2302f1be (diff)
downloadcpython-git-c08177a1ccad2ed0d50898c2731b518c631aed14.tar.gz
bpo-30703: Improve signal delivery (#2415)
* Improve signal delivery Avoid using Py_AddPendingCall from signal handler, to avoid calling signal-unsafe functions. * Remove unused function * Improve comments * Add stress test * Adapt for --without-threads * Add second stress test * Add NEWS blurb * Address comments @haypo
Diffstat (limited to 'Python/ceval.c')
-rw-r--r--Python/ceval.c75
1 files changed, 54 insertions, 21 deletions
diff --git a/Python/ceval.c b/Python/ceval.c
index 3243a4f8a0..0f6978e569 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -140,6 +140,15 @@ static long dxp[256];
do { pending_async_exc = 0; COMPUTE_EVAL_BREAKER(); } while (0)
+/* This single variable consolidates all requests to break out of the fast path
+ in the eval loop. */
+static _Py_atomic_int eval_breaker = {0};
+/* Request for running pending calls. */
+static _Py_atomic_int pendingcalls_to_do = {0};
+/* Request for looking at the `async_exc` field of the current thread state.
+ Guarded by the GIL. */
+static int pending_async_exc = 0;
+
#ifdef WITH_THREAD
#ifdef HAVE_ERRNO_H
@@ -149,16 +158,8 @@ static long dxp[256];
static PyThread_type_lock pending_lock = 0; /* for pending calls */
static unsigned long main_thread = 0;
-/* This single variable consolidates all requests to break out of the fast path
- in the eval loop. */
-static _Py_atomic_int eval_breaker = {0};
/* Request for dropping the GIL */
static _Py_atomic_int gil_drop_request = {0};
-/* Request for running pending calls. */
-static _Py_atomic_int pendingcalls_to_do = {0};
-/* Request for looking at the `async_exc` field of the current thread state.
- Guarded by the GIL. */
-static int pending_async_exc = 0;
#include "ceval_gil.h"
@@ -253,9 +254,6 @@ PyEval_ReInitThreads(void)
_PyThreadState_DeleteExcept(current_tstate);
}
-#else
-static _Py_atomic_int eval_breaker = {0};
-static int pending_async_exc = 0;
#endif /* WITH_THREAD */
/* This function is used to signal that async exceptions are waiting to be
@@ -330,6 +328,15 @@ PyEval_RestoreThread(PyThreadState *tstate)
#endif
*/
+void
+_PyEval_SignalReceived(void)
+{
+ /* bpo-30703: Function called when the C signal handler of Python gets a
+ signal. We cannot queue a callback using Py_AddPendingCall() since
+ that function is not async-signal-safe. */
+ SIGNAL_PENDING_CALLS();
+}
+
#ifdef WITH_THREAD
/* The WITH_THREAD implementation is thread-safe. It allows
@@ -394,6 +401,8 @@ Py_MakePendingCalls(void)
int i;
int r = 0;
+ assert(PyGILState_Check());
+
if (!pending_lock) {
/* initial allocation of the lock */
pending_lock = PyThread_allocate_lock();
@@ -408,6 +417,16 @@ Py_MakePendingCalls(void)
if (busy)
return 0;
busy = 1;
+ /* unsignal before starting to call callbacks, so that any callback
+ added in-between re-signals */
+ UNSIGNAL_PENDING_CALLS();
+
+ /* Python signal handler doesn't really queue a callback: it only signals
+ that a signal was received, see _PyEval_SignalReceived(). */
+ if (PyErr_CheckSignals() < 0) {
+ goto error;
+ }
+
/* perform a bounded number of calls, in case of recursion */
for (i=0; i<NPENDINGCALLS; i++) {
int j;
@@ -424,20 +443,23 @@ Py_MakePendingCalls(void)
arg = pendingcalls[j].arg;
pendingfirst = (j + 1) % NPENDINGCALLS;
}
- if (pendingfirst != pendinglast)
- SIGNAL_PENDING_CALLS();
- else
- UNSIGNAL_PENDING_CALLS();
PyThread_release_lock(pending_lock);
/* having released the lock, perform the callback */
if (func == NULL)
break;
r = func(arg);
- if (r)
- break;
+ if (r) {
+ goto error;
+ }
}
+
busy = 0;
return r;
+
+error:
+ busy = 0;
+ SIGNAL_PENDING_CALLS(); /* We're not done yet */
+ return -1;
}
#else /* if ! defined WITH_THREAD */
@@ -472,7 +494,6 @@ static struct {
} pendingcalls[NPENDINGCALLS];
static volatile int pendingfirst = 0;
static volatile int pendinglast = 0;
-static _Py_atomic_int pendingcalls_to_do = {0};
int
Py_AddPendingCall(int (*func)(void *), void *arg)
@@ -506,7 +527,16 @@ Py_MakePendingCalls(void)
if (busy)
return 0;
busy = 1;
+
+ /* unsignal before starting to call callbacks, so that any callback
+ added in-between re-signals */
UNSIGNAL_PENDING_CALLS();
+ /* Python signal handler doesn't really queue a callback: it only signals
+ that a signal was received, see _PyEval_SignalReceived(). */
+ if (PyErr_CheckSignals() < 0) {
+ goto error;
+ }
+
for (;;) {
int i;
int (*func)(void *);
@@ -518,13 +548,16 @@ Py_MakePendingCalls(void)
arg = pendingcalls[i].arg;
pendingfirst = (i + 1) % NPENDINGCALLS;
if (func(arg) < 0) {
- busy = 0;
- SIGNAL_PENDING_CALLS(); /* We're not done yet */
- return -1;
+ goto error:
}
}
busy = 0;
return 0;
+
+error:
+ busy = 0;
+ SIGNAL_PENDING_CALLS(); /* We're not done yet */
+ return -1;
}
#endif /* WITH_THREAD */