@@ -406,9 +406,6 @@ future_ensure_alive(FutureObj *fut)
406406static int
407407future_schedule_callbacks (asyncio_state * state , FutureObj * fut )
408408{
409- Py_ssize_t len ;
410- Py_ssize_t i ;
411-
412409 if (fut -> fut_callback0 != NULL ) {
413410 /* There's a 1st callback */
414411
@@ -434,27 +431,25 @@ future_schedule_callbacks(asyncio_state *state, FutureObj *fut)
434431 return 0 ;
435432 }
436433
437- len = PyList_GET_SIZE (fut -> fut_callbacks );
438- if (len == 0 ) {
439- /* The list of callbacks was empty; clear it and return. */
440- Py_CLEAR (fut -> fut_callbacks );
441- return 0 ;
442- }
443-
444- for (i = 0 ; i < len ; i ++ ) {
445- PyObject * cb_tup = PyList_GET_ITEM (fut -> fut_callbacks , i );
434+ // Beware: An evil call_soon could change fut->fut_callbacks.
435+ // The idea is to transfer the ownership of the callbacks list
436+ // so that external code is not able to mutate the list during
437+ // the iteration.
438+ PyObject * callbacks = fut -> fut_callbacks ;
439+ fut -> fut_callbacks = NULL ;
440+ Py_ssize_t n = PyList_GET_SIZE (callbacks );
441+ for (Py_ssize_t i = 0 ; i < n ; i ++ ) {
442+ assert (PyList_GET_SIZE (callbacks ) == n );
443+ PyObject * cb_tup = PyList_GET_ITEM (callbacks , i );
446444 PyObject * cb = PyTuple_GET_ITEM (cb_tup , 0 );
447445 PyObject * ctx = PyTuple_GET_ITEM (cb_tup , 1 );
448446
449447 if (call_soon (state , fut -> fut_loop , cb , (PyObject * )fut , ctx )) {
450- /* If an error occurs in pure-Python implementation,
451- all callbacks are cleared. */
452- Py_CLEAR (fut -> fut_callbacks );
448+ Py_DECREF (callbacks );
453449 return -1 ;
454450 }
455451 }
456-
457- Py_CLEAR (fut -> fut_callbacks );
452+ Py_DECREF (callbacks );
458453 return 0 ;
459454}
460455
0 commit comments