Skip to content

gh-155606: Increment the managed buffer export count atomically - #155882

Open
ayaangazali wants to merge 1 commit into
python:mainfrom
ayaangazali:gh-155606-memoryview-exports-atomic
Open

gh-155606: Increment the managed buffer export count atomically#155882
ayaangazali wants to merge 1 commit into
python:mainfrom
ayaangazali:gh-155606-memoryview-exports-atomic

Conversation

@ayaangazali

Copy link
Copy Markdown

memoryview slicing registers a new view on the shared _PyManagedBufferObject and bumps its export count. Both registration sites do that with a plain ++:

https://github.com/python/cpython/blob/main/Objects/memoryobject.c#L699-L703

mbuf_add_view() and mbuf_add_incomplete_view() are reached from memory_subscript(), so slicing one shared memoryview from several threads is concurrent unsynchronised read-modify-write on mbuf->exports. Increments get lost, the count ends up lower than the number of live views, and dropping those views walks it through zero early. mbuf_release() then frees the buffer while views are still using it, which is the ValueError: operation forbidden on released memoryview object from gh-155606. On a debug build it trips assert(self->mbuf->exports > 0) in _memory_release() first.

The sibling counter, PyMemoryViewObject.exports, is already atomic via FT_ATOMIC_ADD_SSIZE (added in gh-127085). This does the same for the managed buffer's counter. On default builds the macro expands to a plain +=, so nothing changes there.

Verifying

Free-threaded debug build, --disable-gil --with-pydebug, 8 threads slicing one shared memoryview. I built each combination separately and ran each several times:

mbuf->exports++ fixed #154770 applied result
no no 8/8 threads fail, then abort on the assert
yes no 8/8 threads fail, then abort on the assert
no yes 8/8 threads fail, then abort on the assert
yes yes clean, 0/8, over repeated runs

So this is one of two independent races on the same counter and it is not enough on its own. The decrement side in _memory_release() is already covered by #154770 (open, for gh-127716), and I confirmed that PR does not fix gh-155606 by itself either. No overlapping hunks between the two, they touch different functions. Flagging it because reviewing this diff alone would suggest gh-155606 is closed, and it is not until both land.

The added test covers the increment on its own: slices are created concurrently but only dropped afterwards on a single thread, so the decrement race cannot contribute. It aborts on unpatched main and passes with just this change, 5 runs each. It is skipped on default builds.

Also ran test_memoryview, test_buffer, test_free_threading, test_capi on both a normal and a free-threaded build, plus -R 3:3 on test_memoryview and test_buffer. All clean. pre-commit passes on the touched files.

One thing I did not do: I have no ThreadSanitizer build here, so the race is evidenced by the assert and the failure counts above rather than a TSan report.

apologies if I've missed something obvious in here, I worked through the logic myself and talked the design decisions over with Claude Code as a sanity check. still a freshman in college so I'm sure there's plenty I don't know yet, and I'd genuinely like the correction if I've got it wrong somewhere :)

mbuf_add_view() and mbuf_add_incomplete_view() registered a new view on the
shared _PyManagedBufferObject with a plain mbuf->exports++. On free-threaded
builds concurrent slices of one memoryview lose increments, so the count drops
to zero while views are still alive and mbuf_release() frees the buffer early.

The matching PyMemoryViewObject.exports counter already uses FT_ATOMIC_ADD_SSIZE;
use it here too. On default builds the macro expands to a plain +=, so this is a
no-op there.
Copilot AI lite review requested due to automatic review settings August 16, 2026 04:19
@python-cla-bot

Copy link
Copy Markdown

The following commit authors need to sign the Contributor License Agreement:

CLA not signed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Free threaded: slicing a shared memoryview raises ValueError: operation forbidden on released memoryview object

2 participants