GeezerVison is showing github.com — make memoryview thread safe in free-threading · Issue #127716 · python/cpython
make memoryview thread safe in free-threading #127716Descriptionmemoryview is currently not safe against free-threading especially concurrent releasing the memoryviews. This issue track work to make it thread safe.
This is a follow-up to #127412 (review).
Linked PRs
Activitypicnixz commented on Dec 7, 2024 Do you think it should be a feature (main only) or a bug fix (3.13+)? kumaraditya303 commented on Dec 7, 2024 ContributorAuthor More actions For now I'll target main, if it doesn't need too many changes it may be backported. picnixz commented on Dec 7, 2024 Ok. I'll apply the labels after we decided whether we should backport it or not. ZeroIntensity commented on Dec 7, 2024 FWIW, we typically do backport thread safety fixes to 3.13. We should probably merge gh-127412 before putting up anything new to prevent conflicts. changed the title [-]make memoryview thread safe in free-theading[/-] [+]make memoryview thread safe in free-threading[/+] on Dec 13, 2024devdanzin commented on Jul 25, 2026 A concrete, crashing reproducer for the "concurrent releasing" part of this issue. On a free-threaded build, several threads calling memoryview.release() on views of one shared buffer (interleaved with reads of a view) underflow the non-atomic export counters and abort.
import threading
buf = bytearray(64)
cell = [memoryview(buf)]
NT = 8
ITERS = 40000
def worker(w):
for _ in range(ITERS):
mv = cell[0]
try:
if w % 2 == 0:
mv.release()
cell[0] = memoryview(buf)
else:
_ = mv.readonly
_ = mv[0]
except Exception:
pass
ts = [threading.Thread(target=worker, args=(w,)) for w in range(NT)]
for t in ts: t.start()
for t in ts: t.join()
On a free-threaded debug build (PYTHON_GIL=0) this aborts on essentially every run. The exact assertion that fires depends on the build/scheduling — both are the same root cause, a non-atomic export counter driven below its invariant by concurrent release():
-
Debug build with ASan — the memoryview's own mbuf->exports:
Objects/memoryobject.c:1116: void _memory_release(PyMemoryViewObject *):
Assertion `self->mbuf->exports > 0' failed.
_memory_release (memoryobject.c:1115) decrements self->mbuf->exports non-atomically, so
concurrent releases drive it to/below zero.
-
Plain debug build (no ASan) — the exporter bytearray's ob_exports:
Objects/bytearrayobject.c:89: void bytearray_releasebuffer(PyObject *, Py_buffer *):
Assertion `obj->ob_exports >= 0' failed.
the release path double-decrements the exporter's export count (also non-atomic), so it goes negative.
Both reproduce 5/5 here. Under TSan the same race shows up as data-race stanzas pairing the release side against the read side, e.g. _memory_release / mbuf_release vs memory_readonly_get / memory_item / memory_subscript — and the read of a concurrently-released view is also a use-after-release angle.
This is the release side of the exports counter, which looks still-open under this issue — distinct from the memoryview.hex exports race that was fixed (gh-149858 / item 132 of #149816) and the earlier ->exports fix in gh-127085 (#127412) that this issue is the follow-up to.
Found with fusil's --tsan mode; reduced and drafted with AI assistance (Claude Code), verified by hand on free-threaded debug / ASan / TSan builds of main (3.16.0a0). weixlu commented on Jul 27, 2026 @devdanzin Hi, I submitted a PR #154770 trying to fix the issue you mentioned. I think self->flags need to be protected by atomic operation, then only one thread would enter the release path
MetadataMetadataStatusShow more project fields
DevelopmentNo branches or pull requestsIssue actions
|