@@ -580,6 +580,32 @@ def test_join(self):
580580 with self .assertRaises (TypeError ):
581581 dot_join ([memoryview (b"ab" ), "cd" , b"ef" ])
582582
583+ def test_join_concurrent_buffer_mutation (self ):
584+ # __buffer__() can release the GIL, letting another thread concurrently
585+ # mutate the joined sequence (simulated here by mutating in __buffer__).
586+ # See: https://github.com/python/cpython/issues/151295
587+ def make_seq (mutate ):
588+ # Item is only referenced from the list slot, so mutate() frees it.
589+ class Item :
590+ def __buffer__ (self , flags ):
591+ mutate (seq )
592+ return memoryview (b'x' )
593+ seq = [b'a' , Item (), b'c' ]
594+ return seq
595+
596+ for sep in (self .type2test (b'' ), self .type2test (b'::' )):
597+ with self .subTest (sep = sep ):
598+ # Changing the list length is reported as a RuntimeError.
599+ seq = make_seq (lambda seq : seq .clear ())
600+ self .assertRaises (RuntimeError , sep .join , seq )
601+
602+ # The list length is unchanged, so the size-change recheck
603+ # cannot fire: only keeping the item alive avoids the crash.
604+ def replace (seq ):
605+ seq [1 ] = b'z'
606+ seq = make_seq (replace )
607+ self .assertEqual (sep .join (seq ), sep .join ([b'a' , b'x' , b'c' ]))
608+
583609 def test_count (self ):
584610 b = self .type2test (b'mississippi' )
585611 i = 105
0 commit comments