From 0e91938e5851173821f917bf19157dde5f946c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Fri, 9 Jan 2009 20:31:26 +0000 Subject: Issue 4293: Make Py_AddPendingCall() thread safe --- Python/ceval.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 146 insertions(+), 22 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index 09501af..88483a5 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -213,6 +213,7 @@ PyEval_GetCallStats(PyObject *self) #include "pythread.h" static PyThread_type_lock interpreter_lock = 0; /* This is the GIL */ +static PyThread_type_lock pending_lock = 0; /* for pending calls */ static long main_thread = 0; int @@ -284,6 +285,7 @@ PyEval_ReInitThreads(void) adding a new function to each thread_*.h. Instead, just create a new lock and waste a little bit of memory */ interpreter_lock = PyThread_allocate_lock(); + pending_lock = PyThread_allocate_lock(); PyThread_acquire_lock(interpreter_lock, 1); main_thread = PyThread_get_thread_ident(); @@ -356,19 +358,145 @@ PyEval_RestoreThread(PyThreadState *tstate) #ifdef WITH_THREAD Any thread can schedule pending calls, but only the main thread will execute them. + There is no facility to schedule calls to a particular thread, but + that should be easy to change, should that ever be required. In + that case, the static variables here should go into the python + threadstate. #endif +*/ + +#ifdef WITH_THREAD + +/* The WITH_THREAD implementation is thread-safe. It allows + scheduling to be made from any thread, and even from an executing + callback. + */ + +#define NPENDINGCALLS 32 +static struct { + int (*func)(void *); + void *arg; +} pendingcalls[NPENDINGCALLS]; +static int pendingfirst = 0; +static int pendinglast = 0; +static volatile int pendingcalls_to_do = 1; /* trigger initialization of lock */ +static char pendingbusy = 0; + +int +Py_AddPendingCall(int (*func)(void *), void *arg) +{ + int i, j, result=0; + PyThread_type_lock lock = pending_lock; + + /* try a few times for the lock. Since this mechanism is used + * for signal handling (on the main thread), there is a (slim) + * chance that a signal is delivered on the same thread while we + * hold the lock during the Py_MakePendingCalls() function. + * This avoids a deadlock in that case. + * Note that signals can be delivered on any thread. In particular, + * on Windows, a SIGINT is delivered on a system-created worker + * thread. + * We also check for lock being NULL, in the unlikely case that + * this function is called before any bytecode evaluation takes place. + */ + if (lock != NULL) { + for (i = 0; i<100; i++) { + if (PyThread_acquire_lock(lock, NOWAIT_LOCK)) + break; + } + if (i == 100) + return -1; + } + + i = pendinglast; + j = (i + 1) % NPENDINGCALLS; + if (j == pendingfirst) { + result = -1; /* Queue full */ + } else { + pendingcalls[i].func = func; + pendingcalls[i].arg = arg; + pendinglast = j; + } + /* signal main loop */ + _Py_Ticker = 0; + pendingcalls_to_do = 1; + if (lock != NULL) + PyThread_release_lock(lock); + return result; +} + +int +Py_MakePendingCalls(void) +{ + int i; + int r = 0; - XXX WARNING! ASYNCHRONOUSLY EXECUTING CODE! + if (!pending_lock) { + /* initial allocation of the lock */ + pending_lock = PyThread_allocate_lock(); + if (pending_lock == NULL) + return -1; + } + + /* only service pending calls on main thread */ + if (main_thread && PyThread_get_thread_ident() != main_thread) + return 0; + /* don't perform recursive pending calls */ + if (pendingbusy) + return 0; + pendingbusy = 1; + /* perform a bounded number of calls, in case of recursion */ + for (i=0; i