diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index a217177e1deb06..5ed3ec582224c6 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -763,6 +763,24 @@ def test_future_disallow_multiple_initialization(self): with self.assertRaises(RuntimeError, msg="is already initialized"): f.__init__(loop=self.loop) + def test_futureiter_send_after_throw_no_crash(self): + fut = self._new_future(loop=self.loop) + it = fut.__await__() + next(it) + with self.assertRaises(RuntimeError): + it.throw(RuntimeError) + with self.assertRaises(StopIteration): + it.send(None) + + def test_futureiter_send_after_close_no_crash(self): + fut = self._new_future(loop=self.loop) + it = fut.__await__() + next(it) + it.close() + with self.assertRaises(StopIteration): + it.send(None) + + @unittest.skipUnless(hasattr(futures, '_CFuture'), 'requires the C _asyncio module') class CFutureTests(BaseFutureTests, test_utils.TestCase): diff --git a/Misc/NEWS.d/next/Library/2026-03-23-00-04-13.gh-issue-146065.FIdn8D.rst b/Misc/NEWS.d/next/Library/2026-03-23-00-04-13.gh-issue-146065.FIdn8D.rst new file mode 100644 index 00000000000000..db91ce76198c01 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-23-00-04-13.gh-issue-146065.FIdn8D.rst @@ -0,0 +1,2 @@ +Fix a crash in asyncio.Future iterator when send() is +called after throw() or close(). diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 18e731336d6da1..5569f7a3670378 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1795,10 +1795,9 @@ FutureIter_dealloc(PyObject *it) } static PySendResult -FutureIter_am_send_lock_held(futureiterobject *it, PyObject **result) +FutureIter_am_send_lock_held(FutureObj *fut, PyObject **result) { PyObject *res; - FutureObj *fut = it->future; _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(fut); *result = NULL; @@ -1829,11 +1828,24 @@ FutureIter_am_send(PyObject *op, PyObject **result) { futureiterobject *it = (futureiterobject*)op; + + FutureObj *fut; + Py_BEGIN_CRITICAL_SECTION(op); + fut = (FutureObj*)Py_XNewRef(it->future); + Py_END_CRITICAL_SECTION(); + + if (fut == NULL) { + PyErr_SetNone(PyExc_StopIteration); + *result = NULL; + return PYGEN_ERROR; + } + /* arg is unused, see the comment on FutureIter_send for clarification */ PySendResult res; - Py_BEGIN_CRITICAL_SECTION(it->future); - res = FutureIter_am_send_lock_held(it, result); + Py_BEGIN_CRITICAL_SECTION(fut); + res = FutureIter_am_send_lock_held(fut, result); Py_END_CRITICAL_SECTION(); + Py_DECREF(fut); return res; } @@ -1927,7 +1939,9 @@ FutureIter_throw(PyObject *op, PyObject *const *args, Py_ssize_t nargs) goto fail; } + Py_BEGIN_CRITICAL_SECTION(self); Py_CLEAR(self->future); + Py_END_CRITICAL_SECTION(); PyErr_Restore(type, val, tb); @@ -1940,6 +1954,16 @@ FutureIter_throw(PyObject *op, PyObject *const *args, Py_ssize_t nargs) return NULL; } +static PyObject * +FutureIter_close(PyObject *self, PyObject *arg) +{ + futureiterobject *it = (futureiterobject*)self; + Py_BEGIN_CRITICAL_SECTION(self); + Py_CLEAR(it->future); + Py_END_CRITICAL_SECTION(); + Py_RETURN_NONE; +} + static int FutureIter_clear(PyObject *op) { @@ -1948,13 +1972,6 @@ FutureIter_clear(PyObject *op) return 0; } -static PyObject * -FutureIter_close(PyObject *self, PyObject *arg) -{ - (void)FutureIter_clear(self); - Py_RETURN_NONE; -} - static int FutureIter_traverse(PyObject *op, visitproc visit, void *arg) {