mirror of
				https://github.com/python/cpython.git
				synced 2025-11-01 06:01:29 +00:00 
			
		
		
		
	bpo-30703: Improve signal delivery (#2415)
* Improve signal delivery Avoid using Py_AddPendingCall from signal handler, to avoid calling signal-unsafe functions. * Remove unused function * Improve comments * Add stress test * Adapt for --without-threads * Add second stress test * Add NEWS blurb * Address comments @haypo
This commit is contained in:
		
							parent
							
								
									9f3bdcb643
								
							
						
					
					
						commit
						c08177a1cc
					
				
					 5 changed files with 171 additions and 37 deletions
				
			
		|  | @ -140,6 +140,15 @@ static long dxp[256]; | |||
|     do { pending_async_exc = 0; COMPUTE_EVAL_BREAKER(); } while (0) | ||||
| 
 | ||||
| 
 | ||||
| /* This single variable consolidates all requests to break out of the fast path
 | ||||
|    in the eval loop. */ | ||||
| static _Py_atomic_int eval_breaker = {0}; | ||||
| /* Request for running pending calls. */ | ||||
| static _Py_atomic_int pendingcalls_to_do = {0}; | ||||
| /* Request for looking at the `async_exc` field of the current thread state.
 | ||||
|    Guarded by the GIL. */ | ||||
| static int pending_async_exc = 0; | ||||
| 
 | ||||
| #ifdef WITH_THREAD | ||||
| 
 | ||||
| #ifdef HAVE_ERRNO_H | ||||
|  | @ -149,16 +158,8 @@ static long dxp[256]; | |||
| 
 | ||||
| static PyThread_type_lock pending_lock = 0; /* for pending calls */ | ||||
| static unsigned long main_thread = 0; | ||||
| /* This single variable consolidates all requests to break out of the fast path
 | ||||
|    in the eval loop. */ | ||||
| static _Py_atomic_int eval_breaker = {0}; | ||||
| /* Request for dropping the GIL */ | ||||
| static _Py_atomic_int gil_drop_request = {0}; | ||||
| /* Request for running pending calls. */ | ||||
| static _Py_atomic_int pendingcalls_to_do = {0}; | ||||
| /* Request for looking at the `async_exc` field of the current thread state.
 | ||||
|    Guarded by the GIL. */ | ||||
| static int pending_async_exc = 0; | ||||
| 
 | ||||
| #include "ceval_gil.h" | ||||
| 
 | ||||
|  | @ -253,9 +254,6 @@ PyEval_ReInitThreads(void) | |||
|     _PyThreadState_DeleteExcept(current_tstate); | ||||
| } | ||||
| 
 | ||||
| #else | ||||
| static _Py_atomic_int eval_breaker = {0}; | ||||
| static int pending_async_exc = 0; | ||||
| #endif /* WITH_THREAD */ | ||||
| 
 | ||||
| /* This function is used to signal that async exceptions are waiting to be
 | ||||
|  | @ -330,6 +328,15 @@ PyEval_RestoreThread(PyThreadState *tstate) | |||
| #endif | ||||
| */ | ||||
| 
 | ||||
| void | ||||
| _PyEval_SignalReceived(void) | ||||
| { | ||||
|     /* bpo-30703: Function called when the C signal handler of Python gets a
 | ||||
|        signal. We cannot queue a callback using Py_AddPendingCall() since | ||||
|        that function is not async-signal-safe. */ | ||||
|     SIGNAL_PENDING_CALLS(); | ||||
| } | ||||
| 
 | ||||
| #ifdef WITH_THREAD | ||||
| 
 | ||||
