summaryrefslogtreecommitdiffstats
path: root/Python/pystate.c
diff options
context:
space:
mode:
authorTim Peters <tim.peters@gmail.com>2006-08-10 22:45:34 (GMT)
committerTim Peters <tim.peters@gmail.com>2006-08-10 22:45:34 (GMT)
commit4643c2fda1546d6d5b0b33a93ee84218da7ad78b (patch)
tree3b75481e12d7c671950abc4400de3960009662ce /Python/pystate.c
parent789c09d2cd8da14166846f007d53a56585b0d6c3 (diff)
downloadcpython-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.c39
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;
}