mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Merged revisions 68460 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r68460 | kristjan.jonsson | 2009-01-09 14:31:26 -0600 (Fri, 09 Jan 2009) | 1 line Issue 4293: Make Py_AddPendingCall() thread safe ........
This commit is contained in:
parent
92d0a6a559
commit
e5bf383959
1 changed files with 146 additions and 22 deletions
168
Python/ceval.c
168
Python/ceval.c
|
@ -205,6 +205,7 @@ PyEval_GetCallStats(PyObject *self)
|
||||||
#include "pythread.h"
|
#include "pythread.h"
|
||||||
|
|
||||||
static PyThread_type_lock interpreter_lock = 0; /* This is the GIL */
|
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;
|
static long main_thread = 0;
|
||||||
|
|
||||||
int
|
int
|
||||||
|
@ -276,6 +277,7 @@ PyEval_ReInitThreads(void)
|
||||||
adding a new function to each thread_*.h. Instead, just
|
adding a new function to each thread_*.h. Instead, just
|
||||||
create a new lock and waste a little bit of memory */
|
create a new lock and waste a little bit of memory */
|
||||||
interpreter_lock = PyThread_allocate_lock();
|
interpreter_lock = PyThread_allocate_lock();
|
||||||
|
pending_lock = PyThread_allocate_lock();
|
||||||
PyThread_acquire_lock(interpreter_lock, 1);
|
PyThread_acquire_lock(interpreter_lock, 1);
|
||||||
main_thread = PyThread_get_thread_ident();
|
main_thread = PyThread_get_thread_ident();
|
||||||
|
|
||||||
|
@ -348,19 +350,145 @@ PyEval_RestoreThread(PyThreadState *tstate)
|
||||||
#ifdef WITH_THREAD
|
#ifdef WITH_THREAD
|
||||||
Any thread can schedule pending calls, but only the main thread
|
Any thread can schedule pending calls, but only the main thread
|
||||||
will execute them.
|
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
|
#endif
|
||||||
|
*/
|
||||||
|
|
||||||
XXX WARNING! ASYNCHRONOUSLY EXECUTING CODE!
|
#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;
|
||||||
|
|
||||||
|
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<NPENDINGCALLS; i++) {
|
||||||
|
int j;
|
||||||
|
int (*func)(void *);
|
||||||
|
void *arg;
|
||||||
|
|
||||||
|
/* pop one item off the queue while holding the lock */
|
||||||
|
PyThread_acquire_lock(pending_lock, WAIT_LOCK);
|
||||||
|
j = pendingfirst;
|
||||||
|
if (j == pendinglast) {
|
||||||
|
func = NULL; /* Queue empty */
|
||||||
|
} else {
|
||||||
|
func = pendingcalls[j].func;
|
||||||
|
arg = pendingcalls[j].arg;
|
||||||
|
pendingfirst = (j + 1) % NPENDINGCALLS;
|
||||||
|
}
|
||||||
|
pendingcalls_to_do = pendingfirst != pendinglast;
|
||||||
|
PyThread_release_lock(pending_lock);
|
||||||
|
/* having released the lock, perform the callback */
|
||||||
|
if (func == NULL)
|
||||||
|
break;
|
||||||
|
r = func(arg);
|
||||||
|
if (r)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pendingbusy = 0;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* if ! defined WITH_THREAD */
|
||||||
|
|
||||||
|
/*
|
||||||
|
WARNING! ASYNCHRONOUSLY EXECUTING CODE!
|
||||||
|
This code is used for signal handling in python that isn't built
|
||||||
|
with WITH_THREAD.
|
||||||
|
Don't use this implementation when Py_AddPendingCalls() can happen
|
||||||
|
on a different thread!
|
||||||
|
|
||||||
There are two possible race conditions:
|
There are two possible race conditions:
|
||||||
(1) nested asynchronous registry calls;
|
(1) nested asynchronous calls to Py_AddPendingCall()
|
||||||
(2) registry calls made while pending calls are being processed.
|
(2) AddPendingCall() calls made while pending calls are being processed.
|
||||||
While (1) is very unlikely, (2) is a real possibility.
|
|
||||||
|
(1) is very unlikely because typically signal delivery
|
||||||
|
is blocked during signal handling. So it should be impossible.
|
||||||
|
(2) is a real possibility.
|
||||||
The current code is safe against (2), but not against (1).
|
The current code is safe against (2), but not against (1).
|
||||||
The safety against (2) is derived from the fact that only one
|
The safety against (2) is derived from the fact that only one
|
||||||
thread (the main thread) ever takes things out of the queue.
|
thread is present, interrupted by signals, and that the critical
|
||||||
|
section is protected with the "busy" variable. On Windows, which
|
||||||
XXX Darn! With the advent of thread state, we should have an array
|
delivers SIGINT on a system thread, this does not hold and therefore
|
||||||
of pending calls per thread in the thread state! Later...
|
Windows really shouldn't use this version.
|
||||||
|
The two threads could theoretically wiggle around the "busy" variable.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define NPENDINGCALLS 32
|
#define NPENDINGCALLS 32
|
||||||
|
@ -370,7 +498,7 @@ static struct {
|
||||||
} pendingcalls[NPENDINGCALLS];
|
} pendingcalls[NPENDINGCALLS];
|
||||||
static volatile int pendingfirst = 0;
|
static volatile int pendingfirst = 0;
|
||||||
static volatile int pendinglast = 0;
|
static volatile int pendinglast = 0;
|
||||||
static volatile int things_to_do = 0;
|
static volatile int pendingcalls_to_do = 0;
|
||||||
|
|
||||||
int
|
int
|
||||||
Py_AddPendingCall(int (*func)(void *), void *arg)
|
Py_AddPendingCall(int (*func)(void *), void *arg)
|
||||||
|
@ -378,8 +506,6 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
|
||||||
static volatile int busy = 0;
|
static volatile int busy = 0;
|
||||||
int i, j;
|
int i, j;
|
||||||
/* XXX Begin critical section */
|
/* XXX Begin critical section */
|
||||||
/* XXX If you want this to be safe against nested
|
|
||||||
XXX asynchronous calls, you'll have to work harder! */
|
|
||||||
if (busy)
|
if (busy)
|
||||||
return -1;
|
return -1;
|
||||||
busy = 1;
|
busy = 1;
|
||||||
|
@ -394,7 +520,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
|
||||||
pendinglast = j;
|
pendinglast = j;
|
||||||
|
|
||||||
_Py_Ticker = 0;
|
_Py_Ticker = 0;
|
||||||
things_to_do = 1; /* Signal main loop */
|
pendingcalls_to_do = 1; /* Signal main loop */
|
||||||
busy = 0;
|
busy = 0;
|
||||||
/* XXX End critical section */
|
/* XXX End critical section */
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -404,14 +530,10 @@ int
|
||||||
Py_MakePendingCalls(void)
|
Py_MakePendingCalls(void)
|
||||||
{
|
{
|
||||||
static int busy = 0;
|
static int busy = 0;
|
||||||
#ifdef WITH_THREAD
|
|
||||||
if (main_thread && PyThread_get_thread_ident() != main_thread)
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
if (busy)
|
if (busy)
|
||||||
return 0;
|
return 0;
|
||||||
busy = 1;
|
busy = 1;
|
||||||
things_to_do = 0;
|
pendingcalls_to_do = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int i;
|
int i;
|
||||||
int (*func)(void *);
|
int (*func)(void *);
|
||||||
|
@ -424,7 +546,7 @@ Py_MakePendingCalls(void)
|
||||||
pendingfirst = (i + 1) % NPENDINGCALLS;
|
pendingfirst = (i + 1) % NPENDINGCALLS;
|
||||||
if (func(arg) < 0) {
|
if (func(arg) < 0) {
|
||||||
busy = 0;
|
busy = 0;
|
||||||
things_to_do = 1; /* We're not done yet */
|
pendingcalls_to_do = 1; /* We're not done yet */
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,6 +554,8 @@ Py_MakePendingCalls(void)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif /* WITH_THREAD */
|
||||||
|
|
||||||
|
|
||||||
/* The interpreter's recursion limit */
|
/* The interpreter's recursion limit */
|
||||||
|
|
||||||
|
@ -518,7 +642,7 @@ static int _Py_TracingPossible = 0;
|
||||||
/* for manipulating the thread switch and periodic "stuff" - used to be
|
/* for manipulating the thread switch and periodic "stuff" - used to be
|
||||||
per thread, now just a pair o' globals */
|
per thread, now just a pair o' globals */
|
||||||
int _Py_CheckInterval = 100;
|
int _Py_CheckInterval = 100;
|
||||||
volatile int _Py_Ticker = 100;
|
volatile int _Py_Ticker = 0; /* so that we hit a "tick" first thing */
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyEval_EvalCode(PyCodeObject *co, PyObject *globals, PyObject *locals)
|
PyEval_EvalCode(PyCodeObject *co, PyObject *globals, PyObject *locals)
|
||||||
|
@ -903,7 +1027,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
|
||||||
/* Do periodic things. Doing this every time through
|
/* Do periodic things. Doing this every time through
|
||||||
the loop would add too much overhead, so we do it
|
the loop would add too much overhead, so we do it
|
||||||
only every Nth instruction. We also do it if
|
only every Nth instruction. We also do it if
|
||||||
``things_to_do'' is set, i.e. when an asynchronous
|
``pendingcalls_to_do'' is set, i.e. when an asynchronous
|
||||||
event needs attention (e.g. a signal handler or
|
event needs attention (e.g. a signal handler or
|
||||||
async I/O handler); see Py_AddPendingCall() and
|
async I/O handler); see Py_AddPendingCall() and
|
||||||
Py_MakePendingCalls() above. */
|
Py_MakePendingCalls() above. */
|
||||||
|
@ -919,12 +1043,12 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
|
||||||
#ifdef WITH_TSC
|
#ifdef WITH_TSC
|
||||||
ticked = 1;
|
ticked = 1;
|
||||||
#endif
|
#endif
|
||||||
if (things_to_do) {
|
if (pendingcalls_to_do) {
|
||||||
if (Py_MakePendingCalls() < 0) {
|
if (Py_MakePendingCalls() < 0) {
|
||||||
why = WHY_EXCEPTION;
|
why = WHY_EXCEPTION;
|
||||||
goto on_error;
|
goto on_error;
|
||||||
}
|
}
|
||||||
if (things_to_do)
|
if (pendingcalls_to_do)
|
||||||
/* MakePendingCalls() didn't succeed.
|
/* MakePendingCalls() didn't succeed.
|
||||||
Force early re-execution of this
|
Force early re-execution of this
|
||||||
"periodic" code, possibly after
|
"periodic" code, possibly after
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue