@@ -205,6 +205,7 @@ PyEval_GetCallStats(PyObject *self)
205205#include "pythread.h"
206206
207207static PyThread_type_lock interpreter_lock = 0 ; /* This is the GIL */
208+ static PyThread_type_lock pending_lock = 0 ; /* for pending calls */
208209static long main_thread = 0 ;
209210
210211int
@@ -276,6 +277,7 @@ PyEval_ReInitThreads(void)
276277 adding a new function to each thread_*.h. Instead, just
277278 create a new lock and waste a little bit of memory */
278279 interpreter_lock = PyThread_allocate_lock ();
280+ pending_lock = PyThread_allocate_lock ();
279281 PyThread_acquire_lock (interpreter_lock , 1 );
280282 main_thread = PyThread_get_thread_ident ();
281283
@@ -348,19 +350,145 @@ PyEval_RestoreThread(PyThreadState *tstate)
348350#ifdef WITH_THREAD
349351 Any thread can schedule pending calls, but only the main thread
350352 will execute them.
353+ There is no facility to schedule calls to a particular thread, but
354+ that should be easy to change, should that ever be required. In
355+ that case, the static variables here should go into the python
356+ threadstate.
351357#endif
358+ */
359+
360+ #ifdef WITH_THREAD
361+
362+ /* The WITH_THREAD implementation is thread-safe. It allows
363+ scheduling to be made from any thread, and even from an executing
364+ callback.
365+ */
366+
367+ #define NPENDINGCALLS 32
368+ static struct {
369+ int (* func )(void * );
370+ void * arg ;
371+ } pendingcalls [NPENDINGCALLS ];
372+ static int pendingfirst = 0 ;
373+ static int pendinglast = 0 ;
374+ static volatile int pendingcalls_to_do = 1 ; /* trigger initialization of lock */
375+ static char pendingbusy = 0 ;
376+
377+ int
378+ Py_AddPendingCall (int (* func )(void * ), void * arg )
379+ {
380+ int i , j , result = 0 ;
381+ PyThread_type_lock lock = pending_lock ;
382+
383+ /* try a few times for the lock. Since this mechanism is used
384+ * for signal handling (on the main thread), there is a (slim)
385+ * chance that a signal is delivered on the same thread while we
386+ * hold the lock during the Py_MakePendingCalls() function.
387+ * This avoids a deadlock in that case.
388+ * Note that signals can be delivered on any thread. In particular,
389+ * on Windows, a SIGINT is delivered on a system-created worker
390+ * thread.
391+ * We also check for lock being NULL, in the unlikely case that
392+ * this function is called before any bytecode evaluation takes place.
393+ */
394+ if (lock != NULL ) {
395+ for (i = 0 ; i < 100 ; i ++ ) {
396+ if (PyThread_acquire_lock (lock , NOWAIT_LOCK ))
397+ break ;
398+ }
399+ if (i == 100 )
400+ return -1 ;
401+ }
402+
403+ i = pendinglast ;
404+ j = (i + 1 ) % NPENDINGCALLS ;
405+ if (j == pendingfirst ) {
406+ result = -1 ; /* Queue full */
407+ } else {
408+ pendingcalls [i ].func = func ;
409+ pendingcalls [i ].arg = arg ;
410+ pendinglast = j ;
411+ }
412+ /* signal main loop */
413+ _Py_Ticker = 0 ;
414+ pendingcalls_to_do = 1 ;
415+ if (lock != NULL )
416+ PyThread_release_lock (lock );
417+ return result ;
418+ }
419+
420+ int
421+ Py_MakePendingCalls (void )
422+ {
423+ int i ;
424+ int r = 0 ;
352425
353- XXX WARNING! ASYNCHRONOUSLY EXECUTING CODE!
426+ if (!pending_lock ) {
427+ /* initial allocation of the lock */
428+ pending_lock = PyThread_allocate_lock ();
429+ if (pending_lock == NULL )
430+ return -1 ;
431+ }
432+
433+ /* only service pending calls on main thread */
434+ if (main_thread && PyThread_get_thread_ident () != main_thread )
435+ return 0 ;
436+ /* don't perform recursive pending calls */
437+ if (pendingbusy )
438+ return 0 ;
439+ pendingbusy = 1 ;
440+ /* perform a bounded number of calls, in case of recursion */
441+ for (i = 0 ; i < NPENDINGCALLS ; i ++ ) {
442+ int j ;
443+ int (* func )(void * );
444+ void * arg ;
445+
446+ /* pop one item off the queue while holding the lock */
447+ PyThread_acquire_lock (pending_lock , WAIT_LOCK );
448+ j = pendingfirst ;
449+ if (j == pendinglast ) {
450+ func = NULL ; /* Queue empty */
451+ } else {
452+ func = pendingcalls [j ].func ;
453+ arg = pendingcalls [j ].arg ;
454+ pendingfirst = (j + 1 ) % NPENDINGCALLS ;
455+ }
456+ pendingcalls_to_do = pendingfirst != pendinglast ;
457+ PyThread_release_lock (pending_lock );
458+ /* having released the lock, perform the callback */
459+ if (func == NULL )
460+ break ;
461+ r = func (arg );
462+ if (r )
463+ break ;
464+ }
465+ pendingbusy = 0 ;
466+ return r ;
467+ }
468+
469+ #else /* if ! defined WITH_THREAD */
470+
471+ /*
472+ WARNING! ASYNCHRONOUSLY EXECUTING CODE!
473+ This code is used for signal handling in python that isn't built
474+ with WITH_THREAD.
475+ Don't use this implementation when Py_AddPendingCalls() can happen
476+ on a different thread!
477+
354478 There are two possible race conditions:
355- (1) nested asynchronous registry calls;
356- (2) registry calls made while pending calls are being processed.
357- While (1) is very unlikely, (2) is a real possibility.
479+ (1) nested asynchronous calls to Py_AddPendingCall()
480+ (2) AddPendingCall() calls made while pending calls are being processed.
481+
482+ (1) is very unlikely because typically signal delivery
483+ is blocked during signal handling. So it should be impossible.
484+ (2) is a real possibility.
358485 The current code is safe against (2), but not against (1).
359486 The safety against (2) is derived from the fact that only one
360- thread (the main thread) ever takes things out of the queue.
361-
362- XXX Darn! With the advent of thread state, we should have an array
363- of pending calls per thread in the thread state! Later...
487+ thread is present, interrupted by signals, and that the critical
488+ section is protected with the "busy" variable. On Windows, which
489+ delivers SIGINT on a system thread, this does not hold and therefore
490+ Windows really shouldn't use this version.
491+ The two threads could theoretically wiggle around the "busy" variable.
364492*/
365493
366494#define NPENDINGCALLS 32
@@ -370,16 +498,14 @@ static struct {
370498} pendingcalls [NPENDINGCALLS ];
371499static volatile int pendingfirst = 0 ;
372500static volatile int pendinglast = 0 ;
373- static volatile int things_to_do = 0 ;
501+ static volatile int pendingcalls_to_do = 0 ;
374502
375503int
376504Py_AddPendingCall (int (* func )(void * ), void * arg )
377505{
378506 static volatile int busy = 0 ;
379507 int i , j ;
380508 /* XXX Begin critical section */
381- /* XXX If you want this to be safe against nested
382- XXX asynchronous calls, you'll have to work harder! */
383509 if (busy )
384510 return -1 ;
385511 busy = 1 ;
@@ -394,7 +520,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
394520 pendinglast = j ;
395521
396522 _Py_Ticker = 0 ;
397- things_to_do = 1 ; /* Signal main loop */
523+ pendingcalls_to_do = 1 ; /* Signal main loop */
398524 busy = 0 ;
399525 /* XXX End critical section */
400526 return 0 ;
@@ -404,14 +530,10 @@ int
404530Py_MakePendingCalls (void )
405531{
406532 static int busy = 0 ;
407- #ifdef WITH_THREAD
408- if (main_thread && PyThread_get_thread_ident () != main_thread )
409- return 0 ;
410- #endif
411533 if (busy )
412534 return 0 ;
413535 busy = 1 ;
414- things_to_do = 0 ;
536+ pendingcalls_to_do = 0 ;
415537 for (;;) {
416538 int i ;
417539 int (* func )(void * );
@@ -424,14 +546,16 @@ Py_MakePendingCalls(void)
424546 pendingfirst = (i + 1 ) % NPENDINGCALLS ;
425547 if (func (arg ) < 0 ) {
426548 busy = 0 ;
427- things_to_do = 1 ; /* We're not done yet */
549+ pendingcalls_to_do = 1 ; /* We're not done yet */
428550 return -1 ;
429551 }
430552 }
431553 busy = 0 ;
432554 return 0 ;
433555}
434556
557+ #endif /* WITH_THREAD */
558+
435559
436560/* The interpreter's recursion limit */
437561
@@ -518,7 +642,7 @@ static int _Py_TracingPossible = 0;
518642/* for manipulating the thread switch and periodic "stuff" - used to be
519643 per thread, now just a pair o' globals */
520644int _Py_CheckInterval = 100 ;
521- volatile int _Py_Ticker = 100 ;
645+ volatile int _Py_Ticker = 0 ; /* so that we hit a "tick" first thing */
522646
523647PyObject *
524648PyEval_EvalCode (PyCodeObject * co , PyObject * globals , PyObject * locals )
@@ -903,7 +1027,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
9031027 /* Do periodic things. Doing this every time through
9041028 the loop would add too much overhead, so we do it
9051029 only every Nth instruction. We also do it if
906- ``things_to_do '' is set, i.e. when an asynchronous
1030+ ``pendingcalls_to_do '' is set, i.e. when an asynchronous
9071031 event needs attention (e.g. a signal handler or
9081032 async I/O handler); see Py_AddPendingCall() and
9091033 Py_MakePendingCalls() above. */
@@ -919,12 +1043,12 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
9191043#ifdef WITH_TSC
9201044 ticked = 1 ;
9211045#endif
922- if (things_to_do ) {
1046+ if (pendingcalls_to_do ) {
9231047 if (Py_MakePendingCalls () < 0 ) {
9241048 why = WHY_EXCEPTION ;
9251049 goto on_error ;
9261050 }
927- if (things_to_do )
1051+ if (pendingcalls_to_do )
9281052 /* MakePendingCalls() didn't succeed.
9291053 Force early re-execution of this
9301054 "periodic" code, possibly after
0 commit comments