@@ -168,6 +168,22 @@ class PyMisbehavedRawIO(MisbehavedRawIO, pyio.RawIOBase):
168168 pass
169169
170170
171+ class SlowFlushRawIO (MockRawIO ):
172+ def __init__ (self ):
173+ super ().__init__ ()
174+ self .in_flush = threading .Event ()
175+
176+ def flush (self ):
177+ self .in_flush .set ()
178+ time .sleep (0.25 )
179+
180+ class CSlowFlushRawIO (SlowFlushRawIO , io .RawIOBase ):
181+ pass
182+
183+ class PySlowFlushRawIO (SlowFlushRawIO , pyio .RawIOBase ):
184+ pass
185+
186+
171187class CloseFailureIO (MockRawIO ):
172188 closed = 0
173189
@@ -1726,6 +1742,18 @@ def bad_write(b):
17261742 self .assertRaises (OSError , b .close ) # exception not swallowed
17271743 self .assertTrue (b .closed )
17281744
1745+ def test_slow_close_from_thread (self ):
1746+ # Issue #31976
1747+ rawio = self .SlowFlushRawIO ()
1748+ bufio = self .tp (rawio , 8 )
1749+ t = threading .Thread (target = bufio .close )
1750+ t .start ()
1751+ rawio .in_flush .wait ()
1752+ self .assertRaises (ValueError , bufio .write , b'spam' )
1753+ self .assertTrue (bufio .closed )
1754+ t .join ()
1755+
1756+
17291757
17301758class CBufferedWriterTest (BufferedWriterTest , SizeofTest ):
17311759 tp = io .BufferedWriter
@@ -4085,7 +4113,8 @@ def load_tests(*args):
40854113 # Put the namespaces of the IO module we are testing and some useful mock
40864114 # classes in the __dict__ of each test.
40874115 mocks = (MockRawIO , MisbehavedRawIO , MockFileIO , CloseFailureIO ,
4088- MockNonBlockWriterIO , MockUnseekableIO , MockRawIOWithoutRead )
4116+ MockNonBlockWriterIO , MockUnseekableIO , MockRawIOWithoutRead ,
4117+ SlowFlushRawIO )
40894118 all_members = io .__all__ + ["IncrementalNewlineDecoder" ]
40904119 c_io_ns = {name : getattr (io , name ) for name in all_members }
40914120 py_io_ns = {name : getattr (pyio , name ) for name in all_members }
0 commit comments