Skip to content

Commit 15a9cfe

Browse files
bpo-45924: Fix asyncio incorrect traceback when future's exception is raised multiple times
1 parent d75a51b commit 15a9cfe

4 files changed

Lines changed: 42 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: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
# IsolatedAsyncioTestCase based tests
22
import asyncio
33
import unittest
4-
4+
import traceback
55

66
class FutureTests(unittest.IsolatedAsyncioTestCase):
7+
async def test_future_traceback(self):
8+
async def raise_exc():
9+
raise TypeError(42)
10+
future = asyncio.create_task(raise_exc())
11+
for _ in range(10):
12+
try:
13+
await future
14+
except TypeError:
15+
tb = traceback.format_exc()
16+
expected = (
17+
f"""Traceback (most recent call last):
18+
File "{__file__}", line 13, in test_future_traceback
19+
await future
20+
^^^^^^^^^^^^
21+
File "{__file__}", line 9, in raise_exc
22+
raise TypeError(42)
23+
^^^^^^^^^^^^^^^^^^^
24+
TypeError: 42
25+
"""
26+
)
27+
self.assertEqual(tb, expected)
28+
29+
730
async def test_recursive_repr_for_pending_tasks(self):
831
# The call crashes if the guard for recursive call
932
# 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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ typedef enum {
6868
PyObject *prefix##_context0; \
6969
PyObject *prefix##_callbacks; \
7070
PyObject *prefix##_exception; \
71+
PyObject *prefix##_exception_tb; \
7172
PyObject *prefix##_result; \
7273
PyObject *prefix##_source_tb; \
7374
PyObject *prefix##_cancel_msg; \
@@ -492,6 +493,7 @@ future_init(FutureObj *fut, PyObject *loop)
492493
Py_CLEAR(fut->fut_callbacks);
493494
Py_CLEAR(fut->fut_result);
494495
Py_CLEAR(fut->fut_exception);
496+
Py_CLEAR(fut->fut_exception_tb);
495497
Py_CLEAR(fut->fut_source_tb);
496498
Py_CLEAR(fut->fut_cancel_msg);
497499
_PyErr_ClearExcState(&fut->fut_cancelled_exc_state);
@@ -598,7 +600,9 @@ future_set_exception(FutureObj *fut, PyObject *exc)
598600
}
599601

600602
assert(!fut->fut_exception);
603+
assert(!fut->fut_exception_tb);
601604
fut->fut_exception = exc_val;
605+
fut->fut_exception_tb = PyException_GetTraceback(exc_val);
602606
fut->fut_state = STATE_FINISHED;
603607

604608
if (future_schedule_callbacks(fut) == -1) {
@@ -646,6 +650,15 @@ future_get_result(FutureObj *fut, PyObject **result)
646650

647651
fut->fut_log_tb = 0;
648652
if (fut->fut_exception != NULL) {
653+
if (fut->fut_exception_tb != NULL) {
654+
if (PyException_SetTraceback(fut->fut_exception, fut->fut_exception_tb) < 0)
655+
{
656+
return -1;
657+
}
658+
}
659+
else {
660+
PyException_SetTraceback(fut->fut_exception, Py_None);
661+
}
649662
Py_INCREF(fut->fut_exception);
650663
*result = fut->fut_exception;
651664
return 1;
@@ -789,6 +802,7 @@ FutureObj_clear(FutureObj *fut)
789802
Py_CLEAR(fut->fut_callbacks);
790803
Py_CLEAR(fut->fut_result);
791804
Py_CLEAR(fut->fut_exception);
805+
Py_CLEAR(fut->fut_exception_tb);
792806
Py_CLEAR(fut->fut_source_tb);
793807
Py_CLEAR(fut->fut_cancel_msg);
794808
_PyErr_ClearExcState(&fut->fut_cancelled_exc_state);
@@ -805,6 +819,7 @@ FutureObj_traverse(FutureObj *fut, visitproc visit, void *arg)
805819
Py_VISIT(fut->fut_callbacks);
806820
Py_VISIT(fut->fut_result);
807821
Py_VISIT(fut->fut_exception);
822+
Py_VISIT(fut->fut_exception_tb);
808823
Py_VISIT(fut->fut_source_tb);
809824
Py_VISIT(fut->fut_cancel_msg);
810825
Py_VISIT(fut->dict);

0 commit comments

Comments
 (0)