1111import weakref
1212from collections import UserList
1313from test import support
14- from test .support import set_recursion_limit
1514from test .support import os_helper , threading_helper
1615from test .support .script_helper import assert_python_ok
1716from .utils import CTestCase , PyTestCase
@@ -1563,21 +1562,35 @@ def closed(self):
15631562
15641563 def test_reentrant_detach_during_flush (self ):
15651564 # gh-143008: Reentrant detach() during flush should not crash.
1566- wrapper = None
15671565
1568- class DetachOnFlush (self .BufferedRandom ):
1566+ class DetachOnce (self .BufferedRandom ):
1567+ wrapper = None
1568+
1569+ def detach_once (self ):
1570+ original = self .wrapper
1571+ self .wrapper = None
1572+ if original is not None :
1573+ original .detach ()
1574+ original .flush ()
1575+
1576+ class DetachOnFlush (DetachOnce ):
15691577 def flush (self ):
1570- if wrapper is not None :
1571- wrapper .detach ()
1572- return super ().flush ()
1578+ self .detach_once ()
15731579
1574- class DetachOnWrite (self . BufferedRandom ):
1580+ class DetachOnWrite (DetachOnce ):
15751581 def write (self , b ):
1576- if wrapper is not None :
1577- wrapper .detach ()
1582+ self .detach_once ()
15781583 return len (b )
15791584
1580- # Used to crash; now will run out of stack.
1585+ # Separate reference for after detach_once.
1586+ wrapper = None
1587+
1588+ def make_text (buffer ):
1589+ nonlocal wrapper
1590+ buffer .wrapper = self .TextIOWrapper (buffer , encoding = 'utf-8' )
1591+ wrapper = buffer .wrapper
1592+
1593+ # Many calls could result in the same null self->buffer crash.
15811594 tests = [
15821595 ('truncate' , lambda : wrapper .truncate (0 )),
15831596 ('close' , lambda : wrapper .close ()),
@@ -1586,26 +1599,16 @@ def write(self, b):
15861599 ('tell' , lambda : wrapper .tell ()),
15871600 ('reconfigure' , lambda : wrapper .reconfigure (line_buffering = True )),
15881601 ]
1589- for name , call in tests :
1590- with self .subTest (name ), set_recursion_limit (100 ):
1591- buffer = DetachOnFlush (self .MockRawIO ())
1592- wrapper = self .TextIOWrapper (buffer , encoding = 'utf-8' )
1593- try :
1594- self .assertRaises (RecursionError , call )
1595- finally :
1596- # Disarm before GC so finalization succeeds.
1597- wrapper = None
1602+ for name , method in tests :
1603+ with self .subTest (name ):
1604+ make_text (DetachOnFlush (self .MockRawIO ()))
1605+ self .assertRaisesRegex (ValueError , "detached" , method )
15981606
1599- # Used to crash.
1607+ # Should not crash.
16001608 with self .subTest ('read via writeflush' ):
1601- buffer = DetachOnWrite (self .MockRawIO ())
1602- wrapper = self .TextIOWrapper (buffer , encoding = 'utf-8' )
1603- try :
1604- wrapper .write ('x' )
1605- self .assertRaisesRegex (ValueError , "detached" , wrapper .read )
1606- finally :
1607- # Disarm before GC so finalization succeeds.
1608- wrapper = None
1609+ make_text (DetachOnWrite (self .MockRawIO ()))
1610+ wrapper .write ('x' )
1611+ self .assertRaisesRegex (ValueError , "detached" , wrapper .read )
16091612
16101613
16111614class PyTextIOWrapperTest (TextIOWrapperTest , PyTestCase ):
0 commit comments