From 4e03e6f873953ed5dcb45d0ba6b827e5fea56457 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 15 May 2026 11:32:35 +0300 Subject: [PATCH 1/2] gh-149816: Fix RC in `memoryview` with free-threading --- ...-05-15-11-31-57.gh-issue-149816.ugN2rx.rst | 1 + Objects/memoryobject.c | 39 ++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-15-11-31-57.gh-issue-149816.ugN2rx.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-15-11-31-57.gh-issue-149816.ugN2rx.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-15-11-31-57.gh-issue-149816.ugN2rx.rst new file mode 100644 index 000000000000000..016c17dd66b19ea --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-15-11-31-57.gh-issue-149816.ugN2rx.rst @@ -0,0 +1 @@ +Fix a race condition in :class:`memoryview` with free-threading. diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index c0fd0b8b2f0f538..d0e7291d9501e1d 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -67,6 +67,25 @@ class memoryview "PyMemoryViewObject *" "&PyMemoryView_Type" releasebufferprocs must NOT decrement view.obj. */ +static inline void +exports_increment(PyMemoryViewObject *self) +{ + #ifdef Py_GIL_DISABLED + _Py_atomic_add_ssize(&self->exports, 1); + #else + self->exports++; + #endif +} + +static inline void +exports_decrement(PyMemoryViewObject *self) +{ + #ifdef Py_GIL_DISABLED + _Py_atomic_add_ssize(&self->exports, -1); + #else + self->exports--; + #endif +} static inline _PyManagedBufferObject * mbuf_alloc(void) @@ -1629,11 +1648,7 @@ memory_getbuf(PyObject *_self, Py_buffer *view, int flags) view->obj = Py_NewRef(self); -#ifdef Py_GIL_DISABLED - _Py_atomic_add_ssize(&self->exports, 1); -#else - self->exports++; -#endif + exports_increment(self); return 0; } @@ -1642,11 +1657,7 @@ static void memory_releasebuf(PyObject *_self, Py_buffer *view) { PyMemoryViewObject *self = (PyMemoryViewObject *)_self; -#ifdef Py_GIL_DISABLED - _Py_atomic_add_ssize(&self->exports, -1); -#else - self->exports--; -#endif + exports_decrement(self); return; /* PyBuffer_Release() decrements view->obj after this function returns. */ } @@ -2434,9 +2445,9 @@ memoryview_hex_impl(PyMemoryViewObject *self, PyObject *sep, // Prevent 'self' from being freed if computing len(sep) mutates 'self' // in _Py_strhex_with_sep(). // See: https://github.com/python/cpython/issues/143195. - self->exports++; + exports_increment(self); PyObject *ret = _Py_strhex_with_sep(src->buf, src->len, sep, bytes_per_sep); - self->exports--; + exports_decrement(self); return ret; } @@ -3363,9 +3374,9 @@ memory_hash(PyObject *_self) if (view->obj != NULL) { // Prevent 'self' from being freed when computing the item's hash. // See https://github.com/python/cpython/issues/142664. - self->exports++; + exports_increment(self); Py_hash_t h = PyObject_Hash(view->obj); - self->exports--; + exports_decrement(self); if (h == -1) { /* Keep the original error message */ return -1; From d9d920dfe4c4c93489c358481fa5b4e0c46b7ba9 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 15 May 2026 13:12:55 +0300 Subject: [PATCH 2/2] Address review --- Objects/memoryobject.c | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index d0e7291d9501e1d..ebb3ed7360de682 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -67,25 +67,6 @@ class memoryview "PyMemoryViewObject *" "&PyMemoryView_Type" releasebufferprocs must NOT decrement view.obj. */ -static inline void -exports_increment(PyMemoryViewObject *self) -{ - #ifdef Py_GIL_DISABLED - _Py_atomic_add_ssize(&self->exports, 1); - #else - self->exports++; - #endif -} - -static inline void -exports_decrement(PyMemoryViewObject *self) -{ - #ifdef Py_GIL_DISABLED - _Py_atomic_add_ssize(&self->exports, -1); - #else - self->exports--; - #endif -} static inline _PyManagedBufferObject * mbuf_alloc(void) @@ -1648,7 +1629,7 @@ memory_getbuf(PyObject *_self, Py_buffer *view, int flags) view->obj = Py_NewRef(self); - exports_increment(self); + FT_ATOMIC_ADD_SSIZE(self->exports, 1); return 0; } @@ -1657,7 +1638,7 @@ static void memory_releasebuf(PyObject *_self, Py_buffer *view) { PyMemoryViewObject *self = (PyMemoryViewObject *)_self; - exports_decrement(self); + FT_ATOMIC_ADD_SSIZE(self->exports, -1); return; /* PyBuffer_Release() decrements view->obj after this function returns. */ } @@ -2445,9 +2426,9 @@ memoryview_hex_impl(PyMemoryViewObject *self, PyObject *sep, // Prevent 'self' from being freed if computing len(sep) mutates 'self' // in _Py_strhex_with_sep(). // See: https://github.com/python/cpython/issues/143195. - exports_increment(self); + FT_ATOMIC_ADD_SSIZE(self->exports, 1); PyObject *ret = _Py_strhex_with_sep(src->buf, src->len, sep, bytes_per_sep); - exports_decrement(self); + FT_ATOMIC_ADD_SSIZE(self->exports, -1); return ret; } @@ -3374,9 +3355,9 @@ memory_hash(PyObject *_self) if (view->obj != NULL) { // Prevent 'self' from being freed when computing the item's hash. // See https://github.com/python/cpython/issues/142664. - exports_increment(self); + FT_ATOMIC_ADD_SSIZE(self->exports, 1); Py_hash_t h = PyObject_Hash(view->obj); - exports_decrement(self); + FT_ATOMIC_ADD_SSIZE(self->exports, -1); if (h == -1) { /* Keep the original error message */ return -1;