https://github.com/python/cpython/commit/abdbe0b80724c5d12886b78ec0959b05b7352f9d
commit: abdbe0b80724c5d12886b78ec0959b05b7352f9d
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-01-03T13:27:02+05:30
summary:
gh-142615: disallow multiple initializations of `asyncio.Task` and
`asyncio.Future` (#142616)
files:
A Misc/NEWS.d/next/Library/2025-12-12-08-51-29.gh-issue-142615.GoJ6el.rst
M Lib/asyncio/futures.py
M Lib/test/test_asyncio/test_futures.py
M Lib/test/test_asyncio/test_tasks.py
M Modules/_asynciomodule.c
diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py
index 29652295218a22..11858a0274a69f 100644
--- a/Lib/asyncio/futures.py
+++ b/Lib/asyncio/futures.py
@@ -79,6 +79,10 @@ def __init__(self, *, loop=None):
loop object used by the future. If it's not provided, the future uses
the default event loop.
"""
+ if self._loop is not None:
+ raise RuntimeError(f"{self.__class__.__name__} object is already "
+ "initialized")
+
if loop is None:
self._loop = events.get_event_loop()
else:
diff --git a/Lib/test/test_asyncio/test_futures.py
b/Lib/test/test_asyncio/test_futures.py
index 666f9c9ee18783..9385a65e52813e 100644
--- a/Lib/test/test_asyncio/test_futures.py
+++ b/Lib/test/test_asyncio/test_futures.py
@@ -750,6 +750,10 @@ def test_future_cancelled_exception_refcycles(self):
self.assertIsNotNone(exc)
self.assertListEqual(gc.get_referrers(exc), [])
+ def test_future_disallow_multiple_initialization(self):
+ f = self._new_future(loop=self.loop)
+ with self.assertRaises(RuntimeError, msg="is already initialized"):
+ f.__init__(loop=self.loop)
@unittest.skipUnless(hasattr(futures, '_CFuture'),
'requires the C _asyncio module')
@@ -1091,33 +1095,6 @@ def __getattribute__(self, name):
fut.add_done_callback(fut_callback_0)
self.assertRaises(ReachableCode, fut.set_result, "boom")
- def test_use_after_free_on_fut_context_0_with_evil__getattribute__(self):
- # see: https://github.com/python/cpython/issues/125984
-
- class EvilEventLoop(SimpleEvilEventLoop):
- def call_soon(self, *args, **kwargs):
- super().call_soon(*args, **kwargs)
- raise ReachableCode
-
- def __getattribute__(self, name):
- if name == 'call_soon':
- # resets the future's event loop
- fut.__init__(loop=SimpleEvilEventLoop())
- return object.__getattribute__(self, name)
-
- evil_loop = EvilEventLoop()
- with mock.patch.object(self, 'loop', evil_loop):
- fut = self._new_future()
- self.assertIs(fut.get_loop(), evil_loop)
-
- fut_callback_0 = mock.Mock()
- fut_context_0 = mock.Mock()
- fut.add_done_callback(fut_callback_0, context=fut_context_0)
- del fut_context_0
- del fut_callback_0
- self.assertRaises(ReachableCode, fut.set_result, "boom")
-
-
@unittest.skipUnless(hasattr(futures, '_CFuture'),
'requires the C _asyncio module')
class CFutureDoneCallbackTests(BaseFutureDoneCallbackTests,
diff --git a/Lib/test/test_asyncio/test_tasks.py
b/Lib/test/test_asyncio/test_tasks.py
index a3c5351fed0252..dc179acd86e8a6 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -2776,28 +2776,17 @@ def test_get_context(self):
finally:
loop.close()
- def test_proper_refcounts(self):
- # see: https://github.com/python/cpython/issues/126083
- class Break:
- def __str__(self):
- raise RuntimeError("break")
-
- obj = object()
- initial_refcount = sys.getrefcount(obj)
-
- coro = coroutine_function()
- with contextlib.closing(asyncio.EventLoop()) as loop:
- task = asyncio.Task.__new__(asyncio.Task)
- for _ in range(5):
- with self.assertRaisesRegex(RuntimeError, 'break'):
- task.__init__(coro, loop=loop, context=obj, name=Break())
-
- coro.close()
- task._log_destroy_pending = False
- del task
+ def test_task_disallow_multiple_initialization(self):
+ async def foo():
+ pass
- self.assertEqual(sys.getrefcount(obj), initial_refcount)
+ coro = foo()
+ self.addCleanup(coro.close)
+ task = self.new_task(self.loop, coro)
+ task._log_destroy_pending = False
+ with self.assertRaises(RuntimeError, msg="is already initialized"):
+ task.__init__(coro, loop=self.loop)
def add_subclass_tests(cls):
BaseTask = cls.Task
@@ -2921,19 +2910,6 @@ class CTask_CFuture_Tests(BaseTaskTests, SetMethodsTest,
all_tasks = getattr(tasks, '_c_all_tasks', None)
current_task = staticmethod(getattr(tasks, '_c_current_task', None))
- @support.refcount_test
- def test_refleaks_in_task___init__(self):
- gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
- async def coro():
- pass
- task = self.new_task(self.loop, coro())
- self.loop.run_until_complete(task)
- refs_before = gettotalrefcount()
- for i in range(100):
- task.__init__(coro(), loop=self.loop)
- self.loop.run_until_complete(task)
- self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
-
def test_del__log_destroy_pending_segfault(self):
async def coro():
pass
diff --git
a/Misc/NEWS.d/next/Library/2025-12-12-08-51-29.gh-issue-142615.GoJ6el.rst
b/Misc/NEWS.d/next/Library/2025-12-12-08-51-29.gh-issue-142615.GoJ6el.rst
new file mode 100644
index 00000000000000..3413f9a5ac6db6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-12-12-08-51-29.gh-issue-142615.GoJ6el.rst
@@ -0,0 +1,3 @@
+Fix possible crashes when initializing :class:`asyncio.Task` or
:class:`asyncio.Future` multiple times.
+These classes can now be initialized only once and any subsequent
initialization attempt will raise a RuntimeError.
+Patch by Kumar Aditya.
diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c
index 0b2a5d7093e1ea..8eb8e191530a33 100644
--- a/Modules/_asynciomodule.c
+++ b/Modules/_asynciomodule.c
@@ -498,21 +498,13 @@ future_schedule_callbacks(asyncio_state *state, FutureObj
*fut)
static int
future_init(FutureObj *fut, PyObject *loop)
{
+ if (fut->fut_loop != NULL) {
+ PyErr_Format(PyExc_RuntimeError, "%T object is already initialized",
fut);
+ return -1;
+ }
+
PyObject *res;
int is_true;
-
- Py_CLEAR(fut->fut_loop);
- Py_CLEAR(fut->fut_callback0);
- Py_CLEAR(fut->fut_context0);
- Py_CLEAR(fut->fut_callbacks);
- Py_CLEAR(fut->fut_result);
- Py_CLEAR(fut->fut_exception);
- Py_CLEAR(fut->fut_exception_tb);
- Py_CLEAR(fut->fut_source_tb);
- Py_CLEAR(fut->fut_cancel_msg);
- Py_CLEAR(fut->fut_cancelled_exc);
- Py_CLEAR(fut->fut_awaited_by);
-
fut->fut_state = STATE_PENDING;
fut->fut_log_tb = 0;
fut->fut_blocking = 0;
@@ -3008,11 +3000,7 @@ task_call_step_soon(asyncio_state *state, TaskObj *task,
PyObject *arg)
return -1;
}
- // Beware: An evil call_soon could alter task_context.
- // See: https://github.com/python/cpython/issues/126080.
- PyObject *task_context = Py_NewRef(task->task_context);
- int ret = call_soon(state, task->task_loop, cb, NULL, task_context);
- Py_DECREF(task_context);
+ int ret = call_soon(state, task->task_loop, cb, NULL, task->task_context);
Py_DECREF(cb);
return ret;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]