@@ -2063,6 +2063,8 @@ static int task_call_step_soon(asyncio_state *state, TaskObj *, PyObject *);
20632063static PyObject * task_wakeup (TaskObj * , PyObject * );
20642064static PyObject * task_step (asyncio_state * , TaskObj * , PyObject * );
20652065static int task_eager_start (asyncio_state * state , TaskObj * task );
2066+ static inline void clear_ts_asyncio_running_task (PyObject * loop );
2067+ static inline void set_ts_asyncio_running_task (PyObject * loop , PyObject * task );
20662068
20672069/* ----- Task._step wrapper */
20682070
@@ -2236,47 +2238,7 @@ enter_task(asyncio_state *state, PyObject *loop, PyObject *task)
22362238
22372239 assert (task == item );
22382240 Py_CLEAR (item );
2239-
2240- // This block is needed to enable `asyncio.capture_call_graph()` API.
2241- // We want to be enable debuggers and profilers to be able to quickly
2242- // introspect the asyncio running state from another process.
2243- // When we do that, we need to essentially traverse the address space
2244- // of a Python process and understand what every Python thread in it is
2245- // currently doing, mainly:
2246- //
2247- // * current frame
2248- // * current asyncio task
2249- //
2250- // A naive solution would be to require profilers and debuggers to
2251- // find the current task in the "_asynciomodule" module state, but
2252- // unfortunately that would require a lot of complicated remote
2253- // memory reads and logic, as Python's dict is a notoriously complex
2254- // and ever-changing data structure.
2255- //
2256- // So the easier solution is to put a strong reference to the currently
2257- // running `asyncio.Task` on the interpreter thread state (we already
2258- // have some asyncio state there.)
2259- _PyThreadStateImpl * ts = (_PyThreadStateImpl * )_PyThreadState_GET ();
2260- if (ts -> asyncio_running_loop == loop ) {
2261- // Protect from a situation when someone calls this method
2262- // from another thread. This shouldn't ever happen though,
2263- // as `enter_task` and `leave_task` can either be called by:
2264- //
2265- // - `asyncio.Task` itself, in `Task.__step()`. That method
2266- // can only be called by the event loop itself.
2267- //
2268- // - third-party Task "from scratch" implementations, that
2269- // our `capture_call_graph` API doesn't support anyway.
2270- //
2271- // That said, we still want to make sure we don't end up in
2272- // a broken state, so we check that we're in the correct thread
2273- // by comparing the *loop* argument to the event loop running
2274- // in the current thread. If they match we know we're in the
2275- // right thread, as asyncio event loops don't change threads.
2276- assert (ts -> asyncio_running_task == NULL );
2277- ts -> asyncio_running_task = Py_NewRef (task );
2278- }
2279-
2241+ set_ts_asyncio_running_task (loop , task );
22802242 return 0 ;
22812243}
22822244
@@ -2308,14 +2270,7 @@ leave_task(asyncio_state *state, PyObject *loop, PyObject *task)
23082270 // task was not found
23092271 return err_leave_task (Py_None , task );
23102272 }
2311-
2312- // See the comment in `enter_task` for the explanation of why
2313- // the following is needed.
2314- _PyThreadStateImpl * ts = (_PyThreadStateImpl * )_PyThreadState_GET ();
2315- if (ts -> asyncio_running_loop == NULL || ts -> asyncio_running_loop == loop ) {
2316- Py_CLEAR (ts -> asyncio_running_task );
2317- }
2318-
2273+ clear_ts_asyncio_running_task (loop );
23192274 return res ;
23202275}
23212276
@@ -2342,6 +2297,7 @@ swap_current_task(asyncio_state *state, PyObject *loop, PyObject *task)
23422297{
23432298 PyObject * prev_task ;
23442299
2300+ clear_ts_asyncio_running_task (loop );
23452301 if (task == Py_None ) {
23462302 if (PyDict_Pop (state -> current_tasks , loop , & prev_task ) < 0 ) {
23472303 return NULL ;
@@ -2361,9 +2317,63 @@ swap_current_task(asyncio_state *state, PyObject *loop, PyObject *task)
23612317 Py_BEGIN_CRITICAL_SECTION (current_tasks );
23622318 prev_task = swap_current_task_lock_held (current_tasks , loop , hash , task );
23632319 Py_END_CRITICAL_SECTION ();
2320+ set_ts_asyncio_running_task (loop , task );
23642321 return prev_task ;
23652322}
23662323
2324+ static inline void
2325+ set_ts_asyncio_running_task (PyObject * loop , PyObject * task )
2326+ {
2327+ // We want to enable debuggers and profilers to be able to quickly
2328+ // introspect the asyncio running state from another process.
2329+ // When we do that, we need to essentially traverse the address space
2330+ // of a Python process and understand what every Python thread in it is
2331+ // currently doing, mainly:
2332+ //
2333+ // * current frame
2334+ // * current asyncio task
2335+ //
2336+ // A naive solution would be to require profilers and debuggers to
2337+ // find the current task in the "_asynciomodule" module state, but
2338+ // unfortunately that would require a lot of complicated remote
2339+ // memory reads and logic, as Python's dict is a notoriously complex
2340+ // and ever-changing data structure.
2341+ //
2342+ // So the easier solution is to put a strong reference to the currently
2343+ // running `asyncio.Task` on the current thread state (the current loop
2344+ // is also stored there.)
2345+ _PyThreadStateImpl * ts = (_PyThreadStateImpl * )_PyThreadState_GET ();
2346+ if (ts -> asyncio_running_loop == loop ) {
2347+ // Protect from a situation when someone calls this method
2348+ // from another thread. This shouldn't ever happen though,
2349+ // as `enter_task` and `leave_task` can either be called by:
2350+ //
2351+ // - `asyncio.Task` itself, in `Task.__step()`. That method
2352+ // can only be called by the event loop itself.
2353+ //
2354+ // - third-party Task "from scratch" implementations, that
2355+ // our `capture_call_graph` API doesn't support anyway.
2356+ //
2357+ // That said, we still want to make sure we don't end up in
2358+ // a broken state, so we check that we're in the correct thread
2359+ // by comparing the *loop* argument to the event loop running
2360+ // in the current thread. If they match we know we're in the
2361+ // right thread, as asyncio event loops don't change threads.
2362+ assert (ts -> asyncio_running_task == NULL );
2363+ ts -> asyncio_running_task = Py_NewRef (task );
2364+ }
2365+ }
2366+
2367+ static inline void
2368+ clear_ts_asyncio_running_task (PyObject * loop )
2369+ {
2370+ // See comment in set_ts_asyncio_running_task() for details.
2371+ _PyThreadStateImpl * ts = (_PyThreadStateImpl * )_PyThreadState_GET ();
2372+ if (ts -> asyncio_running_loop == NULL || ts -> asyncio_running_loop == loop ) {
2373+ Py_CLEAR (ts -> asyncio_running_task );
2374+ }
2375+ }
2376+
23672377/* ----- Task */
23682378
23692379/*[clinic input]
0 commit comments