Skip to content

Commit fc3d400

Browse files
ambvkumaraditya303
andauthored
gh-91048: Also clear and set ts->asyncio_running_task with eager tasks (#129197)
This was missing from gh-124640. It's already covered by the new test_asyncio/test_free_threading.py in combination with the runtime assertion in set_ts_asyncio_running_task. Co-authored-by: Kumar Aditya <kumaraditya@python.org>
1 parent dbb25ce commit fc3d400

File tree

1 file changed

+59
-49
lines changed

1 file changed

+59
-49
lines changed

Modules/_asynciomodule.c

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,6 +2063,8 @@ static int task_call_step_soon(asyncio_state *state, TaskObj *, PyObject *);
20632063
static PyObject * task_wakeup(TaskObj *, PyObject *);
20642064
static PyObject * task_step(asyncio_state *, TaskObj *, PyObject *);
20652065
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);
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

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy