@@ -149,6 +149,7 @@ typedef struct {
149149 PyTypeObject * TaskType ;
150150
151151 PyObject * asyncio_mod ;
152+ PyObject * call_soon_private_name ;
152153 PyObject * context_kwname ;
153154
154155 /* WeakSet containing scheduled 3rd party tasks which don't
@@ -355,31 +356,149 @@ get_event_loop(asyncio_state *state)
355356}
356357
357358
359+ static int
360+ is_asyncio_base_event_loop_type (PyTypeObject * loop_type )
361+ {
362+ if (strcmp (loop_type -> tp_name , "BaseEventLoop" ) != 0 ) {
363+ return 0 ;
364+ }
365+
366+ PyObject * module_name =
367+ PyDict_GetItemWithError (loop_type -> tp_dict , & _Py_ID (__module__ ));
368+ if (module_name == NULL ) {
369+ if (PyErr_Occurred ()) {
370+ return -1 ;
371+ }
372+ return 0 ;
373+ }
374+
375+ return (
376+ PyUnicode_Check (module_name ) &&
377+ PyUnicode_CompareWithASCIIString (module_name , "asyncio.base_events" ) == 0
378+ );
379+ }
380+
381+ static int
382+ is_asyncio_base_event_loop_subclass (PyTypeObject * loop_type )
383+ {
384+ // The _asyncio module is imported from asyncio.events while
385+ // asyncio.base_events is still initializing, so we can't cache the
386+ // BaseEventLoop type during module init without creating an import cycle.
387+ for (PyTypeObject * base = loop_type ; base != NULL ; base = base -> tp_base ) {
388+ int is_base_event_loop = is_asyncio_base_event_loop_type (base );
389+ if (is_base_event_loop < 0 ) {
390+ return -1 ;
391+ }
392+ if (is_base_event_loop ) {
393+ return 1 ;
394+ }
395+ }
396+
397+ return 0 ;
398+ }
399+
400+ static int
401+ loop_has_instance_call_soon_shadow (PyObject * loop )
402+ {
403+ // Respect instance-level monkeypatching such as:
404+ // loop.call_soon = custom_call_soon
405+ // These must keep using the public slow path.
406+ PyObject * attr = NULL ;
407+ PyObject * dict = PyObject_GenericGetDict (loop , NULL );
408+ if (dict == NULL ) {
409+ if (PyErr_ExceptionMatches (PyExc_AttributeError )) {
410+ PyErr_Clear ();
411+ return 0 ;
412+ }
413+ return -1 ;
414+ }
415+
416+ int rc = PyDict_GetItemRef (dict , & _Py_ID (call_soon ), & attr );
417+ Py_DECREF (dict );
418+ if (rc < 0 ) {
419+ return -1 ;
420+ }
421+ Py_XDECREF (attr );
422+ return rc > 0 ;
423+ }
424+
358425static int
359426call_soon (asyncio_state * state , PyObject * loop , PyObject * func , PyObject * arg ,
360427 PyObject * ctx )
361428{
362- PyObject * handle ;
429+ PyObject * handle = NULL ;
363430
364- if (ctx == NULL ) {
365- PyObject * stack [] = {loop , func , arg };
366- size_t nargsf = 3 | PY_VECTORCALL_ARGUMENTS_OFFSET ;
367- handle = PyObject_VectorcallMethod (& _Py_ID (call_soon ), stack , nargsf , NULL );
431+ PyTypeObject * loop_type = Py_TYPE (loop );
432+ int is_base_event_loop_subclass =
433+ is_asyncio_base_event_loop_subclass (loop_type );
434+ int is_stdlib_base_event_loop =
435+ is_asyncio_base_event_loop_type (loop_type );
436+ if (is_base_event_loop_subclass < 0 || is_stdlib_base_event_loop < 0 ) {
437+ return -1 ;
368438 }
369- else {
370- /* All refs in 'stack' are borrowed. */
371- PyObject * stack [4 ];
372- size_t nargs = 2 ;
373- stack [0 ] = loop ;
374- stack [1 ] = func ;
439+ PyObject * call_soon_attr =
440+ PyDict_GetItemWithError (loop_type -> tp_dict , & _Py_ID (call_soon ));
441+ if (call_soon_attr == NULL && PyErr_Occurred ()) {
442+ return -1 ;
443+ }
444+ int has_instance_call_soon_shadow = loop_has_instance_call_soon_shadow (loop );
445+ if (has_instance_call_soon_shadow < 0 ) {
446+ return -1 ;
447+ }
448+
449+ int use_fastpath = (
450+ is_base_event_loop_subclass &&
451+ // Only use the private fast path when the subclass keeps the public
452+ // call_soon() contract unchanged at both the type and instance level.
453+ !has_instance_call_soon_shadow &&
454+ (is_stdlib_base_event_loop || call_soon_attr == NULL )
455+ );
456+
457+ if (use_fastpath ) {
458+ PyObject * args ;
459+ if (arg == NULL ) {
460+ args = Py_GetConstantBorrowed (Py_CONSTANT_EMPTY_TUPLE );
461+ }
462+ else {
463+ args = PyTuple_Pack (1 , arg );
464+ if (args == NULL ) {
465+ return -1 ;
466+ }
467+ }
468+
469+ PyObject * stack [] = {loop , func , args , ctx ? ctx : Py_None };
470+ size_t nargsf = 4 | PY_VECTORCALL_ARGUMENTS_OFFSET ;
471+ handle = PyObject_VectorcallMethod (state -> call_soon_private_name , stack ,
472+ nargsf , NULL );
375473 if (arg != NULL ) {
376- stack [2 ] = arg ;
377- nargs ++ ;
474+ Py_DECREF (args );
475+ }
476+
477+ if (handle == NULL && PyErr_ExceptionMatches (PyExc_AttributeError )) {
478+ PyErr_Clear ();
479+ }
480+ }
481+
482+ if (handle == NULL ) {
483+ if (ctx == NULL ) {
484+ PyObject * stack [] = {loop , func , arg };
485+ size_t nargsf = 3 | PY_VECTORCALL_ARGUMENTS_OFFSET ;
486+ handle = PyObject_VectorcallMethod (& _Py_ID (call_soon ), stack , nargsf , NULL );
487+ }
488+ else {
489+ PyObject * stack [4 ];
490+ size_t nargs = 2 ;
491+ stack [0 ] = loop ;
492+ stack [1 ] = func ;
493+ if (arg != NULL ) {
494+ stack [2 ] = arg ;
495+ nargs ++ ;
496+ }
497+ stack [nargs ] = (PyObject * )ctx ;
498+ size_t nargsf = nargs | PY_VECTORCALL_ARGUMENTS_OFFSET ;
499+ handle = PyObject_VectorcallMethod (& _Py_ID (call_soon ), stack , nargsf ,
500+ state -> context_kwname );
378501 }
379- stack [nargs ] = (PyObject * )ctx ;
380- size_t nargsf = nargs | PY_VECTORCALL_ARGUMENTS_OFFSET ;
381- handle = PyObject_VectorcallMethod (& _Py_ID (call_soon ), stack , nargsf ,
382- state -> context_kwname );
383502 }
384503
385504 if (handle == NULL ) {
@@ -4183,6 +4302,7 @@ module_traverse(PyObject *mod, visitproc visit, void *arg)
41834302 Py_VISIT (state -> TaskType );
41844303
41854304 Py_VISIT (state -> asyncio_mod );
4305+ Py_VISIT (state -> call_soon_private_name );
41864306 Py_VISIT (state -> traceback_extract_stack );
41874307 Py_VISIT (state -> asyncio_future_repr_func );
41884308 Py_VISIT (state -> asyncio_get_event_loop );
@@ -4213,6 +4333,7 @@ module_clear(PyObject *mod)
42134333 Py_CLEAR (state -> TaskType );
42144334
42154335 Py_CLEAR (state -> asyncio_mod );
4336+ Py_CLEAR (state -> call_soon_private_name );
42164337 Py_CLEAR (state -> traceback_extract_stack );
42174338 Py_CLEAR (state -> asyncio_future_repr_func );
42184339 Py_CLEAR (state -> asyncio_get_event_loop );
@@ -4264,6 +4385,11 @@ module_init(asyncio_state *state)
42644385 goto fail ;
42654386 }
42664387
4388+ state -> call_soon_private_name = PyUnicode_InternFromString ("_call_soon" );
4389+ if (state -> call_soon_private_name == NULL ) {
4390+ goto fail ;
4391+ }
4392+
42674393#define WITH_MOD (NAME ) \
42684394 Py_CLEAR(module); \
42694395 module = PyImport_ImportModule(NAME); \
0 commit comments