]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-45924: Fix asyncio incorrect traceback when future's exception is raised multiple...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 11 Jul 2022 14:38:27 +0000 (07:38 -0700)
committerGitHub <noreply@github.com>
Mon, 11 Jul 2022 14:38:27 +0000 (15:38 +0100)
(cherry picked from commit 86c1df18727568758cc329baddc1836e45664023)

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
Lib/asyncio/futures.py
Lib/test/test_asyncio/test_futures2.py
Misc/NEWS.d/next/Library/2021-12-27-15-32-15.bpo-45924.0ZpHX2.rst [new file with mode: 0644]
Modules/_asynciomodule.c

index 8e8cd87612588cfa85e75b1366507c70da08ad69..48a32f868385c42da0e2608a5fc46d06a26ede38 100644 (file)
@@ -198,7 +198,7 @@ class Future:
             raise exceptions.InvalidStateError('Result is not ready.')
         self.__log_traceback = False
         if self._exception is not None:
-            raise self._exception
+            raise self._exception.with_traceback(self._exception_tb)
         return self._result
 
     def exception(self):
@@ -274,6 +274,7 @@ class Future:
             raise TypeError("StopIteration interacts badly with generators "
                             "and cannot be raised into a Future")
         self._exception = exception
+        self._exception_tb = exception.__traceback__
         self._state = _FINISHED
         self.__schedule_callbacks()
         self.__log_traceback = True
index 60b58850369f053fd832f49bae461af4c10b4020..71279b69c7929ea958327287271a21b55b5bf3db 100644 (file)
@@ -1,13 +1,42 @@
 # IsolatedAsyncioTestCase based tests
 import asyncio
+import traceback
 import unittest
+from asyncio import tasks
 
 
 def tearDownModule():
     asyncio.set_event_loop_policy(None)
 
 
-class FutureTests(unittest.IsolatedAsyncioTestCase):
+class FutureTests:
+
+    async def test_future_traceback(self):
+
+        async def raise_exc():
+            raise TypeError(42)
+
+        future = self.cls(raise_exc())
+
+        for _ in range(5):
+            try:
+                await future
+            except TypeError as e:
+                tb = ''.join(traceback.format_tb(e.__traceback__))
+                self.assertEqual(tb.count("await future"), 1)
+            else:
+                self.fail('TypeError was not raised')
+
+@unittest.skipUnless(hasattr(tasks, '_CTask'),
+                       'requires the C _asyncio module')
+class CFutureTests(FutureTests, unittest.IsolatedAsyncioTestCase):
+    cls = tasks._CTask
+
+class PyFutureTests(FutureTests, unittest.IsolatedAsyncioTestCase):
+    cls = tasks._PyTask
+
+class FutureReprTests(unittest.IsolatedAsyncioTestCase):
+
     async def test_recursive_repr_for_pending_tasks(self):
         # The call crashes if the guard for recursive call
         # in base_futures:_future_repr_info is absent
diff --git a/Misc/NEWS.d/next/Library/2021-12-27-15-32-15.bpo-45924.0ZpHX2.rst b/Misc/NEWS.d/next/Library/2021-12-27-15-32-15.bpo-45924.0ZpHX2.rst
new file mode 100644 (file)
index 0000000..5cda227
--- /dev/null
@@ -0,0 +1 @@
+Fix :mod:`asyncio` incorrect traceback when future's exception is raised multiple times. Patch by Kumar Aditya.
index 392e0e7c7357f85c55ef497619c590901da06d36..1f6de2177a37afeb8da86fd97b87e993b399304c 100644 (file)
@@ -63,6 +63,7 @@ typedef enum {
     PyObject *prefix##_context0;                                            \
     PyObject *prefix##_callbacks;                                           \
     PyObject *prefix##_exception;                                           \
+    PyObject *prefix##_exception_tb;                                        \
     PyObject *prefix##_result;                                              \
     PyObject *prefix##_source_tb;                                           \
     PyObject *prefix##_cancel_msg;                                          \
@@ -487,6 +488,7 @@ future_init(FutureObj *fut, PyObject *loop)
     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);
     _PyErr_ClearExcState(&fut->fut_cancelled_exc_state);
@@ -593,7 +595,9 @@ future_set_exception(FutureObj *fut, PyObject *exc)
     }
 
     assert(!fut->fut_exception);
+    assert(!fut->fut_exception_tb);
     fut->fut_exception = exc_val;
+    fut->fut_exception_tb = PyException_GetTraceback(exc_val);
     fut->fut_state = STATE_FINISHED;
 
     if (future_schedule_callbacks(fut) == -1) {
@@ -641,8 +645,16 @@ future_get_result(FutureObj *fut, PyObject **result)
 
     fut->fut_log_tb = 0;
     if (fut->fut_exception != NULL) {
+        PyObject *tb = fut->fut_exception_tb;
+        if (tb == NULL) {
+            tb = Py_None;
+        }
+        if (PyException_SetTraceback(fut->fut_exception, tb) < 0) {
+            return -1;
+        }
         Py_INCREF(fut->fut_exception);
         *result = fut->fut_exception;
+        Py_CLEAR(fut->fut_exception_tb);
         return 1;
     }
 
@@ -784,6 +796,7 @@ FutureObj_clear(FutureObj *fut)
     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);
     _PyErr_ClearExcState(&fut->fut_cancelled_exc_state);
@@ -800,6 +813,7 @@ FutureObj_traverse(FutureObj *fut, visitproc visit, void *arg)
     Py_VISIT(fut->fut_callbacks);
     Py_VISIT(fut->fut_result);
     Py_VISIT(fut->fut_exception);
+    Py_VISIT(fut->fut_exception_tb);
     Py_VISIT(fut->fut_source_tb);
     Py_VISIT(fut->fut_cancel_msg);
     Py_VISIT(fut->dict);