diff options
author | Tim Peters <tim.peters@gmail.com> | 2006-08-10 22:45:34 (GMT) |
---|---|---|
committer | Tim Peters <tim.peters@gmail.com> | 2006-08-10 22:45:34 (GMT) |
commit | 4643c2fda1546d6d5b0b33a93ee84218da7ad78b (patch) | |
tree | 3b75481e12d7c671950abc4400de3960009662ce /Python/pystate.c | |
parent | 789c09d2cd8da14166846f007d53a56585b0d6c3 (diff) | |
download | cpython-4643c2fda1546d6d5b0b33a93ee84218da7ad78b.zip cpython-4643c2fda1546d6d5b0b33a93ee84218da7ad78b.tar.gz cpython-4643c2fda1546d6d5b0b33a93ee84218da7ad78b.tar.bz2 |
Followup to bug #1069160.
PyThreadState_SetAsyncExc(): internal correctness changes wrt
refcount safety and deadlock avoidance. Also added a basic test
case (relying on ctypes) and repaired the docs.
Diffstat (limited to 'Python/pystate.c')
-rw-r--r-- | Python/pystate.c | 39 |
1 files changed, 27 insertions, 12 deletions
diff --git a/Python/pystate.c b/Python/pystate.c index 3fae85b..f591a59 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -342,28 +342,43 @@ PyThreadState_GetDict(void) /* Asynchronously raise an exception in a thread. Requested by Just van Rossum and Alex Martelli. To prevent naive misuse, you must write your own extension - to call this. Must be called with the GIL held. - Returns the number of tstates modified; if it returns a number - greater than one, you're in trouble, and you should call it again - with exc=NULL to revert the effect. This raises no exceptions. */ + to call this, or use ctypes. Must be called with the GIL held. + Returns the number of tstates modified (normally 1, but 0 if `id` didn't + match any known thread id). Can be called with exc=NULL to clear an + existing async exception. This raises no exceptions. */ int PyThreadState_SetAsyncExc(long id, PyObject *exc) { PyThreadState *tstate = PyThreadState_GET(); PyInterpreterState *interp = tstate->interp; PyThreadState *p; - int count = 0; + + /* Although the GIL is held, a few C API functions can be called + * without the GIL held, and in particular some that create and + * destroy thread and interpreter states. Those can mutate the + * list of thread states we're traversing, so to prevent that we lock + * head_mutex for the duration. + */ HEAD_LOCK(); for (p = interp->tstate_head; p != NULL; p = p->next) { - if (p->thread_id != id) - continue; - Py_CLEAR(p->async_exc); - Py_XINCREF(exc); - p->async_exc = exc; - count += 1; + if (p->thread_id == id) { + /* Tricky: we need to decref the current value + * (if any) in p->async_exc, but that can in turn + * allow arbitrary Python code to run, including + * perhaps calls to this function. To prevent + * deadlock, we need to release head_mutex before + * the decref. + */ + PyObject *old_exc = p->async_exc; + Py_XINCREF(exc); + p->async_exc = exc; + HEAD_UNLOCK(); + Py_XDECREF(old_exc); + return 1; + } } HEAD_UNLOCK(); - return count; + return 0; } |