@@ -645,6 +645,32 @@ def test_join(self):
645645 with self .assertRaises (TypeError ):
646646 dot_join ([memoryview (b"ab" ), "cd" , b"ef" ])
647647
648+ def test_join_concurrent_buffer_mutation (self ):
649+ # __buffer__() can release the GIL, letting another thread concurrently
650+ # mutate the joined sequence (simulated here by mutating in __buffer__).
651+ # See: https://github.com/python/cpython/issues/151295
652+ def make_seq (mutate ):
653+ # Item is only referenced from the list slot, so mutate() frees it.
654+ class Item :
655+ def __buffer__ (self , flags ):
656+ mutate (seq )
657+ return memoryview (b'x' )
658+ seq = [b'a' , Item (), b'c' ]
659+ return seq
660+
661+ for sep in (self .type2test (b'' ), self .type2test (b'::' )):
662+ with self .subTest (sep = sep ):
663+ # Changing the list length is reported as a RuntimeError.
664+ seq = make_seq (lambda seq : seq .clear ())
665+ self .assertRaises (RuntimeError , sep .join , seq )
666+
667+ # The list length is unchanged, so the size-change recheck
668+ # cannot fire: only keeping the item alive avoids the crash.
669+ def replace (seq ):
670+ seq [1 ] = b'z'
671+ seq = make_seq (replace )
672+ self .assertEqual (sep .join (seq ), sep .join ([b'a' , b'x' , b'c' ]))
673+
648674 def test_count (self ):
649675 b = self .type2test (b'mississippi' )
650676 i = 105
0 commit comments