mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 07:31:38 +00:00 
			
		
		
		
	bpo-42183: Fix a stack overflow error for asyncio Task or Future repr() (GH-23020)
The overflow occurs under some circumstances when a task or future recursively returns itself. Co-authored-by: Kyle Stanley <aeros167@gmail.com>
This commit is contained in:
		
							parent
							
								
									0b9c4c6fcf
								
							
						
					
					
						commit
						42d873c63a
					
				
					 3 changed files with 44 additions and 3 deletions
				
			
		| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
__all__ = ()
 | 
			
		||||
 | 
			
		||||
import reprlib
 | 
			
		||||
from _thread import get_ident
 | 
			
		||||
 | 
			
		||||
from . import format_helpers
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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__"""
 | 
			
		||||
| 
						 | 
				
			
			@ -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))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										18
									
								
								Lib/test/test_asyncio/test_futures2.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Lib/test/test_asyncio/test_futures2.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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)))
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue