Skip to content

Commit aa2142d

Browse files
bpo-45924: Fix asyncio incorrect traceback when future's exception is raised multiple times (GH-30274) (#94748)
(cherry picked from commit 86c1df1) Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
1 parent 916686f commit aa2142d

4 files changed

Lines changed: 47 additions & 2 deletions

File tree

Lib/asyncio/futures.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def result(self):
198198
raise exceptions.InvalidStateError('Result is not ready.')
199199
self.__log_traceback = False
200200
if self._exception is not None:
201-
raise self._exception
201+
raise self._exception.with_traceback(self._exception_tb)
202202
return self._result
203203

204204
def exception(self):
@@ -274,6 +274,7 @@ def set_exception(self, exception):
274274
raise TypeError("StopIteration interacts badly with generators "
275275
"and cannot be raised into a Future")
276276
self._exception = exception
277+
self._exception_tb = exception.__traceback__
277278
self._state = _FINISHED
278279
self.__schedule_callbacks()
279280
self.__log_traceback = True

Lib/test/test_asyncio/test_futures2.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,42 @@
11
# IsolatedAsyncioTestCase based tests
22
import asyncio
3+
import traceback
34
import unittest
5+
from asyncio import tasks
46

57

68
def tearDownModule():
79
asyncio.set_event_loop_policy(None)
810

911

10-
class FutureTests(unittest.IsolatedAsyncioTestCase):
12+
class FutureTests:
13+
14+
async def test_future_traceback(self):
15+
16+
async def raise_exc():
17+
raise TypeError(42)
18+
19+
future = self.cls(raise_exc())
20+
21+
for _ in range(5):
22+
try:
23+
await future
24+
except TypeError as e:
25+
tb = ''.join(traceback.format_tb(e.__traceback__))
26+
self.assertEqual(tb.count("await future"), 1)
27+
else:
28+
self.fail('TypeError was not raised')
29+
30+
@unittest.skipUnless(hasattr(tasks, '_CTask'),
31+
'requires the C _asyncio module')
32+
class CFutureTests(FutureTests, unittest.IsolatedAsyncioTestCase):
33+
cls = tasks._CTask
34+
35+
class PyFutureTests(FutureTests, unittest.IsolatedAsyncioTestCase):
36+
cls = tasks._PyTask
37+
38+
class FutureReprTests(unittest.IsolatedAsyncioTestCase):
39+
1140
async def test_recursive_repr_for_pending_tasks(self):
1241
# The call crashes if the guard for recursive call
1342
# in base_futures:_future_repr_info is absent
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :mod:`asyncio` incorrect traceback when future's exception is raised multiple times. Patch by Kumar Aditya.

Modules/_asynciomodule.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ typedef enum {
6363
PyObject *prefix##_context0; \
6464
PyObject *prefix##_callbacks; \
6565
PyObject *prefix##_exception; \
66+
PyObject *prefix##_exception_tb; \
6667
PyObject *prefix##_result; \
6768
PyObject *prefix##_source_tb; \
6869
PyObject *prefix##_cancel_msg; \
@@ -487,6 +488,7 @@ future_init(FutureObj *fut, PyObject *loop)
487488
Py_CLEAR(fut->fut_callbacks);
488489
Py_CLEAR(fut->fut_result);
489490
Py_CLEAR(fut->fut_exception);
491+
Py_CLEAR(fut->fut_exception_tb);
490492
Py_CLEAR(fut->fut_source_tb);
491493
Py_CLEAR(fut->fut_cancel_msg);
492494
_PyErr_ClearExcState(&fut->fut_cancelled_exc_state);
@@ -593,7 +595,9 @@ future_set_exception(FutureObj *fut, PyObject *exc)
593595
}
594596

595597
assert(!fut->fut_exception);
598+
assert(!fut->fut_exception_tb);
596599
fut->fut_exception = exc_val;
600+
fut->fut_exception_tb = PyException_GetTraceback(exc_val);
597601
fut->fut_state = STATE_FINISHED;
598602

599603
if (future_schedule_callbacks(fut) == -1) {
@@ -641,8 +645,16 @@ future_get_result(FutureObj *fut, PyObject **result)
641645

642646
fut->fut_log_tb = 0;
643647
if (fut->fut_exception != NULL) {
648+
PyObject *tb = fut->fut_exception_tb;
649+
if (tb == NULL) {
650+
tb = Py_None;
651+
}
652+
if (PyException_SetTraceback(fut->fut_exception, tb) < 0) {
653+
return -1;
654+
}
644655
Py_INCREF(fut->fut_exception);
645656
*result = fut->fut_exception;
657+
Py_CLEAR(fut->fut_exception_tb);
646658
return 1;
647659
}
648660

@@ -784,6 +796,7 @@ FutureObj_clear(FutureObj *fut)
784796
Py_CLEAR(fut->fut_callbacks);
785797
Py_CLEAR(fut->fut_result);
786798
Py_CLEAR(fut->fut_exception);
799+
Py_CLEAR(fut->fut_exception_tb);
787800
Py_CLEAR(fut->fut_source_tb);
788801
Py_CLEAR(fut->fut_cancel_msg);
789802
_PyErr_ClearExcState(&fut->fut_cancelled_exc_state);
@@ -800,6 +813,7 @@ FutureObj_traverse(FutureObj *fut, visitproc visit, void *arg)
800813
Py_VISIT(fut->fut_callbacks);
801814
Py_VISIT(fut->fut_result);
802815
Py_VISIT(fut->fut_exception);
816+
Py_VISIT(fut->fut_exception_tb);
803817
Py_VISIT(fut->fut_source_tb);
804818
Py_VISIT(fut->fut_cancel_msg);
805819
Py_VISIT(fut->dict);

0 commit comments

Comments
 (0)