Skip to content

Commit b8b6d0c

Browse files
committed
Add PyThreadState_SetAsyncExc(long, PyObject *).
A new API (only accessible from C) to interrupt a thread by sending it an exception. This is not always effective, but might help some people. Requested by Just van Rossum and Alex Martelli. It is intentional that you have to write your own C extension to call it from Python. Docs will have to wait.
1 parent 90a2041 commit b8b6d0c

File tree

3 files changed

+51
-1
lines changed

3 files changed

+51
-1
lines changed

Include/pystate.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ typedef struct _ts {
7474
int tick_counter;
7575
int gilstate_counter;
7676

77+
PyObject *async_exc; /* Asynchronous exception to raise */
78+
long thread_id; /* Thread id where this tstate was created */
79+
7780
/* XXX signal handlers should also be here */
7881

7982
} PyThreadState;
@@ -93,6 +96,7 @@ PyAPI_FUNC(void) PyThreadState_DeleteCurrent(void);
9396
PyAPI_FUNC(PyThreadState *) PyThreadState_Get(void);
9497
PyAPI_FUNC(PyThreadState *) PyThreadState_Swap(PyThreadState *);
9598
PyAPI_FUNC(PyObject *) PyThreadState_GetDict(void);
99+
PyAPI_FUNC(int) PyThreadState_SetAsyncExc(long, PyObject *);
96100

97101

98102
/* Variable and macro for in-line access to current thread state */

Python/ceval.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ static PyTypeObject gentype = {
290290

291291
extern int _PyThread_Started; /* Flag for Py_Exit */
292292

293-
static PyThread_type_lock interpreter_lock = 0;
293+
static PyThread_type_lock interpreter_lock = 0; /* This is the GIL */
294294
static long main_thread = 0;
295295

296296
void
@@ -773,6 +773,11 @@ eval_frame(PyFrameObject *f)
773773
Py_MakePendingCalls() above. */
774774

775775
if (--_Py_Ticker < 0) {
776+
if (*next_instr == SETUP_FINALLY) {
777+
/* Make the last opcode before
778+
a try: finally: block uninterruptable. */
779+
goto fast_next_opcode;
780+
}
776781
_Py_Ticker = _Py_CheckInterval;
777782
tstate->tick_counter++;
778783
if (things_to_do) {
@@ -805,6 +810,17 @@ eval_frame(PyFrameObject *f)
805810
PyThread_acquire_lock(interpreter_lock, 1);
806811
if (PyThreadState_Swap(tstate) != NULL)
807812
Py_FatalError("ceval: orphan tstate");
813+
814+
/* Check for thread interrupts */
815+
816+
if (tstate->async_exc != NULL) {
817+
x = tstate->async_exc;
818+
tstate->async_exc = NULL;
819+
PyErr_SetNone(x);
820+
Py_DECREF(x);
821+
why = WHY_EXCEPTION;
822+
goto on_error;
823+
}
808824
}
809825
#endif
810826
}

Python/pystate.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ PyThreadState_New(PyInterpreterState *interp)
143143
tstate->use_tracing = 0;
144144
tstate->tick_counter = 0;
145145
tstate->gilstate_counter = 0;
146+
tstate->async_exc = NULL;
147+
tstate->thread_id = PyThread_get_thread_ident();
146148

147149
tstate->dict = NULL;
148150

@@ -179,6 +181,7 @@ PyThreadState_Clear(PyThreadState *tstate)
179181
ZAP(tstate->frame);
180182

181183
ZAP(tstate->dict);
184+
ZAP(tstate->async_exc);
182185

183186
ZAP(tstate->curexc_type);
184187
ZAP(tstate->curexc_value);
@@ -296,6 +299,32 @@ PyThreadState_GetDict(void)
296299
}
297300

298301

302+
/* Asynchronously raise an exception in a thread.
303+
Requested by Just van Rossum and Alex Martelli.
304+
To prevent naive misuse, you must write your own exception
305+
to call this. Must be called with the GIL held.
306+
Returns the number of tstates modified; if it returns a number
307+
greater than one, you're in trouble, and you should call it again
308+
with exc=NULL to revert the effect. This raises no exceptions. */
309+
310+
int
311+
PyThreadState_SetAsyncExc(long id, PyObject *exc) {
312+
PyThreadState *tstate = PyThreadState_Get();
313+
PyInterpreterState *interp = tstate->interp;
314+
PyThreadState *p;
315+
int count = 0;
316+
for (p = interp->tstate_head; p != NULL; p = p->next) {
317+
if (p->thread_id != id)
318+
continue;
319+
ZAP(p->async_exc);
320+
Py_XINCREF(exc);
321+
p->async_exc = exc;
322+
count += 1;
323+
}
324+
return count;
325+
}
326+
327+
299328
/* Routines for advanced debuggers, requested by David Beazley.
300329
Don't use unless you know what you are doing! */
301330

@@ -320,6 +349,7 @@ PyThreadState_Next(PyThreadState *tstate) {
320349
return tstate->next;
321350
}
322351

352+
323353
/* Python "auto thread state" API. */
324354
#ifdef WITH_THREAD
325355

0 commit comments

Comments
 (0)