Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions Lib/asyncio/base_futures.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
__all__ = ()

import reprlib
from _thread import get_ident

from . import format_helpers

Expand Down Expand Up @@ -41,6 +42,16 @@ def format_cb(callback):
return f'cb=[{cb}]'


# bpo-42183: _repr_running is needed for repr protection
# when a Future or Task result contains itself directly or indirectly.
# The logic is borrowed from @reprlib.recursive_repr decorator.
# Unfortunately, the direct decorator usage is impossible because of
# AttributeError: '_asyncio.Task' object has no attribute '__module__' error.
#
# After fixing this thing we can return to the decorator based approach.
_repr_running = set()


def _future_repr_info(future):
# (Future) -> str
"""helper function for Future.__repr__"""
Expand All @@ -49,9 +60,17 @@ def _future_repr_info(future):
if future._exception is not None:
info.append(f'exception={future._exception!r}')
else:
# use reprlib to limit the length of the output, especially
# for very long strings
result = reprlib.repr(future._result)
key = id(future), get_ident()
if key in _repr_running:
result = '...'
else:
_repr_running.add(key)
try:
# use reprlib to limit the length of the output, especially
# for very long strings
result = reprlib.repr(future._result)
finally:
_repr_running.discard(key)
info.append(f'result={result}')
if future._callbacks:
info.append(_format_callbacks(future._callbacks))
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_asyncio/test_futures2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# IsolatedAsyncioTestCase based tests
import asyncio
import unittest


class FutureTests(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
# See Also: https://bugs.python.org/issue42183

async def func():
return asyncio.all_tasks()

# The repr() call should not raise RecursiveError at first.
# The check for returned string is not very reliable but
# exact comparison for the whole string is even weaker.
self.assertIn('...', repr(await asyncio.wait_for(func(), timeout=10)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix a stack overflow error for asyncio Task or Future repr().

The overflow occurs under some circumstances when a Task or Future
recursively returns itself.