From ca901bb528037f190b080f7027f8d80c6ac9e8c8 Mon Sep 17 00:00:00 2001 From: Chris Billington Date: Fri, 16 Oct 2026 20:49:08 +1100 Subject: [PATCH] Don't override task factory's `eager_start` default PR #748 introduced a regression by passing `eager_start=None` to the task factory when not provided by the caller. On Python 3.14 this is interpreted as `False`, overriding `asyncio.eager_task_factory`'s default of `eager_start=True` and resulting in non-eager tasks. On Python 3.13, it's a `TypeError` , since `eager_task_factory` and user factories with the `(loop, coro, *, name=None, context=None)` signature do not accept an `eager_start` argument at all. Forward `eager_start` to the factory and to `Task` only when provided by the caller. This matches the behaviour of `asyncio`'s loop. Add tests for behaviour when `eager_start=False` and when not provided. --- tests/test_base.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++ uvloop/loop.pyx | 8 +++++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/tests/test_base.py b/tests/test_base.py index b226a84f..b737fb64 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -641,6 +641,58 @@ async def main(): self.loop.run_until_complete(main()) + @unittest.skipUnless( + sys.version_info >= (3, 12), + "asyncio.eager_task_factory requires Python 3.12+", + ) + def test_eager_task_factory(self): + async def coro(): + return "eager" + + async def main(): + task = self.loop.create_task(coro()) + self.assertTrue(task.done()) + self.assertEqual(task.result(), "eager") + + task = asyncio.create_task(coro()) + self.assertTrue(task.done()) + self.assertEqual(task.result(), "eager") + + if sys.version_info >= (3, 14): + task = asyncio.create_task(coro(), eager_start=False) + self.assertFalse(task.done()) + self.assertEqual(await task, "eager") + + self.loop.set_task_factory(asyncio.eager_task_factory) + try: + self.loop.run_until_complete(main()) + finally: + self.loop.set_task_factory(None) + + @unittest.skipUnless( + sys.version_info >= (3, 11), + "asyncio.Task accepts a context argument on Python 3.11+", + ) + def test_task_factory_without_eager_start_kwarg(self): + async def coro(): + return "result" + + def factory(loop, coro, *, name=None, context=None): + return asyncio.Task(coro, loop=loop, name=name, context=context) + + async def main(): + task = self.loop.create_task(coro()) + self.assertEqual(await task, "result") + + task = asyncio.create_task(coro()) + self.assertEqual(await task, "result") + + self.loop.set_task_factory(factory) + try: + self.loop.run_until_complete(main()) + finally: + self.loop.set_task_factory(None) + def test_shutdown_asyncgens_01(self): finalized = list() diff --git a/uvloop/loop.pyx b/uvloop/loop.pyx index 802cdb69..5b62fab7 100644 --- a/uvloop/loop.pyx +++ b/uvloop/loop.pyx @@ -1417,10 +1417,14 @@ cdef class Loop: """ self._check_closed() if PY313: + kwargs = {} + if eager_start is not None: + kwargs['eager_start'] = eager_start if self._task_factory is None: - task = aio_Task(coro, loop=self, context=context, eager_start=eager_start) + task = aio_Task(coro, loop=self, context=context, **kwargs) else: - task = self._task_factory(self, coro, context=context, eager_start=eager_start) + task = self._task_factory( + self, coro, context=context, **kwargs) elif PY311: if self._task_factory is None: task = aio_Task(coro, loop=self, context=context)