From cc66eeb5575af1303f997a7955d50998a03cba6e Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Wed, 24 May 2023 06:15:37 +0000 Subject: [PATCH 01/22] linked list --- Lib/asyncio/tasks.py | 2 +- Modules/_asynciomodule.c | 212 +++++++++++++++++++----------- Modules/clinic/_asynciomodule.c.h | 62 ++++++++- main.py | 11 ++ 4 files changed, 211 insertions(+), 76 deletions(-) create mode 100644 main.py diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 8d5bde09ea9b5b..2e8fce74965826 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -1041,7 +1041,7 @@ def _unregister_eager_task(task): _unregister_task, _unregister_eager_task, _enter_task, _leave_task, _swap_current_task, _scheduled_tasks, _eager_tasks, _current_tasks, - current_task) + current_task, all_tasks) except ImportError: pass else: diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 08ce172c6a8fcb..e62a009f3ccc76 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -16,6 +16,59 @@ module _asyncio [clinic start generated code]*/ /*[clinic end generated code: output=da39a3ee5e6b4b0d input=8fd17862aa989c69]*/ +typedef enum { + STATE_PENDING, + STATE_CANCELLED, + STATE_FINISHED +} fut_state; + +#define FutureObj_HEAD(prefix) \ + PyObject_HEAD \ + PyObject *prefix##_loop; \ + PyObject *prefix##_callback0; \ + PyObject *prefix##_context0; \ + PyObject *prefix##_callbacks; \ + PyObject *prefix##_exception; \ + PyObject *prefix##_exception_tb; \ + PyObject *prefix##_result; \ + PyObject *prefix##_source_tb; \ + PyObject *prefix##_cancel_msg; \ + fut_state prefix##_state; \ + int prefix##_log_tb; \ + int prefix##_blocking; \ + PyObject *dict; \ + PyObject *prefix##_weakreflist; \ + PyObject *prefix##_cancelled_exc; + +typedef struct { + FutureObj_HEAD(fut) +} FutureObj; + +typedef struct TaskObj { + FutureObj_HEAD(task) + PyObject *task_fut_waiter; + PyObject *task_coro; + PyObject *task_name; + PyObject *task_context; + int task_must_cancel; + int task_log_destroy_pending; + int task_num_cancels_requested; + struct TaskObj *next; + struct TaskObj *prev; +} TaskObj; + +typedef struct { + PyObject_HEAD + TaskObj *sw_task; + PyObject *sw_arg; +} TaskStepMethWrapper; + + +#define Future_CheckExact(state, obj) Py_IS_TYPE(obj, state->FutureType) +#define Task_CheckExact(state, obj) Py_IS_TYPE(obj, state->TaskType) + +#define Future_Check(state, obj) PyObject_TypeCheck(obj, state->FutureType) +#define Task_Check(state, obj) PyObject_TypeCheck(obj, state->TaskType) #define FI_FREELIST_MAXLEN 255 @@ -73,6 +126,11 @@ typedef struct { futureiterobject *fi_freelist; Py_ssize_t fi_freelist_len; + + struct { + TaskObj *head; + } asyncio_tasks; + } asyncio_state; static inline asyncio_state * @@ -102,56 +160,6 @@ get_asyncio_state_by_def(PyObject *self) return get_asyncio_state(mod); } -typedef enum { - STATE_PENDING, - STATE_CANCELLED, - STATE_FINISHED -} fut_state; - -#define FutureObj_HEAD(prefix) \ - PyObject_HEAD \ - PyObject *prefix##_loop; \ - PyObject *prefix##_callback0; \ - PyObject *prefix##_context0; \ - PyObject *prefix##_callbacks; \ - PyObject *prefix##_exception; \ - PyObject *prefix##_exception_tb; \ - PyObject *prefix##_result; \ - PyObject *prefix##_source_tb; \ - PyObject *prefix##_cancel_msg; \ - fut_state prefix##_state; \ - int prefix##_log_tb; \ - int prefix##_blocking; \ - PyObject *prefix##_weakreflist; \ - PyObject *prefix##_cancelled_exc; - -typedef struct { - FutureObj_HEAD(fut) -} FutureObj; - -typedef struct { - FutureObj_HEAD(task) - PyObject *task_fut_waiter; - PyObject *task_coro; - PyObject *task_name; - PyObject *task_context; - int task_must_cancel; - int task_log_destroy_pending; - int task_num_cancels_requested; -} TaskObj; - -typedef struct { - PyObject_HEAD - TaskObj *sw_task; - PyObject *sw_arg; -} TaskStepMethWrapper; - - -#define Future_CheckExact(state, obj) Py_IS_TYPE(obj, state->FutureType) -#define Task_CheckExact(state, obj) Py_IS_TYPE(obj, state->TaskType) - -#define Future_Check(state, obj) PyObject_TypeCheck(obj, state->FutureType) -#define Task_Check(state, obj) PyObject_TypeCheck(obj, state->TaskType) #include "clinic/_asynciomodule.c.h" @@ -1939,16 +1947,17 @@ static PyMethodDef TaskWakeupDef = { /* ----- Task introspection helpers */ -static int -register_task(asyncio_state *state, PyObject *task) +static void +register_task(asyncio_state *state, TaskObj *task) { - PyObject *res = PyObject_CallMethodOneArg(state->scheduled_tasks, - &_Py_ID(add), task); - if (res == NULL) { - return -1; + assert(Task_Check(state, task)); + assert(task->prev == NULL); + assert(task->next == NULL); + task->prev = state->asyncio_tasks.head; + if (state->asyncio_tasks.head != NULL) { + state->asyncio_tasks.head->next = task; } - Py_DECREF(res); - return 0; + state->asyncio_tasks.head = task; } static int @@ -1957,16 +1966,22 @@ register_eager_task(asyncio_state *state, PyObject *task) return PySet_Add(state->eager_tasks, task); } -static int -unregister_task(asyncio_state *state, PyObject *task) +static void +unregister_task(asyncio_state *state, TaskObj *task) { - PyObject *res = PyObject_CallMethodOneArg(state->scheduled_tasks, - &_Py_ID(discard), task); - if (res == NULL) { - return -1; + assert(Task_Check(state, task)); + if (task->prev != NULL) { + task->prev->next = task->next; } - Py_DECREF(res); - return 0; + if (task->next != NULL) { + task->next->prev = task->prev; + } + if (state->asyncio_tasks.head == task) { + assert(task->next == NULL); + state->asyncio_tasks.head = task->prev; + } + task->next = NULL; + task->prev = NULL; } static int @@ -2147,7 +2162,8 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, if (task_call_step_soon(state, self, NULL)) { return -1; } - return register_task(state, (PyObject*)self); + register_task(state, self); + return 0; } static int @@ -2552,6 +2568,9 @@ _asyncio_Task_set_name(TaskObj *self, PyObject *value) static void TaskObj_finalize(TaskObj *task) { + asyncio_state *state = get_asyncio_state_by_def((PyObject *)task); + unregister_task(state, task); + PyObject *context; PyObject *message = NULL; PyObject *func; @@ -3184,9 +3203,7 @@ task_eager_start(asyncio_state *state, TaskObj *task) } if (task->task_state == STATE_PENDING) { - if (register_task(state, (PyObject *)task) == -1) { - retval = -1; - } + register_task(state, task); } else { // This seems to really help performance on pyperformance benchmarks Py_CLEAR(task->task_coro); @@ -3352,9 +3369,16 @@ _asyncio__register_task_impl(PyObject *module, PyObject *task) /*[clinic end generated code: output=8672dadd69a7d4e2 input=21075aaea14dfbad]*/ { asyncio_state *state = get_asyncio_state(module); - if (register_task(state, task) < 0) { - return NULL; + if (!Task_Check(state, task)) { + PyObject *res = PyObject_CallMethodOneArg(state->scheduled_tasks, + &_Py_ID(add), task); + if (res == NULL) { + return NULL; + } + Py_DECREF(res); + Py_RETURN_NONE; } + register_task(state, (TaskObj *)task); Py_RETURN_NONE; } @@ -3395,9 +3419,16 @@ _asyncio__unregister_task_impl(PyObject *module, PyObject *task) /*[clinic end generated code: output=6e5585706d568a46 input=28fb98c3975f7bdc]*/ { asyncio_state *state = get_asyncio_state(module); - if (unregister_task(state, task) < 0) { - return NULL; + if (!Task_Check(state, task)) { + PyObject *res = PyObject_CallMethodOneArg(state->scheduled_tasks, + &_Py_ID(discard), task); + if (res == NULL) { + return NULL; + } + Py_DECREF(res); + Py_RETURN_NONE; } + unregister_task(state, (TaskObj *)task); Py_RETURN_NONE; } @@ -3534,6 +3565,37 @@ _asyncio_current_task_impl(PyObject *module, PyObject *loop) /*********************** Module **************************/ +/*[clinic input] +_asyncio.all_tasks + + loop: object = None + +Return set of tasks associated for loop. + +[clinic start generated code]*/ + +static PyObject * +_asyncio_all_tasks_impl(PyObject *module, PyObject *loop) +/*[clinic end generated code: output=0e107cbb7f72aa7b input=02fab144171b1879]*/ +{ + PyObject *tasks = PySet_New(NULL); + if (tasks == NULL) { + return NULL; + } + asyncio_state *state = get_asyncio_state(module); + TaskObj *head = state->asyncio_tasks.head; + while (head) + { + if (loop == Py_None || head->task_loop == loop) { + if (PySet_Add(tasks, (PyObject *)head) < 0) { + Py_DECREF(tasks); + return NULL; + } + } + head = head->prev; + } + return tasks; +} static void module_free_freelists(asyncio_state *state) @@ -3731,6 +3793,7 @@ static PyMethodDef asyncio_methods[] = { _ASYNCIO__ENTER_TASK_METHODDEF _ASYNCIO__LEAVE_TASK_METHODDEF _ASYNCIO__SWAP_CURRENT_TASK_METHODDEF + _ASYNCIO_ALL_TASKS_METHODDEF {NULL, NULL} }; @@ -3738,6 +3801,7 @@ static int module_exec(PyObject *mod) { asyncio_state *state = get_asyncio_state(mod); + state->asyncio_tasks.head = NULL; #define CREATE_TYPE(m, tp, spec, base) \ do { \ diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index 6a780a80cd0bc4..3836a9f491f8d0 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -1487,4 +1487,64 @@ _asyncio_current_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=6b0e283177b07639 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_asyncio_all_tasks__doc__, +"all_tasks($module, /, loop=None)\n" +"--\n" +"\n" +"Return set of tasks associated for loop."); + +#define _ASYNCIO_ALL_TASKS_METHODDEF \ + {"all_tasks", _PyCFunction_CAST(_asyncio_all_tasks), METH_FASTCALL|METH_KEYWORDS, _asyncio_all_tasks__doc__}, + +static PyObject * +_asyncio_all_tasks_impl(PyObject *module, PyObject *loop); + +static PyObject * +_asyncio_all_tasks(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(loop), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"loop", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "all_tasks", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + PyObject *loop = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + loop = args[0]; +skip_optional_pos: + return_value = _asyncio_all_tasks_impl(module, loop); + +exit: + return return_value; +} +/*[clinic end generated code: output=ad9ae2a27dcb1fe3 input=a9049054013a1b77]*/ diff --git a/main.py b/main.py new file mode 100644 index 00000000000000..569a2e8a77195d --- /dev/null +++ b/main.py @@ -0,0 +1,11 @@ +import asyncio +import gc +async def foo():pass + +async def main(): + await asyncio.create_task(foo()) + gc.collect(0) + print(asyncio.all_tasks()) + + +asyncio.run(main()) From d5a3d8728e05df9d4ec5291e2759369cf44937fa Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Fri, 26 May 2023 11:15:19 +0530 Subject: [PATCH 02/22] add tail optmiization to linked list --- Modules/_asynciomodule.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index e62a009f3ccc76..78255bb0cabd1d 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -128,6 +128,7 @@ typedef struct { Py_ssize_t fi_freelist_len; struct { + TaskObj tail; TaskObj *head; } asyncio_tasks; @@ -1953,10 +1954,10 @@ register_task(asyncio_state *state, TaskObj *task) assert(Task_Check(state, task)); assert(task->prev == NULL); assert(task->next == NULL); + assert(state->asyncio_tasks.head != NULL); + task->prev = state->asyncio_tasks.head; - if (state->asyncio_tasks.head != NULL) { - state->asyncio_tasks.head->next = task; - } + state->asyncio_tasks.head->next = task; state->asyncio_tasks.head = task; } @@ -1970,15 +1971,13 @@ static void unregister_task(asyncio_state *state, TaskObj *task) { assert(Task_Check(state, task)); - if (task->prev != NULL) { - task->prev->next = task->next; - } - if (task->next != NULL) { - task->next->prev = task->prev; - } - if (state->asyncio_tasks.head == task) { - assert(task->next == NULL); + assert(task->prev != NULL); + task->prev->next = task->next; + if (task->next == NULL) { + assert(state->asyncio_tasks.head == task); state->asyncio_tasks.head = task->prev; + } else { + task->next->prev = task->prev; } task->next = NULL; task->prev = NULL; @@ -3584,7 +3583,10 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) } asyncio_state *state = get_asyncio_state(module); TaskObj *head = state->asyncio_tasks.head; - while (head) + assert(head != NULL); + assert(head->next == NULL); + TaskObj *tail = &state->asyncio_tasks.tail; + while (head != tail) { if (loop == Py_None || head->task_loop == loop) { if (PySet_Add(tasks, (PyObject *)head) < 0) { @@ -3801,7 +3803,7 @@ static int module_exec(PyObject *mod) { asyncio_state *state = get_asyncio_state(mod); - state->asyncio_tasks.head = NULL; + state->asyncio_tasks.head = &state->asyncio_tasks.tail; #define CREATE_TYPE(m, tp, spec, base) \ do { \ From a0c5fcfc2337931a4265935698025c72cbab9a9e Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Fri, 26 May 2023 06:08:58 +0000 Subject: [PATCH 03/22] wip --- Modules/_asynciomodule.c | 2 ++ main.py | 11 ----------- 2 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 main.py diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 78255bb0cabd1d..1c6db3699ed107 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1952,6 +1952,7 @@ static void register_task(asyncio_state *state, TaskObj *task) { assert(Task_Check(state, task)); + assert(task != &state->asyncio_tasks.tail); assert(task->prev == NULL); assert(task->next == NULL); assert(state->asyncio_tasks.head != NULL); @@ -1971,6 +1972,7 @@ static void unregister_task(asyncio_state *state, TaskObj *task) { assert(Task_Check(state, task)); + assert(task != &state->asyncio_tasks.tail); assert(task->prev != NULL); task->prev->next = task->next; if (task->next == NULL) { diff --git a/main.py b/main.py deleted file mode 100644 index 569a2e8a77195d..00000000000000 --- a/main.py +++ /dev/null @@ -1,11 +0,0 @@ -import asyncio -import gc -async def foo():pass - -async def main(): - await asyncio.create_task(foo()) - gc.collect(0) - print(asyncio.all_tasks()) - - -asyncio.run(main()) From 77d012fc7c5f363f8066db2b88076a4e6512db35 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Sun, 28 May 2023 06:53:38 +0000 Subject: [PATCH 04/22] wip --- Modules/_asynciomodule.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 1c6db3699ed107..05f38365f918a0 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1953,6 +1953,9 @@ register_task(asyncio_state *state, TaskObj *task) { assert(Task_Check(state, task)); assert(task != &state->asyncio_tasks.tail); + if (task->prev != NULL) { + return; + } assert(task->prev == NULL); assert(task->next == NULL); assert(state->asyncio_tasks.head != NULL); @@ -1973,7 +1976,11 @@ unregister_task(asyncio_state *state, TaskObj *task) { assert(Task_Check(state, task)); assert(task != &state->asyncio_tasks.tail); - assert(task->prev != NULL); + if (task->prev == NULL) { + assert(task->next == NULL); + assert(state->asyncio_tasks.head != task); + return; + } task->prev->next = task->next; if (task->next == NULL) { assert(state->asyncio_tasks.head == task); @@ -1983,6 +1990,7 @@ unregister_task(asyncio_state *state, TaskObj *task) } task->next = NULL; task->prev = NULL; + assert(state->asyncio_tasks.head != task); } static int @@ -3583,6 +3591,15 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) if (tasks == NULL) { return NULL; } + if (loop == Py_None) { + loop = _asyncio_get_running_loop_impl(module); + if (loop == NULL) { + Py_DECREF(tasks); + return NULL; + } + } else { + Py_INCREF(loop); + } asyncio_state *state = get_asyncio_state(module); TaskObj *head = state->asyncio_tasks.head; assert(head != NULL); @@ -3590,14 +3607,16 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) TaskObj *tail = &state->asyncio_tasks.tail; while (head != tail) { - if (loop == Py_None || head->task_loop == loop) { + if (head->task_loop == loop) { if (PySet_Add(tasks, (PyObject *)head) < 0) { Py_DECREF(tasks); + Py_DECREF(loop); return NULL; } } head = head->prev; } + Py_DECREF(loop); return tasks; } From 5d9653dbf0f040e08d7bfcb07fef8d6666e9874d Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Sun, 28 May 2023 06:55:14 +0000 Subject: [PATCH 05/22] wip --- Modules/_asynciomodule.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 05f38365f918a0..e35471fc981c05 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3587,7 +3587,9 @@ static PyObject * _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) /*[clinic end generated code: output=0e107cbb7f72aa7b input=02fab144171b1879]*/ { - PyObject *tasks = PySet_New(NULL); + + asyncio_state *state = get_asyncio_state(module); + PyObject *tasks = PySet_New(state->scheduled_tasks); if (tasks == NULL) { return NULL; } @@ -3600,7 +3602,6 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) } else { Py_INCREF(loop); } - asyncio_state *state = get_asyncio_state(module); TaskObj *head = state->asyncio_tasks.head; assert(head != NULL); assert(head->next == NULL); From 35a00f1f929b4bc5e9149c04c749077366053e32 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Wed, 2 Aug 2023 07:26:16 +0000 Subject: [PATCH 06/22] more fixes --- Modules/_asynciomodule.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index e72e8595578a4d..856f7bb1329ea1 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -47,7 +47,7 @@ typedef struct { FutureObj_HEAD(fut) } FutureObj; -typedef struct { +typedef struct TaskObj { FutureObj_HEAD(task) unsigned task_must_cancel: 1; unsigned task_log_destroy_pending: 1; @@ -56,8 +56,8 @@ typedef struct { PyObject *task_coro; PyObject *task_name; PyObject *task_context; - TaskObj *next; - TaskObj *prev; + struct TaskObj *next; + struct TaskObj *prev; } TaskObj; typedef struct { @@ -3580,7 +3580,7 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) { asyncio_state *state = get_asyncio_state(module); - PyObject *tasks = PySet_New(state->scheduled_tasks); + PyObject *tasks = PySet_New(NULL); if (tasks == NULL) { return NULL; } @@ -3608,6 +3608,35 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) } head = head->prev; } + PyObject *iter = PyObject_GetIter(state->scheduled_tasks); + if (iter == NULL) { + Py_DECREF(tasks); + Py_DECREF(loop); + return NULL; + } + PyObject *item; + while ((item = PyIter_Next(iter)) != NULL) { + PyObject *task_loop = get_future_loop(state, item); + if (task_loop == NULL) { + Py_DECREF(tasks); + Py_DECREF(loop); + Py_DECREF(iter); + Py_DECREF(item); + return NULL; + } + if (task_loop == loop) { + if (PySet_Add(tasks, item) < 0) { + Py_DECREF(tasks); + Py_DECREF(loop); + Py_DECREF(iter); + Py_DECREF(item); + return NULL; + } + } + Py_DECREF(task_loop); + Py_DECREF(item); + } + Py_DECREF(iter); Py_DECREF(loop); return tasks; } From 1d328351985d2513c7700494e6a267f5fa07f855 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Sat, 5 Aug 2023 06:00:47 +0000 Subject: [PATCH 07/22] finally it works --- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 ++ Modules/_asynciomodule.c | 28 ++++++++++++++++++- 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 6d50ffd0a02f1f..11306f6b3eb6d9 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -895,6 +895,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(displayhook)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(dklen)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(doc)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(done)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(dont_inherit)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(dst)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(dst_dir_fd)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index bb1fb13f342fc6..220a74c627d041 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -384,6 +384,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(displayhook) STRUCT_FOR_ID(dklen) STRUCT_FOR_ID(doc) + STRUCT_FOR_ID(done) STRUCT_FOR_ID(dont_inherit) STRUCT_FOR_ID(dst) STRUCT_FOR_ID(dst_dir_fd) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 2d66647438b193..b91f917dc6ef3d 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -890,6 +890,7 @@ extern "C" { INIT_ID(displayhook), \ INIT_ID(dklen), \ INIT_ID(doc), \ + INIT_ID(done), \ INIT_ID(dont_inherit), \ INIT_ID(dst), \ INIT_ID(dst_dir_fd), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 59f40075f93983..195ad437a8410a 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -996,6 +996,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(doc); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(done); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(dont_inherit); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 39723d8516c395..520c3f55c57469 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3581,7 +3581,7 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) { asyncio_state *state = get_asyncio_state(module); - PyObject *tasks = PySet_New(NULL); + PyObject *tasks = PySet_New(state->eager_tasks); if (tasks == NULL) { return NULL; } @@ -3600,6 +3600,17 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) TaskObj *tail = &state->asyncio_tasks.tail; while (head != tail) { + PyObject *done = _asyncio_Future_done_impl((FutureObj *)head); + if (done == NULL) { + Py_DECREF(tasks); + Py_DECREF(loop); + return NULL; + } + if (Py_IsTrue(done)) { + Py_DECREF(done); + head = head->prev; + continue; + } if (head->task_loop == loop) { if (PySet_Add(tasks, (PyObject *)head) < 0) { Py_DECREF(tasks); @@ -3625,6 +3636,21 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) Py_DECREF(item); return NULL; } + PyObject *done = PyObject_CallMethodNoArgs(item, &_Py_ID(done)); + if (done == NULL) { + Py_DECREF(tasks); + Py_DECREF(loop); + Py_DECREF(iter); + Py_DECREF(item); + Py_DECREF(task_loop); + return NULL; + } + if (Py_IsTrue(done)) { + Py_DECREF(done); + Py_DECREF(task_loop); + Py_DECREF(item); + continue; + } if (task_loop == loop) { if (PySet_Add(tasks, item) < 0) { Py_DECREF(tasks); From af0280a3c8d90bd66f22a0a0a8ee6fc2db2d562c Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Wed, 9 Aug 2023 10:20:34 +0000 Subject: [PATCH 08/22] add tests --- Lib/asyncio/tasks.py | 3 ++- Lib/test/test_asyncio/test_tasks.py | 34 +++++++++++++++++++---------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index af9811d8bafa59..5429679f63dbd6 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -1025,7 +1025,7 @@ def _unregister_eager_task(task): _py_enter_task = _enter_task _py_leave_task = _leave_task _py_swap_current_task = _swap_current_task - +_py_all_tasks = all_tasks try: from _asyncio import (_register_task, _register_eager_task, @@ -1044,3 +1044,4 @@ def _unregister_eager_task(task): _c_enter_task = _enter_task _c_leave_task = _leave_task _c_swap_current_task = _swap_current_task + _c_all_tasks = all_tasks diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 4dfaff847edb90..a82ab7b6a0f218 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -85,6 +85,7 @@ class BaseTaskTests: Task = None Future = None + all_tasks = None def new_task(self, loop, coro, name='TestTask', context=None): return self.__class__.Task(coro, loop=loop, name=name, context=context) @@ -2087,7 +2088,7 @@ async def kill_me(loop): coro = kill_me(self.loop) task = asyncio.ensure_future(coro, loop=self.loop) - self.assertEqual(asyncio.all_tasks(loop=self.loop), {task}) + self.assertEqual(self.all_tasks(loop=self.loop), {task}) asyncio.set_event_loop(None) @@ -2102,7 +2103,7 @@ async def kill_me(loop): # no more reference to kill_me() task: the task is destroyed by the GC support.gc_collect() - self.assertEqual(asyncio.all_tasks(loop=self.loop), set()) + self.assertEqual(self.all_tasks(loop=self.loop), set()) mock_handler.assert_called_with(self.loop, { 'message': 'Task was destroyed but it is pending!', @@ -2251,7 +2252,7 @@ async def coro(): message = m_log.error.call_args[0][0] self.assertIn('Task was destroyed but it is pending', message) - self.assertEqual(asyncio.all_tasks(self.loop), set()) + self.assertEqual(self.all_tasks(self.loop), set()) def test_create_task_with_noncoroutine(self): with self.assertRaisesRegex(TypeError, @@ -2551,6 +2552,7 @@ async def func(): # Add patched Task & Future back to the test case cls.Task = Task cls.Future = Future + cls.all_tasks = tasks.all_tasks # Add an extra unit-test cls.test_subclasses_ctask_cfuture = test_subclasses_ctask_cfuture @@ -2624,6 +2626,7 @@ class CTask_CFuture_Tests(BaseTaskTests, SetMethodsTest, Task = getattr(tasks, '_CTask', None) Future = getattr(futures, '_CFuture', None) + all_tasks = getattr(tasks, '_c_all_tasks', None) @support.refcount_test def test_refleaks_in_task___init__(self): @@ -2655,6 +2658,7 @@ class CTask_CFuture_SubclassTests(BaseTaskTests, test_utils.TestCase): Task = getattr(tasks, '_CTask', None) Future = getattr(futures, '_CFuture', None) + all_tasks = getattr(tasks, '_c_all_tasks', None) @unittest.skipUnless(hasattr(tasks, '_CTask'), @@ -2664,6 +2668,7 @@ class CTaskSubclass_PyFuture_Tests(BaseTaskTests, test_utils.TestCase): Task = getattr(tasks, '_CTask', None) Future = futures._PyFuture + all_tasks = getattr(tasks, '_py_all_tasks', None) @unittest.skipUnless(hasattr(futures, '_CFuture'), @@ -2673,6 +2678,7 @@ class PyTask_CFutureSubclass_Tests(BaseTaskTests, test_utils.TestCase): Future = getattr(futures, '_CFuture', None) Task = tasks._PyTask + all_tasks = getattr(tasks, '_py_all_tasks', None) @unittest.skipUnless(hasattr(tasks, '_CTask'), @@ -2681,6 +2687,7 @@ class CTask_PyFuture_Tests(BaseTaskTests, test_utils.TestCase): Task = getattr(tasks, '_CTask', None) Future = futures._PyFuture + all_tasks = getattr(tasks, '_c_all_tasks', None) @unittest.skipUnless(hasattr(futures, '_CFuture'), @@ -2689,6 +2696,7 @@ class PyTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase): Task = tasks._PyTask Future = getattr(futures, '_CFuture', None) + all_tasks = getattr(tasks, '_c_all_tasks', None) class PyTask_PyFuture_Tests(BaseTaskTests, SetMethodsTest, @@ -2696,6 +2704,7 @@ class PyTask_PyFuture_Tests(BaseTaskTests, SetMethodsTest, Task = tasks._PyTask Future = futures._PyFuture + all_tasks = asyncio.all_tasks @add_subclass_tests @@ -2735,6 +2744,7 @@ class BaseTaskIntrospectionTests: _unregister_task = None _enter_task = None _leave_task = None + all_tasks = None def test__register_task_1(self): class TaskLike: @@ -2748,9 +2758,9 @@ def done(self): task = TaskLike() loop = mock.Mock() - self.assertEqual(asyncio.all_tasks(loop), set()) + self.assertEqual(self.all_tasks(loop), set()) self._register_task(task) - self.assertEqual(asyncio.all_tasks(loop), {task}) + self.assertEqual(self.all_tasks(loop), {task}) self._unregister_task(task) def test__register_task_2(self): @@ -2764,9 +2774,9 @@ def done(self): task = TaskLike() loop = mock.Mock() - self.assertEqual(asyncio.all_tasks(loop), set()) + self.assertEqual(self.all_tasks(loop), set()) self._register_task(task) - self.assertEqual(asyncio.all_tasks(loop), {task}) + self.assertEqual(self.all_tasks(loop), {task}) self._unregister_task(task) def test__register_task_3(self): @@ -2780,9 +2790,9 @@ def done(self): task = TaskLike() loop = mock.Mock() - self.assertEqual(asyncio.all_tasks(loop), set()) + self.assertEqual(self.all_tasks(loop), set()) self._register_task(task) - self.assertEqual(asyncio.all_tasks(loop), set()) + self.assertEqual(self.all_tasks(loop), set()) self._unregister_task(task) def test__enter_task(self): @@ -2833,13 +2843,13 @@ def test__unregister_task(self): task.get_loop = lambda: loop self._register_task(task) self._unregister_task(task) - self.assertEqual(asyncio.all_tasks(loop), set()) + self.assertEqual(self.all_tasks(loop), set()) def test__unregister_task_not_registered(self): task = mock.Mock() loop = mock.Mock() self._unregister_task(task) - self.assertEqual(asyncio.all_tasks(loop), set()) + self.assertEqual(self.all_tasks(loop), set()) class PyIntrospectionTests(test_utils.TestCase, BaseTaskIntrospectionTests): @@ -2847,6 +2857,7 @@ class PyIntrospectionTests(test_utils.TestCase, BaseTaskIntrospectionTests): _unregister_task = staticmethod(tasks._py_unregister_task) _enter_task = staticmethod(tasks._py_enter_task) _leave_task = staticmethod(tasks._py_leave_task) + all_tasks = staticmethod(tasks._py_all_tasks) @unittest.skipUnless(hasattr(tasks, '_c_register_task'), @@ -2857,6 +2868,7 @@ class CIntrospectionTests(test_utils.TestCase, BaseTaskIntrospectionTests): _unregister_task = staticmethod(tasks._c_unregister_task) _enter_task = staticmethod(tasks._c_enter_task) _leave_task = staticmethod(tasks._c_leave_task) + all_tasks = staticmethod(tasks._c_all_tasks) else: _register_task = _unregister_task = _enter_task = _leave_task = None From 999fff728ca5e85b05404d451e8a22a4b7fd4b9d Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 10 Aug 2023 11:12:22 +0530 Subject: [PATCH 09/22] remove weakreflist --- Modules/_asynciomodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 520c3f55c57469..f27464e9946848 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -34,7 +34,6 @@ typedef enum { PyObject *prefix##_result; \ PyObject *prefix##_source_tb; \ PyObject *prefix##_cancel_msg; \ - PyObject *prefix##_weakreflist; \ PyObject *prefix##_cancelled_exc; \ fut_state prefix##_state; \ /* These bitfields need to be at the end of the struct From 87a223131c987e69c9bb62523ddf3dc758f477e6 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Thu, 10 Aug 2023 05:52:54 +0000 Subject: [PATCH 10/22] add some comments --- Modules/_asynciomodule.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index f27464e9946848..5237f428141bcc 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1949,6 +1949,7 @@ register_task(asyncio_state *state, TaskObj *task) assert(Task_Check(state, task)); assert(task != &state->asyncio_tasks.tail); if (task->prev != NULL) { + // already registered return; } assert(task->prev == NULL); @@ -1972,6 +1973,7 @@ unregister_task(asyncio_state *state, TaskObj *task) assert(Task_Check(state, task)); assert(task != &state->asyncio_tasks.tail); if (task->prev == NULL) { + // not registered assert(task->next == NULL); assert(state->asyncio_tasks.head != task); return; From 95526364895c2344c62ce5a02f746fcd3b4e0620 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Tue, 15 Aug 2023 09:16:30 +0000 Subject: [PATCH 11/22] reduce code duplication in _asynciomodule.c --- Modules/_asynciomodule.c | 87 +++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 5237f428141bcc..b77cdbdf0c88b9 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3565,6 +3565,31 @@ _asyncio_current_task_impl(PyObject *module, PyObject *loop) } +static inline int +add_one_task(asyncio_state *state, PyObject *tasks, PyObject *task, PyObject *loop) +{ + PyObject *done = PyObject_CallMethodNoArgs(task, &_Py_ID(done)); + if (done == NULL) { + return -1; + } + if (Py_IsTrue(done)) { + return 0; + } + Py_DECREF(done); + PyObject *task_loop = get_future_loop(state, task); + if (task_loop == NULL) { + return -1; + } + if (task_loop == loop) { + if (PySet_Add(tasks, task) < 0) { + Py_DECREF(task_loop); + return -1; + } + } + Py_DECREF(task_loop); + return 0; +} + /*********************** Module **************************/ /*[clinic input] @@ -3582,7 +3607,7 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) { asyncio_state *state = get_asyncio_state(module); - PyObject *tasks = PySet_New(state->eager_tasks); + PyObject *tasks = PySet_New(NULL); if (tasks == NULL) { return NULL; } @@ -3601,70 +3626,48 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) TaskObj *tail = &state->asyncio_tasks.tail; while (head != tail) { - PyObject *done = _asyncio_Future_done_impl((FutureObj *)head); - if (done == NULL) { + if (add_one_task(state, tasks, (PyObject *)head, loop) < 0) { Py_DECREF(tasks); Py_DECREF(loop); return NULL; } - if (Py_IsTrue(done)) { - Py_DECREF(done); - head = head->prev; - continue; - } - if (head->task_loop == loop) { - if (PySet_Add(tasks, (PyObject *)head) < 0) { - Py_DECREF(tasks); - Py_DECREF(loop); - return NULL; - } - } head = head->prev; } - PyObject *iter = PyObject_GetIter(state->scheduled_tasks); - if (iter == NULL) { + PyObject *scheduled_iter = PyObject_GetIter(state->scheduled_tasks); + if (scheduled_iter == NULL) { Py_DECREF(tasks); Py_DECREF(loop); return NULL; } PyObject *item; - while ((item = PyIter_Next(iter)) != NULL) { - PyObject *task_loop = get_future_loop(state, item); - if (task_loop == NULL) { + while ((item = PyIter_Next(scheduled_iter)) != NULL) { + if (add_one_task(state, tasks, item, loop) < 0) { Py_DECREF(tasks); Py_DECREF(loop); - Py_DECREF(iter); Py_DECREF(item); + Py_DECREF(scheduled_iter); return NULL; } - PyObject *done = PyObject_CallMethodNoArgs(item, &_Py_ID(done)); - if (done == NULL) { + Py_DECREF(item); + } + Py_DECREF(scheduled_iter); + PyObject *eager_iter = PyObject_GetIter(state->eager_tasks); + if (eager_iter == NULL) { + Py_DECREF(tasks); + Py_DECREF(loop); + return NULL; + } + while ((item = PyIter_Next(eager_iter)) != NULL) { + if (add_one_task(state, tasks, item, loop) < 0) { Py_DECREF(tasks); Py_DECREF(loop); - Py_DECREF(iter); Py_DECREF(item); - Py_DECREF(task_loop); + Py_DECREF(eager_iter); return NULL; } - if (Py_IsTrue(done)) { - Py_DECREF(done); - Py_DECREF(task_loop); - Py_DECREF(item); - continue; - } - if (task_loop == loop) { - if (PySet_Add(tasks, item) < 0) { - Py_DECREF(tasks); - Py_DECREF(loop); - Py_DECREF(iter); - Py_DECREF(item); - return NULL; - } - } - Py_DECREF(task_loop); Py_DECREF(item); } - Py_DECREF(iter); + Py_DECREF(eager_iter); Py_DECREF(loop); return tasks; } From 5c5b559afe7f8ba0e1a8ecdfb192f3517d7e9415 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Thu, 17 Aug 2023 05:10:18 +0000 Subject: [PATCH 12/22] address some review comments --- Modules/_asynciomodule.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index b77cdbdf0c88b9..72e22e791248b2 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -90,8 +90,9 @@ typedef struct { all running event loops. {EventLoop: Task} */ PyObject *current_tasks; - /* WeakSet containing all tasks scheduled to run on event loops. */ - PyObject *scheduled_tasks; + /* WeakSet containing scheduled 3rd party tasks which don't + inherit from native asyncio.Task */ + PyObject *non_asyncio_tasks; /* Set containing all eagerly executing tasks. */ PyObject *eager_tasks; @@ -1952,7 +1953,6 @@ register_task(asyncio_state *state, TaskObj *task) // already registered return; } - assert(task->prev == NULL); assert(task->next == NULL); assert(state->asyncio_tasks.head != NULL); @@ -3372,7 +3372,9 @@ _asyncio__register_task_impl(PyObject *module, PyObject *task) { asyncio_state *state = get_asyncio_state(module); if (!Task_Check(state, task)) { - PyObject *res = PyObject_CallMethodOneArg(state->scheduled_tasks, + // As task does not inherit from asyncio.Task, fallback to less efficient + // weakset implementation. + PyObject *res = PyObject_CallMethodOneArg(state->non_asyncio_tasks, &_Py_ID(add), task); if (res == NULL) { return NULL; @@ -3380,6 +3382,8 @@ _asyncio__register_task_impl(PyObject *module, PyObject *task) Py_DECREF(res); Py_RETURN_NONE; } + // task is an asyncio.Task instance or subclass, use efficient + // linked-list implementation. register_task(state, (TaskObj *)task); Py_RETURN_NONE; } @@ -3422,7 +3426,7 @@ _asyncio__unregister_task_impl(PyObject *module, PyObject *task) { asyncio_state *state = get_asyncio_state(module); if (!Task_Check(state, task)) { - PyObject *res = PyObject_CallMethodOneArg(state->scheduled_tasks, + PyObject *res = PyObject_CallMethodOneArg(state->non_asyncio_tasks, &_Py_ID(discard), task); if (res == NULL) { return NULL; @@ -3597,7 +3601,7 @@ _asyncio.all_tasks loop: object = None -Return set of tasks associated for loop. +Return a set of all tasks for the loop. [clinic start generated code]*/ @@ -3633,7 +3637,7 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) } head = head->prev; } - PyObject *scheduled_iter = PyObject_GetIter(state->scheduled_tasks); + PyObject *scheduled_iter = PyObject_GetIter(state->non_asyncio_tasks); if (scheduled_iter == NULL) { Py_DECREF(tasks); Py_DECREF(loop); @@ -3712,7 +3716,7 @@ module_traverse(PyObject *mod, visitproc visit, void *arg) Py_VISIT(state->asyncio_InvalidStateError); Py_VISIT(state->asyncio_CancelledError); - Py_VISIT(state->scheduled_tasks); + Py_VISIT(state->non_asyncio_tasks); Py_VISIT(state->eager_tasks); Py_VISIT(state->current_tasks); Py_VISIT(state->iscoroutine_typecache); @@ -3750,7 +3754,7 @@ module_clear(PyObject *mod) Py_CLEAR(state->asyncio_InvalidStateError); Py_CLEAR(state->asyncio_CancelledError); - Py_CLEAR(state->scheduled_tasks); + Py_CLEAR(state->non_asyncio_tasks); Py_CLEAR(state->eager_tasks); Py_CLEAR(state->current_tasks); Py_CLEAR(state->iscoroutine_typecache); @@ -3831,9 +3835,9 @@ module_init(asyncio_state *state) PyObject *weak_set; WITH_MOD("weakref") GET_MOD_ATTR(weak_set, "WeakSet"); - state->scheduled_tasks = PyObject_CallNoArgs(weak_set); + state->non_asyncio_tasks = PyObject_CallNoArgs(weak_set); Py_CLEAR(weak_set); - if (state->scheduled_tasks == NULL) { + if (state->non_asyncio_tasks == NULL) { goto fail; } @@ -3906,7 +3910,7 @@ module_exec(PyObject *mod) return -1; } - if (PyModule_AddObjectRef(mod, "_scheduled_tasks", state->scheduled_tasks) < 0) { + if (PyModule_AddObjectRef(mod, "_scheduled_tasks", state->non_asyncio_tasks) < 0) { return -1; } From 82cf69be927f340593241f1bc3fdf84311f1709c Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Thu, 17 Aug 2023 05:29:00 +0000 Subject: [PATCH 13/22] add invariants about the state of the linked list --- Modules/_asynciomodule.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 72e22e791248b2..9455a13de3a6f1 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -130,6 +130,27 @@ typedef struct { futureiterobject *fi_freelist; Py_ssize_t fi_freelist_len; + /* Linked-list of all tasks which are instances of asyncio.Task or subclasses + of it. Third party tasks implementations which don't inherit from + asyncio.Task are managed by the 'non_asyncio_tasks' WeakSet separately. + `tail` is used as sentinel to mark end of linked-list and it avoids one + branch in checking for empty list when adding a new task, the list is + initialized with `head` pointing to `tail` to mark an empty list. + + Invariants: + * When the list is empty: + - asyncio_tasks.head == &asyncio_tasks.tail + - asyncio_tasks.head->prev == NULL + - asyncio_tasks.tail->next == NULL + + * After adding a new task 'task': + - asyncio_tasks.head == task + - task->prev == &asyncio_tasks.tail + - task->next == NULL + - asyncio_tasks.tail->next == task + + */ + struct { TaskObj tail; TaskObj *head; From 8998f6a7fd58ac37210119b19e3cbcc4d02fcac0 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Thu, 17 Aug 2023 05:35:02 +0000 Subject: [PATCH 14/22] add better explanation --- Modules/_asynciomodule.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 9455a13de3a6f1..d3c053b6a0c161 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -143,12 +143,29 @@ typedef struct { - asyncio_tasks.head->prev == NULL - asyncio_tasks.tail->next == NULL - * After adding a new task 'task': - - asyncio_tasks.head == task - - task->prev == &asyncio_tasks.tail - - task->next == NULL - - asyncio_tasks.tail->next == task - + * After adding a new task 'task1': + - asyncio_tasks.head == task1 + - task1->prev == &asyncio_tasks.tail + - task1->next == NULL + - asyncio_tasks.tail->next == task1 + + * After adding a second task 'task2': + - asyncio_tasks.head == task2 + - task2->prev == task1 + - task2->next == NULL + - task1->next == task2 + - asyncio_tasks.tail->next == task1 + + * After removing task 'task1': + - asyncio_tasks.head == task2 + - task2->prev == &asyncio_tasks.tail + - task2->next == NULL + - asyncio_tasks.tail->next == task2 + + * After removing task 'task2', list is empty: + - asyncio_tasks.head == &asyncio_tasks.tail + - asyncio_tasks.head->prev == NULL + - asyncio_tasks.tail->next == NULL */ struct { From d93c4e1581276389c9348418c856a9762464d06c Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Thu, 17 Aug 2023 05:44:38 +0000 Subject: [PATCH 15/22] clinic regen --- Modules/_asynciomodule.c | 2 +- Modules/clinic/_asynciomodule.c.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index d3c053b6a0c161..4651d060ed90f1 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3645,7 +3645,7 @@ Return a set of all tasks for the loop. static PyObject * _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) -/*[clinic end generated code: output=0e107cbb7f72aa7b input=02fab144171b1879]*/ +/*[clinic end generated code: output=0e107cbb7f72aa7b input=43a1b423c2d95bfa]*/ { asyncio_state *state = get_asyncio_state(module); diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index 3836a9f491f8d0..dbf159fa7ee8ce 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -1492,7 +1492,7 @@ PyDoc_STRVAR(_asyncio_all_tasks__doc__, "all_tasks($module, /, loop=None)\n" "--\n" "\n" -"Return set of tasks associated for loop."); +"Return a set of all tasks for the loop."); #define _ASYNCIO_ALL_TASKS_METHODDEF \ {"all_tasks", _PyCFunction_CAST(_asyncio_all_tasks), METH_FASTCALL|METH_KEYWORDS, _asyncio_all_tasks__doc__}, @@ -1547,4 +1547,4 @@ _asyncio_all_tasks(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py exit: return return_value; } -/*[clinic end generated code: output=ad9ae2a27dcb1fe3 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=5b69c70834ee0bfb input=a9049054013a1b77]*/ From 35726d99bb2da773158557d1f54f8526023c824c Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Thu, 17 Aug 2023 05:55:41 +0000 Subject: [PATCH 16/22] reorder branches for better branch prediction --- Modules/_asynciomodule.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 4651d060ed90f1..d5d139a1ec413b 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3409,20 +3409,20 @@ _asyncio__register_task_impl(PyObject *module, PyObject *task) /*[clinic end generated code: output=8672dadd69a7d4e2 input=21075aaea14dfbad]*/ { asyncio_state *state = get_asyncio_state(module); - if (!Task_Check(state, task)) { - // As task does not inherit from asyncio.Task, fallback to less efficient - // weakset implementation. - PyObject *res = PyObject_CallMethodOneArg(state->non_asyncio_tasks, - &_Py_ID(add), task); - if (res == NULL) { - return NULL; - } - Py_DECREF(res); + if (Task_Check(state, task)) { + // task is an asyncio.Task instance or subclass, use efficient + // linked-list implementation. + register_task(state, (TaskObj *)task); Py_RETURN_NONE; } - // task is an asyncio.Task instance or subclass, use efficient - // linked-list implementation. - register_task(state, (TaskObj *)task); + // As task does not inherit from asyncio.Task, fallback to less efficient + // weakset implementation. + PyObject *res = PyObject_CallMethodOneArg(state->non_asyncio_tasks, + &_Py_ID(add), task); + if (res == NULL) { + return NULL; + } + Py_DECREF(res); Py_RETURN_NONE; } @@ -3463,16 +3463,16 @@ _asyncio__unregister_task_impl(PyObject *module, PyObject *task) /*[clinic end generated code: output=6e5585706d568a46 input=28fb98c3975f7bdc]*/ { asyncio_state *state = get_asyncio_state(module); - if (!Task_Check(state, task)) { - PyObject *res = PyObject_CallMethodOneArg(state->non_asyncio_tasks, - &_Py_ID(discard), task); - if (res == NULL) { - return NULL; - } - Py_DECREF(res); + if (Task_Check(state, task)) { + unregister_task(state, (TaskObj *)task); Py_RETURN_NONE; } - unregister_task(state, (TaskObj *)task); + PyObject *res = PyObject_CallMethodOneArg(state->non_asyncio_tasks, + &_Py_ID(discard), task); + if (res == NULL) { + return NULL; + } + Py_DECREF(res); Py_RETURN_NONE; } From 8dd049222e2e2e008b7188ac07f13fc0ef8c1862 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 18 Aug 2023 16:31:53 +0530 Subject: [PATCH 17/22] Update Modules/_asynciomodule.c --- Modules/_asynciomodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index d5d139a1ec413b..a6e4d929193510 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -141,7 +141,7 @@ typedef struct { * When the list is empty: - asyncio_tasks.head == &asyncio_tasks.tail - asyncio_tasks.head->prev == NULL - - asyncio_tasks.tail->next == NULL + - asyncio_tasks.head->next == NULL * After adding a new task 'task1': - asyncio_tasks.head == task1 From 8325302bfe2ebbec37789bf1ebb99cb8176f1ade Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Sat, 19 Aug 2023 12:41:18 +0530 Subject: [PATCH 18/22] Apply suggestions from code review Co-authored-by: Itamar Oren --- Modules/_asynciomodule.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index a6e4d929193510..8086e246b8bf95 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -132,8 +132,8 @@ typedef struct { /* Linked-list of all tasks which are instances of asyncio.Task or subclasses of it. Third party tasks implementations which don't inherit from - asyncio.Task are managed by the 'non_asyncio_tasks' WeakSet separately. - `tail` is used as sentinel to mark end of linked-list and it avoids one + asyncio.Task are tracked separately using the 'non_asyncio_tasks' WeakSet. + `tail` is used as a sentinel to mark the end of the linked-list. It avoids one branch in checking for empty list when adding a new task, the list is initialized with `head` pointing to `tail` to mark an empty list. @@ -143,29 +143,29 @@ typedef struct { - asyncio_tasks.head->prev == NULL - asyncio_tasks.head->next == NULL - * After adding a new task 'task1': + * After adding the first task 'task1': - asyncio_tasks.head == task1 - task1->prev == &asyncio_tasks.tail - task1->next == NULL - - asyncio_tasks.tail->next == task1 + - asyncio_tasks.tail.next == task1 * After adding a second task 'task2': - asyncio_tasks.head == task2 - task2->prev == task1 - task2->next == NULL - task1->next == task2 - - asyncio_tasks.tail->next == task1 + - asyncio_tasks.tail.next == task1 * After removing task 'task1': - asyncio_tasks.head == task2 - task2->prev == &asyncio_tasks.tail - task2->next == NULL - - asyncio_tasks.tail->next == task2 + - asyncio_tasks.tail.next == task2 - * After removing task 'task2', list is empty: + * After removing task 'task2', the list is empty: - asyncio_tasks.head == &asyncio_tasks.tail - asyncio_tasks.head->prev == NULL - - asyncio_tasks.tail->next == NULL + - asyncio_tasks.tail.next == NULL */ struct { From 80b65e0b21aa58c2e9aa4079432bc6d6651600af Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Sat, 19 Aug 2023 07:28:15 +0000 Subject: [PATCH 19/22] fix capturing of eager tasks --- Modules/_asynciomodule.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 8086e246b8bf95..12f08c243aae84 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3662,6 +3662,26 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) } else { Py_INCREF(loop); } + // First add eager tasks to the set so that we don't miss + // any tasks which graduates from eager to non-eager + PyObject *eager_iter = PyObject_GetIter(state->eager_tasks); + if (eager_iter == NULL) { + Py_DECREF(tasks); + Py_DECREF(loop); + return NULL; + } + PyObject *item; + while ((item = PyIter_Next(eager_iter)) != NULL) { + if (add_one_task(state, tasks, item, loop) < 0) { + Py_DECREF(tasks); + Py_DECREF(loop); + Py_DECREF(item); + Py_DECREF(eager_iter); + return NULL; + } + Py_DECREF(item); + } + Py_DECREF(eager_iter); TaskObj *head = state->asyncio_tasks.head; assert(head != NULL); assert(head->next == NULL); @@ -3681,7 +3701,6 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) Py_DECREF(loop); return NULL; } - PyObject *item; while ((item = PyIter_Next(scheduled_iter)) != NULL) { if (add_one_task(state, tasks, item, loop) < 0) { Py_DECREF(tasks); @@ -3693,23 +3712,6 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) Py_DECREF(item); } Py_DECREF(scheduled_iter); - PyObject *eager_iter = PyObject_GetIter(state->eager_tasks); - if (eager_iter == NULL) { - Py_DECREF(tasks); - Py_DECREF(loop); - return NULL; - } - while ((item = PyIter_Next(eager_iter)) != NULL) { - if (add_one_task(state, tasks, item, loop) < 0) { - Py_DECREF(tasks); - Py_DECREF(loop); - Py_DECREF(item); - Py_DECREF(eager_iter); - return NULL; - } - Py_DECREF(item); - } - Py_DECREF(eager_iter); Py_DECREF(loop); return tasks; } From b67649e82cd3891b57958110bc013a7e600d7dd0 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Sun, 20 Aug 2023 08:01:01 +0000 Subject: [PATCH 20/22] add comment to task finalization --- Modules/_asynciomodule.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 12f08c243aae84..2a61c2a098ff19 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -2616,6 +2616,12 @@ static void TaskObj_finalize(TaskObj *task) { asyncio_state *state = get_asyncio_state_by_def((PyObject *)task); + // Unregister the task from the linked list of tasks. + // Since task is a native task, we directly call the + // unregister_task function. Third party event loops + // should use the asyncio._unregister_task function. + // See https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support + unregister_task(state, task); PyObject *context; From e06108198d7dfe40fdf8c8f81aa137df02c9cfa7 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Thu, 24 Aug 2023 13:38:21 +0000 Subject: [PATCH 21/22] fix tests and couple c implmentation to c task improved linked-list logic and more comments --- Lib/test/test_asyncio/test_tasks.py | 6 ++-- Modules/_asynciomodule.c | 46 +++++++++++++++-------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index a82ab7b6a0f218..d831413f238b88 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -2678,7 +2678,7 @@ class PyTask_CFutureSubclass_Tests(BaseTaskTests, test_utils.TestCase): Future = getattr(futures, '_CFuture', None) Task = tasks._PyTask - all_tasks = getattr(tasks, '_py_all_tasks', None) + all_tasks = tasks._py_all_tasks @unittest.skipUnless(hasattr(tasks, '_CTask'), @@ -2696,7 +2696,7 @@ class PyTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase): Task = tasks._PyTask Future = getattr(futures, '_CFuture', None) - all_tasks = getattr(tasks, '_c_all_tasks', None) + all_tasks = staticmethod(tasks._py_all_tasks) class PyTask_PyFuture_Tests(BaseTaskTests, SetMethodsTest, @@ -2704,7 +2704,7 @@ class PyTask_PyFuture_Tests(BaseTaskTests, SetMethodsTest, Task = tasks._PyTask Future = futures._PyFuture - all_tasks = asyncio.all_tasks + all_tasks = staticmethod(tasks._py_all_tasks) @add_subclass_tests diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index b5fc6de0e93e3d..f6da055b7587b3 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -145,26 +145,27 @@ typedef struct { * After adding the first task 'task1': - asyncio_tasks.head == task1 - - task1->prev == &asyncio_tasks.tail - - task1->next == NULL - - asyncio_tasks.tail.next == task1 + - task1->next == &asyncio_tasks.tail + - task1->prev == NULL + - asyncio_tasks.tail.prev == task1 * After adding a second task 'task2': - asyncio_tasks.head == task2 - - task2->prev == task1 - - task2->next == NULL - - task1->next == task2 - - asyncio_tasks.tail.next == task1 + - task2->next == task1 + - task2->prev == NULL + - task1->prev == task2 + - asyncio_tasks.tail.prev == task1 * After removing task 'task1': - asyncio_tasks.head == task2 - - task2->prev == &asyncio_tasks.tail - - task2->next == NULL - - asyncio_tasks.tail.next == task2 + - task2->next == &asyncio_tasks.tail + - task2->prev == NULL + - asyncio_tasks.tail.prev == task2 * After removing task 'task2', the list is empty: - asyncio_tasks.head == &asyncio_tasks.tail - asyncio_tasks.head->prev == NULL + - asyncio_tasks.tail.prev == NULL - asyncio_tasks.tail.next == NULL */ @@ -1987,15 +1988,15 @@ register_task(asyncio_state *state, TaskObj *task) { assert(Task_Check(state, task)); assert(task != &state->asyncio_tasks.tail); - if (task->prev != NULL) { + if (task->next != NULL) { // already registered return; } - assert(task->next == NULL); + assert(task->prev == NULL); assert(state->asyncio_tasks.head != NULL); - task->prev = state->asyncio_tasks.head; - state->asyncio_tasks.head->next = task; + task->next = state->asyncio_tasks.head; + state->asyncio_tasks.head->prev = task; state->asyncio_tasks.head = task; } @@ -2010,18 +2011,18 @@ unregister_task(asyncio_state *state, TaskObj *task) { assert(Task_Check(state, task)); assert(task != &state->asyncio_tasks.tail); - if (task->prev == NULL) { + if (task->next == NULL) { // not registered - assert(task->next == NULL); + assert(task->prev == NULL); assert(state->asyncio_tasks.head != task); return; } - task->prev->next = task->next; - if (task->next == NULL) { + task->next->prev = task->prev; + if (task->prev == NULL) { assert(state->asyncio_tasks.head == task); - state->asyncio_tasks.head = task->prev; + state->asyncio_tasks.head = task->next; } else { - task->next->prev = task->prev; + task->prev->next = task->next; } task->next = NULL; task->prev = NULL; @@ -3690,7 +3691,7 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) Py_DECREF(eager_iter); TaskObj *head = state->asyncio_tasks.head; assert(head != NULL); - assert(head->next == NULL); + assert(head->prev == NULL); TaskObj *tail = &state->asyncio_tasks.tail; while (head != tail) { @@ -3699,7 +3700,8 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) Py_DECREF(loop); return NULL; } - head = head->prev; + head = head->next; + assert(head != NULL); } PyObject *scheduled_iter = PyObject_GetIter(state->non_asyncio_tasks); if (scheduled_iter == NULL) { From efce4b30a08c20c7a2c46354e541089c8d35fa29 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Wed, 30 Aug 2023 05:28:56 +0000 Subject: [PATCH 22/22] fix test --- Lib/test/test_asyncio/test_tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index d831413f238b88..84757185c4f453 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -2668,7 +2668,7 @@ class CTaskSubclass_PyFuture_Tests(BaseTaskTests, test_utils.TestCase): Task = getattr(tasks, '_CTask', None) Future = futures._PyFuture - all_tasks = getattr(tasks, '_py_all_tasks', None) + all_tasks = getattr(tasks, '_c_all_tasks', None) @unittest.skipUnless(hasattr(futures, '_CFuture'),