Skip to content

Commit 4f447cd

Browse files
Optionally lock a pending call to a specific thread.
1 parent 117a9cf commit 4f447cd

File tree

3 files changed

+18
-6
lines changed

3 files changed

+18
-6
lines changed

Include/internal/pycore_ceval.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ extern "C" {
1313

1414
struct _is; // See PyInterpreterState in cpython/pystate.h.
1515

16-
PyAPI_FUNC(int) _Py_AddPendingCall(struct _is*, int (*)(void *), void *);
16+
PyAPI_FUNC(int) _Py_AddPendingCall(struct _is*, unsigned long, int (*)(void *), void *);
1717
PyAPI_FUNC(int) _Py_MakePendingCalls(struct _is*);
1818

1919
struct _pending_calls {
@@ -26,6 +26,7 @@ struct _pending_calls {
2626
int async_exc;
2727
#define NPENDINGCALLS 32
2828
struct {
29+
unsigned long thread_id;
2930
int (*func)(void *);
3031
void *arg;
3132
} calls[NPENDINGCALLS];

Modules/signalmodule.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ trip_signal(int sig_num)
289289
/* Py_AddPendingCall() isn't signal-safe, but we
290290
still use it for this exceptional case. */
291291
_Py_AddPendingCall(_PyRuntime.interpreters.main,
292+
main_thread,
292293
report_wakeup_send_error,
293294
(void *)(intptr_t) last_error);
294295
}
@@ -308,6 +309,7 @@ trip_signal(int sig_num)
308309
/* Py_AddPendingCall() isn't signal-safe, but we
309310
still use it for this exceptional case. */
310311
_Py_AddPendingCall(_PyRuntime.interpreters.main,
312+
main_thread,
311313
report_wakeup_write_error,
312314
(void *)(intptr_t)errno);
313315
}

Python/ceval.c

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,23 +322,24 @@ _PyEval_SignalReceived(void)
322322
}
323323

324324
static int
325-
_add_pending_call(PyInterpreterState *interp, int (*func)(void *), void *arg)
325+
_add_pending_call(PyInterpreterState *interp, unsigned long thread_id, int (*func)(void *), void *arg)
326326
{
327327
int i = interp->ceval.pending.last;
328328
int j = (i + 1) % NPENDINGCALLS;
329329
if (j == interp->ceval.pending.first) {
330330
return -1; /* Queue full */
331331
}
332+
interp->ceval.pending.calls[i].thread_id = thread_id;
332333
interp->ceval.pending.calls[i].func = func;
333334
interp->ceval.pending.calls[i].arg = arg;
334335
interp->ceval.pending.last = j;
335336
return 0;
336337
}
337338

339+
/* pop one item off the queue while holding the lock */
338340
static void
339341
_pop_pending_call(PyInterpreterState *interp, int (**func)(void *), void **arg)
340342
{
341-
/* pop one item off the queue while holding the lock */
342343
int i = interp->ceval.pending.first;
343344
if (i == interp->ceval.pending.last) {
344345
return; /* Queue empty */
@@ -347,13 +348,21 @@ _pop_pending_call(PyInterpreterState *interp, int (**func)(void *), void **arg)
347348
*func = interp->ceval.pending.calls[i].func;
348349
*arg = interp->ceval.pending.calls[i].arg;
349350
interp->ceval.pending.first = (i + 1) % NPENDINGCALLS;
351+
352+
unsigned long thread_id = interp->ceval.pending.calls[i].thread_id;
353+
if (thread_id && PyThread_get_thread_ident() != thread_id) {
354+
// Thread mismatch, so move it to the end of the list
355+
// and start over.
356+
_Py_AddPendingCall(interp, thread_id, *func, *arg);
357+
return;
358+
}
350359
}
351360

352361
int
353362
Py_AddPendingCall(int (*func)(void *), void *arg)
354363
{
355364
PyInterpreterState *interp = _PyRuntime.interpreters.main;
356-
return _Py_AddPendingCall(interp, func, arg);
365+
return _Py_AddPendingCall(interp, _PyRuntime.main_thread, func, arg);
357366
}
358367

359368
/* This implementation is thread-safe. It allows
@@ -362,7 +371,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
362371
*/
363372

364373
int
365-
_Py_AddPendingCall(PyInterpreterState *interp, int (*func)(void *), void *arg)
374+
_Py_AddPendingCall(PyInterpreterState *interp, unsigned long thread_id, int (*func)(void *), void *arg)
366375
{
367376
/* try a few times for the lock. Since this mechanism is used
368377
* for signal handling (on the main thread), there is a (slim)
@@ -396,7 +405,7 @@ _Py_AddPendingCall(PyInterpreterState *interp, int (*func)(void *), void *arg)
396405
goto done;
397406
}
398407

399-
result = _add_pending_call(interp, func, arg);
408+
result = _add_pending_call(interp, thread_id, func, arg);
400409
/* signal main loop */
401410
SIGNAL_PENDING_CALLS(interp);
402411

0 commit comments

Comments
 (0)