https://github.com/python/cpython/commit/94cd2e0ddeff83dee3254ca356d9e4396927d075
commit: 94cd2e0ddeff83dee3254ca356d9e4396927d075
branch: main
author: Kumar Aditya <kumaradi...@python.org>
committer: kumaraditya303 <kumaradi...@python.org>
date: 2025-02-10T17:03:59+05:30
summary:

gh-129289: fix crash when task finalizer is not called in asyncio (#129840)

files:
M Include/internal/pycore_object.h
M Lib/test/test_asyncio/test_tasks.py
M Modules/_asynciomodule.c

diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index 0b1df7e68b8dfa..49ddfd5b43b00c 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -121,7 +121,7 @@ PyAPI_DATA(Py_ssize_t) _Py_RefTotal;
 
 extern void _Py_AddRefTotal(PyThreadState *, Py_ssize_t);
 extern PyAPI_FUNC(void) _Py_IncRefTotal(PyThreadState *);
-extern void _Py_DecRefTotal(PyThreadState *);
+extern PyAPI_FUNC(void) _Py_DecRefTotal(PyThreadState *);
 
 #  define _Py_DEC_REFTOTAL(interp) \
     interp->object_state.reftotal--
@@ -710,7 +710,7 @@ _PyObject_SetMaybeWeakref(PyObject *op)
     }
 }
 
-extern int _PyObject_ResurrectEndSlow(PyObject *op);
+extern PyAPI_FUNC(int) _PyObject_ResurrectEndSlow(PyObject *op);
 #endif
 
 // Temporarily resurrects an object during deallocation. The refcount is set
diff --git a/Lib/test/test_asyncio/test_tasks.py 
b/Lib/test/test_asyncio/test_tasks.py
index 5a28d4c185bf96..de2e658bca66a0 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -2296,6 +2296,22 @@ async def kill_me(loop):
 
         self.assertEqual(self.all_tasks(loop=self.loop), set())
 
+    def test_task_not_crash_without_finalization(self):
+        Task = self.__class__.Task
+
+        class Subclass(Task):
+            def __del__(self):
+                pass
+
+        async def coro():
+            await asyncio.sleep(0.01)
+
+        task = Subclass(coro(), loop = self.loop)
+        task._log_destroy_pending = False
+
+        del task
+
+        support.gc_collect()
 
     @mock.patch('asyncio.base_events.logger')
     def test_tb_logger_not_called_after_cancel(self, m_log):
diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c
index 449c8a9499261a..5a4e65636e4b96 100644
--- a/Modules/_asynciomodule.c
+++ b/Modules/_asynciomodule.c
@@ -2938,15 +2938,6 @@ _asyncio_Task_set_name_impl(TaskObj *self, PyObject 
*value)
 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;
     PyObject *message = NULL;
     PyObject *func;
@@ -3071,8 +3062,15 @@ TaskObj_dealloc(PyObject *self)
 {
     TaskObj *task = (TaskObj *)self;
 
-    if (PyObject_CallFinalizerFromDealloc(self) < 0) {
-        // resurrected.
+    _PyObject_ResurrectStart(self);
+    // Unregister the task here so that even if any subclass of Task
+    // which doesn't end up calling TaskObj_finalize not crashes.
+    asyncio_state *state = get_asyncio_state_by_def(self);
+    unregister_task(state, task);
+
+    PyObject_CallFinalizer(self);
+
+    if (_PyObject_ResurrectEnd(self)) {
         return;
     }
 

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to