Skip to content

Commit

Permalink
bpo-45924: Fix asyncio incorrect traceback when future's exception is…
Browse files Browse the repository at this point in the history
… raised multiple times (GH-30274)
  • Loading branch information
kumaraditya303 authored Jul 11, 2022
1 parent f5b7633 commit 86c1df1
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 2 deletions.
3 changes: 2 additions & 1 deletion Lib/asyncio/futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def result(self):
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):
Expand Down Expand Up @@ -282,6 +282,7 @@ def set_exception(self, exception):
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
Expand Down
31 changes: 30 additions & 1 deletion Lib/test/test_asyncio/test_futures2.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix :mod:`asyncio` incorrect traceback when future's exception is raised multiple times. Patch by Kumar Aditya.
14 changes: 14 additions & 0 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,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; \
Expand Down Expand Up @@ -495,6 +496,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);
Py_CLEAR(fut->fut_cancelled_exc);
Expand Down Expand Up @@ -601,7 +603,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) {
Expand Down Expand Up @@ -656,8 +660,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;
}

Expand Down Expand Up @@ -799,6 +811,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);
Py_CLEAR(fut->fut_cancelled_exc);
Expand All @@ -815,6 +828,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->fut_cancelled_exc);
Expand Down

0 comments on commit 86c1df1

Please sign in to comment.