GH-107803: double linked list implementation for asyncio tasks (GH-107804)

* linked list

* add tail optmiization to linked list

* wip

* wip

* wip

* more fixes

* finally it works

* add tests

* remove weakreflist

* add some comments

* reduce code duplication in _asynciomodule.c

* address some review comments

* add invariants about the state of the linked list

* add better explanation

* clinic regen

* reorder branches for better branch prediction

* Update Modules/_asynciomodule.c

* Apply suggestions from code review

Co-authored-by: Itamar Oren <itamarost@gmail.com>

* fix capturing of eager tasks

* add comment to task finalization

* fix tests and couple c implmentation to c task

improved linked-list logic and more comments

* fix test

---------

Co-authored-by: Itamar Oren <itamarost@gmail.com>
This commit is contained in:
Kumar Aditya 2024-06-22 23:28:35 +05:30 committed by GitHub
parent e213475495
commit 4717aaa1a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 374 additions and 96 deletions

View file

@ -86,6 +86,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)
@ -2267,7 +2268,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)
@ -2282,7 +2283,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!',
@ -2431,7 +2432,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,
@ -2731,6 +2732,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
@ -2804,6 +2806,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):
@ -2835,6 +2838,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'),
@ -2844,6 +2848,7 @@ class CTaskSubclass_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'),
@ -2853,6 +2858,7 @@ class PyTask_CFutureSubclass_Tests(BaseTaskTests, test_utils.TestCase):
Future = getattr(futures, '_CFuture', None)
Task = tasks._PyTask
all_tasks = tasks._py_all_tasks
@unittest.skipUnless(hasattr(tasks, '_CTask'),
@ -2861,6 +2867,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'),
@ -2869,6 +2876,7 @@ class PyTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase):
Task = tasks._PyTask
Future = getattr(futures, '_CFuture', None)
all_tasks = staticmethod(tasks._py_all_tasks)
class PyTask_PyFuture_Tests(BaseTaskTests, SetMethodsTest,
@ -2876,6 +2884,7 @@ class PyTask_PyFuture_Tests(BaseTaskTests, SetMethodsTest,
Task = tasks._PyTask
Future = futures._PyFuture
all_tasks = staticmethod(tasks._py_all_tasks)
@add_subclass_tests
@ -2915,6 +2924,7 @@ class BaseTaskIntrospectionTests:
_unregister_task = None
_enter_task = None
_leave_task = None
all_tasks = None
def test__register_task_1(self):
class TaskLike:
@ -2928,9 +2938,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):
@ -2944,9 +2954,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):
@ -2960,9 +2970,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):
@ -3013,13 +3023,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):
@ -3027,6 +3037,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'),
@ -3037,6 +3048,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