@@ -614,6 +614,32 @@ def test_join(self):
614614 with self .assertRaises (TypeError ):
615615 dot_join ([memoryview (b"ab" ), "cd" , b"ef" ])
616616
617+ def test_join_concurrent_buffer_mutation (self ):
618+ # __buffer__() can release the GIL, letting another thread concurrently
619+ # mutate the joined sequence (simulated here by mutating in __buffer__).
620+ # See: https://github.com/python/cpython/issues/151295
621+ def make_seq (mutate ):
622+ # Item is only referenced from the list slot, so mutate() frees it.
623+ class Item :
624+ def __buffer__ (self , flags ):
625+ mutate (seq )
626+ return memoryview (b'x' )
627+ seq = [b'a' , Item (), b'c' ]
628+ return seq
629+
630+ for sep in (self .type2test (b'' ), self .type2test (b'::' )):
631+ with self .subTest (sep = sep ):
632+ # Changing the list length is reported as a RuntimeError.
633+ seq = make_seq (lambda seq : seq .clear ())
634+ self .assertRaises (RuntimeError , sep .join , seq )
635+
636+ # The list length is unchanged, so the size-change recheck
637+ # cannot fire: only keeping the item alive avoids the crash.
638+ def replace (seq ):
639+ seq [1 ] = b'z'
640+ seq = make_seq (replace )
641+ self .assertEqual (sep .join (seq ), sep .join ([b'a' , b'x' , b'c' ]))
642+
617643 def test_count (self ):
618644 b = self .type2test (b'mississippi' )
619645 i = 105
0 commit comments