Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions Lib/test/test_free_threading/test_memoryview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import threading
import unittest

from test.support import threading_helper


@threading_helper.requires_working_threading()
class TestMemoryViewSliceRace(unittest.TestCase):
def test_concurrent_slicing_keeps_export_count(self):
# gh-155606: slicing registers a new view on the shared managed buffer,
# and mbuf_add_view() bumped that buffer's export count with a plain
# ++. Concurrent slices of a single memoryview therefore lost
# increments, the count reached zero while views were still alive, and
# the underlying buffer was released early.
#
# The slices are created concurrently but only dropped afterwards, on
# one thread, so this covers the increment on its own.
mv = memoryview(bytes(2 ** 16))
slices = []
lock = threading.Lock()

def make_slices():
local = [mv[0:64] for _ in range(2000)]
with lock:
slices.extend(local)

threading_helper.run_concurrently(make_slices, nthreads=8)
del slices

# An early release makes this raise "operation forbidden on released
# memoryview object".
self.assertEqual(bytes(mv[0:4]), b"\x00" * 4)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fix a data race on free-threaded builds where slicing a shared
:class:`memoryview` from several threads could release the underlying buffer
while views were still alive, raising ``ValueError: operation forbidden on
released memoryview object``. The managed buffer's export count is now
incremented atomically.
4 changes: 2 additions & 2 deletions Objects/memoryobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ mbuf_add_view(_PyManagedBufferObject *mbuf, const Py_buffer *src)
init_flags(mv);

mv->mbuf = (_PyManagedBufferObject*)Py_NewRef(mbuf);
mbuf->exports++;
FT_ATOMIC_ADD_SSIZE(mbuf->exports, 1);

return (PyObject *)mv;
}
Expand Down Expand Up @@ -729,7 +729,7 @@ mbuf_add_incomplete_view(_PyManagedBufferObject *mbuf, const Py_buffer *src,
init_shared_values(dest, src);

mv->mbuf = (_PyManagedBufferObject*)Py_NewRef(mbuf);
mbuf->exports++;
FT_ATOMIC_ADD_SSIZE(mbuf->exports, 1);

return (PyObject *)mv;
}
Expand Down
Loading