@@ -213,6 +213,7 @@ PyEval_GetCallStats(PyObject *self)
213213#include "pythread.h"
214214
215215static PyThread_type_lock interpreter_lock = 0 ; /* This is the GIL */
216+ static PyThread_type_lock pending_lock = 0 ; /* for pending calls */
216217static long main_thread = 0 ;
217218
218219int
@@ -284,6 +285,7 @@ PyEval_ReInitThreads(void)
284285 adding a new function to each thread_*.h. Instead, just
285286 create a new lock and waste a little bit of memory */
286287 interpreter_lock = PyThread_allocate_lock ();
288+ pending_lock = PyThread_allocate_lock ();
287289 PyThread_acquire_lock (interpreter_lock , 1 );
288290 main_thread = PyThread_get_thread_ident ();
289291
@@ -356,19 +358,145 @@ PyEval_RestoreThread(PyThreadState *tstate)
356358#ifdef WITH_THREAD
357359 Any thread can schedule pending calls, but only the main thread
358360 will execute them.
361+ There is no facility to schedule calls to a particular thread, but
362+ that should be easy to change, should that ever be required. In
363+ that case, the static variables here should go into the python
364+ threadstate.
359365#endif
366+ */
367+
368+ #ifdef WITH_THREAD
369+
370+ /* The WITH_THREAD implementation is thread-safe. It allows
371+ scheduling to be made from any thread, and even from an executing
372+ callback.
373+ */
374+
375+ #define NPENDINGCALLS 32
376+ static struct {
377+ int (* func )(void * );
378+ void * arg ;
379+ } pendingcalls [NPENDINGCALLS ];
380+ static int pendingfirst = 0 ;
381+ static int pendinglast = 0 ;
382+ static volatile int pendingcalls_to_do = 1 ; /* trigger initialization of lock */
383+ static char pendingbusy = 0 ;
384+
385+ int
386+ Py_AddPendingCall (int (* func )(void * ), void * arg )
387+ {
388+ int i , j , result = 0 ;
389+ PyThread_type_lock lock = pending_lock ;
390+
391+ /* try a few times for the lock. Since this mechanism is used
392+ * for signal handling (on the main thread), there is a (slim)
393+ * chance that a signal is delivered on the same thread while we
394+ * hold the lock during the Py_MakePendingCalls() function.
395+ * This avoids a deadlock in that case.
396+ * Note that signals can be delivered on any thread. In particular,
397+ * on Windows, a SIGINT is delivered on a system-created worker
398+ * thread.
399+ * We also check for lock being NULL, in the unlikely case that
400+ * this function is called before any bytecode evaluation takes place.
401+ */
402+ if (lock != NULL ) {
403+ for (i = 0 ; i < 100 ; i ++ ) {
404+ if (PyThread_acquire_lock (lock , NOWAIT_LOCK ))
405+ break ;
406+ }
407+ if (i == 100 )
408+ return -1 ;
409+ }
410+
411+ i = pendinglast ;
412+ j = (i + 1 ) % NPENDINGCALLS ;
413+ if (j == pendingfirst ) {
414+ result = -1 ; /* Queue full */
415+ } else {
416+ pendingcalls [i ].func = func ;
417+ pendingcalls [i ].arg = arg ;
418+ pendinglast = j ;
419+ }
420+ /* signal main loop */
421+ _Py_Ticker = 0 ;
422+ pendingcalls_to_do = 1 ;
423+ if (lock != NULL )
424+ PyThread_release_lock (lock );
425+ return result ;
426+ }
427+
428+ int
429+ Py_MakePendingCalls (void )
430+ {
431+ int i ;
432+ int r = 0 ;
360433
361- XXX WARNING! ASYNCHRONOUSLY EXECUTING CODE!
434+ if (!pending_lock ) {
435+ /* initial allocation of the lock */
436+ pending_lock = PyThread_allocate_lock ();
437+ if (pending_lock == NULL )
438+ return -1 ;
439+ }
440+
441+ /* only service pending calls on main thread */
442+ if (main_thread && PyThread_get_thread_ident () != main_thread )
443+ return 0 ;
444+ /* don't perform recursive pending calls */
445+ if (pendingbusy )
446+ return 0 ;
447+ pendingbusy = 1 ;
448+ /* perform a bounded number of calls, in case of recursion */
449+ for (i = 0 ; i < NPENDINGCALLS ; i ++ ) {
450+ int j ;
451+ int (* func )(void * );
452+ void * arg ;
453+
454+ /* pop one item off the queue while holding the lock */
455+ PyThread_acquire_lock (pending_lock , WAIT_LOCK );
456+ j = pendingfirst ;
457+ if (j == pendinglast ) {
458+ func = NULL ; /* Queue empty */
459+ } else {
460+ func = pendingcalls [j ].func ;
461+ arg = pendingcalls [j ].arg ;
462+ pendingfirst = (j + 1 ) % NPENDINGCALLS ;
463+ }
464+ pendingcalls_to_do = pendingfirst != pendinglast ;
465+ PyThread_release_lock (pending_lock );
466+ /* having released the lock, perform the callback */
467+ if (func == NULL )
468+ break ;
469+ r = func (arg );
470+ if (r )
471+ break ;
472+ }
473+ pendingbusy = 0 ;
474+ return r ;
475+ }
476+
477+ #else /* if ! defined WITH_THREAD */
478+
479+ /*
480+ WARNING! ASYNCHRONOUSLY EXECUTING CODE!
481+ This code is used for signal handling in python that isn't built
482+ with WITH_THREAD.
483+ Don't use this implementation when Py_AddPendingCalls() can happen
484+ on a different thread!
485+
362486 There are two possible race conditions:
363- (1) nested asynchronous registry calls;
364- (2) registry calls made while pending calls are being processed.
365- While (1) is very unlikely, (2) is a real possibility.
487+ (1) nested asynchronous calls to Py_AddPendingCall()
488+ (2) AddPendingCall() calls made while pending calls are being processed.
489+
490+ (1) is very unlikely because typically signal delivery
491+ is blocked during signal handling. So it should be impossible.
492+ (2) is a real possibility.
366493 The current code is safe against (2), but not against (1).
367494 The safety against (2) is derived from the fact that only one
368- thread (the main thread) ever takes things out of the queue.
369-
370- XXX Darn! With the advent of thread state, we should have an array
371- of pending calls per thread in the thread state! Later...
495+ thread is present, interrupted by signals, and that the critical
496+ section is protected with the "busy" variable. On Windows, which
497+ delivers SIGINT on a system thread, this does not hold and therefore
498+ Windows really shouldn't use this version.
499+ The two threads could theoretically wiggle around the "busy" variable.
372500*/
373501
374502#define NPENDINGCALLS 32
@@ -378,16 +506,14 @@ static struct {
378506} pendingcalls [NPENDINGCALLS ];
379507static volatile int pendingfirst = 0 ;
380508static volatile int pendinglast = 0 ;
381- static volatile int things_to_do = 0 ;
509+ static volatile int pendingcalls_to_do = 0 ;
382510
383511int
384512Py_AddPendingCall (int (* func )(void * ), void * arg )
385513{
386514 static volatile int busy = 0 ;
387515 int i , j ;
388516 /* XXX Begin critical section */
389- /* XXX If you want this to be safe against nested
390- XXX asynchronous calls, you'll have to work harder! */
391517 if (busy )
392518 return -1 ;
393519 busy = 1 ;
@@ -402,7 +528,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
402528 pendinglast = j ;
403529
404530 _Py_Ticker = 0 ;
405- things_to_do = 1 ; /* Signal main loop */
531+ pendingcalls_to_do = 1 ; /* Signal main loop */
406532 busy = 0 ;
407533 /* XXX End critical section */
408534 return 0 ;
@@ -412,14 +538,10 @@ int
412538Py_MakePendingCalls (void )
413539{
414540 static int busy = 0 ;
415- #ifdef WITH_THREAD
416- if (main_thread && PyThread_get_thread_ident () != main_thread )
417- return 0 ;
418- #endif
419541 if (busy )
420542 return 0 ;
421543 busy = 1 ;
422- things_to_do = 0 ;
544+ pendingcalls_to_do = 0 ;
423545 for (;;) {
424546 int i ;
425547 int (* func )(void * );
@@ -432,14 +554,16 @@ Py_MakePendingCalls(void)
432554 pendingfirst = (i + 1 ) % NPENDINGCALLS ;
433555 if (func (arg ) < 0 ) {
434556 busy = 0 ;
435- things_to_do = 1 ; /* We're not done yet */
557+ pendingcalls_to_do = 1 ; /* We're not done yet */
436558 return -1 ;
437559 }
438560 }
439561 busy = 0 ;
440562 return 0 ;
441563}
442564
565+ #endif /* WITH_THREAD */
566+
443567
444568/* The interpreter's recursion limit */
445569
@@ -514,7 +638,7 @@ static int _Py_TracingPossible = 0;
514638/* for manipulating the thread switch and periodic "stuff" - used to be
515639 per thread, now just a pair o' globals */
516640int _Py_CheckInterval = 100 ;
517- volatile int _Py_Ticker = 100 ;
641+ volatile int _Py_Ticker = 0 ; /* so that we hit a "tick" first thing */
518642
519643PyObject *
520644PyEval_EvalCode (PyCodeObject * co , PyObject * globals , PyObject * locals )
@@ -832,7 +956,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
832956 /* Do periodic things. Doing this every time through
833957 the loop would add too much overhead, so we do it
834958 only every Nth instruction. We also do it if
835- ``things_to_do '' is set, i.e. when an asynchronous
959+ ``pendingcalls_to_do '' is set, i.e. when an asynchronous
836960 event needs attention (e.g. a signal handler or
837961 async I/O handler); see Py_AddPendingCall() and
838962 Py_MakePendingCalls() above. */
@@ -848,12 +972,12 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
848972#ifdef WITH_TSC
849973 ticked = 1 ;
850974#endif
851- if (things_to_do ) {
975+ if (pendingcalls_to_do ) {
852976 if (Py_MakePendingCalls () < 0 ) {
853977 why = WHY_EXCEPTION ;
854978 goto on_error ;
855979 }
856- if (things_to_do )
980+ if (pendingcalls_to_do )
857981 /* MakePendingCalls() didn't succeed.
858982 Force early re-execution of this
859983 "periodic" code, possibly after
0 commit comments