@@ -2063,6 +2063,8 @@ static int task_call_step_soon(asyncio_state *state, TaskObj *, PyObject *);
2063
2063
static PyObject * task_wakeup (TaskObj * , PyObject * );
2064
2064
static PyObject * task_step (asyncio_state * , TaskObj * , PyObject * );
2065
2065
static 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 );
2066
2068
2067
2069
/* ----- Task._step wrapper */
2068
2070
@@ -2236,47 +2238,7 @@ enter_task(asyncio_state *state, PyObject *loop, PyObject *task)
2236
2238
2237
2239
assert (task == item );
2238
2240
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 );
2280
2242
return 0 ;
2281
2243
}
2282
2244
@@ -2308,14 +2270,7 @@ leave_task(asyncio_state *state, PyObject *loop, PyObject *task)
2308
2270
// task was not found
2309
2271
return err_leave_task (Py_None , task );
2310
2272
}
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 );
2319
2274
return res ;
2320
2275
}
2321
2276
@@ -2342,6 +2297,7 @@ swap_current_task(asyncio_state *state, PyObject *loop, PyObject *task)
2342
2297
{
2343
2298
PyObject * prev_task ;
2344
2299
2300
+ clear_ts_asyncio_running_task (loop );
2345
2301
if (task == Py_None ) {
2346
2302
if (PyDict_Pop (state -> current_tasks , loop , & prev_task ) < 0 ) {
2347
2303
return NULL ;
@@ -2361,9 +2317,63 @@ swap_current_task(asyncio_state *state, PyObject *loop, PyObject *task)
2361
2317
Py_BEGIN_CRITICAL_SECTION (current_tasks );
2362
2318
prev_task = swap_current_task_lock_held (current_tasks , loop , hash , task );
2363
2319
Py_END_CRITICAL_SECTION ();
2320
+ set_ts_asyncio_running_task (loop , task );
2364
2321
return prev_task ;
2365
2322
}
2366
2323
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
+
2367
2377
/* ----- Task */
2368
2378
2369
2379
/*[clinic input]
0 commit comments