| /* The WITH_THREAD implementation is thread-safe.  It allows
 | ||||
|  | @ -394,6 +401,8 @@ Py_MakePendingCalls(void) | |||
|     int i; | ||||
|     int r = 0; | ||||
| 
 | ||||
|     assert(PyGILState_Check()); | ||||
| 
 | ||||
|     if (!pending_lock) { | ||||
|         /* initial allocation of the lock */ | ||||
|         pending_lock = PyThread_allocate_lock(); | ||||
|  | @ -408,6 +417,16 @@ Py_MakePendingCalls(void) | |||
|     if (busy) | ||||
|         return 0; | ||||
|     busy = 1; | ||||
|     /* unsignal before starting to call callbacks, so that any callback
 | ||||
|        added in-between re-signals */ | ||||
|     UNSIGNAL_PENDING_CALLS(); | ||||
| 
 | ||||
|     /* Python signal handler doesn't really queue a callback: it only signals
 | ||||
|        that a signal was received, see _PyEval_SignalReceived(). */ | ||||
|     if (PyErr_CheckSignals() < 0) { | ||||
|         goto error; | ||||
|     } | ||||
| 
 | ||||
|     /* perform a bounded number of calls, in case of recursion */ | ||||
|     for (i=0; i<NPENDINGCALLS; i++) { | ||||
|         int j; | ||||
|  | @ -424,20 +443,23 @@ Py_MakePendingCalls(void) | |||
|             arg = pendingcalls[j].arg; | ||||
|             pendingfirst = (j + 1) % NPENDINGCALLS; | ||||
|         } | ||||
|         if (pendingfirst != pendinglast) | ||||
|             SIGNAL_PENDING_CALLS(); | ||||
|         else | ||||
|             UNSIGNAL_PENDING_CALLS(); | ||||
|         PyThread_release_lock(pending_lock); | ||||
|         /* having released the lock, perform the callback */ | ||||
|         if (func == NULL) | ||||
|             break; | ||||
|         r = func(arg); | ||||
|         if (r) | ||||
|             break; | ||||
|         if (r) { | ||||
|             goto error; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     busy = 0; | ||||
|     return r; | ||||
| 
 | ||||
| error: | ||||
|     busy = 0; | ||||
|     SIGNAL_PENDING_CALLS(); /* We're not done yet */ | ||||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| #else /* if ! defined WITH_THREAD */ | ||||
|  | @ -472,7 +494,6 @@ static struct { | |||
| } pendingcalls[NPENDINGCALLS]; | ||||
| static volatile int pendingfirst = 0; | ||||
| static volatile int pendinglast = 0; | ||||
| static _Py_atomic_int pendingcalls_to_do = {0}; | ||||
| 
 | ||||
| int | ||||
| Py_AddPendingCall(int (*func)(void *), void *arg) | ||||
|  | @ -506,7 +527,16 @@ Py_MakePendingCalls(void) | |||
|     if (busy) | ||||
|         return 0; | ||||
|     busy = 1; | ||||
| 
 | ||||
|     /* unsignal before starting to call callbacks, so that any callback
 | ||||
|        added in-between re-signals */ | ||||
|     UNSIGNAL_PENDING_CALLS(); | ||||
|     /* Python signal handler doesn't really queue a callback: it only signals
 | ||||
|        that a signal was received, see _PyEval_SignalReceived(). */ | ||||
|     if (PyErr_CheckSignals() < 0) { | ||||
|         goto error; | ||||
|     } | ||||
| 
 | ||||
|     for (;;) { | ||||
|         int i; | ||||
|         int (*func)(void *); | ||||
|  | @ -518,13 +548,16 @@ Py_MakePendingCalls(void) | |||
|         arg = pendingcalls[i].arg; | ||||
|         pendingfirst = (i + 1) % NPENDINGCALLS; | ||||
|         if (func(arg) < 0) { | ||||
|             busy = 0; | ||||
|             SIGNAL_PENDING_CALLS(); /* We're not done yet */ | ||||
|             return -1; | ||||
|             goto error: | ||||
|         } | ||||
|     } | ||||
|     busy = 0; | ||||
|     return 0; | ||||
| 
 | ||||
| error: | ||||
|     busy = 0; | ||||
|     SIGNAL_PENDING_CALLS(); /* We're not done yet */ | ||||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| #endif /* WITH_THREAD */ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Antoine Pitrou
						Antoine Pitrou