From 78bd5dd570b781f81f94f294b749ac425d0b270d Mon Sep 17 00:00:00 2001 From: edward_xu Date: Fri, 18 Sep 2026 10:15:56 +0800 Subject: [PATCH 1/2] fix array `ob_exports` racing in free threading --- Lib/test/test_free_threading/test_array.py | 19 +++++++++++++++++++ Modules/arraymodule.c | 20 +++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 Lib/test/test_free_threading/test_array.py diff --git a/Lib/test/test_free_threading/test_array.py b/Lib/test/test_free_threading/test_array.py new file mode 100644 index 000000000000000..7d2aa812e576ef1 --- /dev/null +++ b/Lib/test/test_free_threading/test_array.py @@ -0,0 +1,19 @@ +from array import array +import unittest + +from test.support import threading_helper + +threading_helper.requires_working_threading(module=True) + +class TestArray(unittest.TestCase): + def test_array_export_race(self): + arr = array('i', [1, 2, 3, 4, 5]) + + ITER_TIMES = 1_000 + THREAD_NUMS = 4 + def incre_export(arr: array): + for _ in range(ITER_TIMES): + view = memoryview(arr) + view.release() + + threading_helper.run_concurrently(incre_export, THREAD_NUMS, (arr,)) diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index a0181c083a60369..60ccbbfcd6855e2 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -137,13 +137,23 @@ enum machine_format_code { #define array_Check(op, state) PyObject_TypeCheck(op, state->ArrayType) +#if defined(Py_GIL_DISABLED) +#define array_ObExports(obj) _Py_atomic_load_ssize(&(obj)->ob_exports) +#define array_ObExportsInc(obj) _Py_atomic_add_ssize(&(obj)->ob_exports, 1) +#define array_ObExportsDec(obj) _Py_atomic_add_ssize(&(obj)->ob_exports, -1) +#else +#define array_ObExports(obj) (obj)->ob_exports +#define array_ObExportsInc(obj) (obj)->ob_exports++ +#define array_ObExportsDec(obj) (obj)->ob_exports-- +#endif + static int array_resize(arrayobject *self, Py_ssize_t newsize) { char *items; size_t _new_size; - if (self->ob_exports > 0 && newsize != Py_SIZE(self)) { + if (array_ObExports(self) > 0 && newsize != Py_SIZE(self)) { PyErr_SetString(PyExc_BufferError, "cannot resize an array that is exporting buffers"); return -1; @@ -1145,7 +1155,7 @@ array_del_slice(arrayobject *a, Py_ssize_t ilow, Py_ssize_t ihigh) /* Issue #4509: If the array has exported buffers and the slice assignment would change the size of the array, fail early to make sure we don't modify it. */ - if (d != 0 && a->ob_exports > 0) { + if (d != 0 && array_ObExports(a) > 0) { PyErr_SetString(PyExc_BufferError, "cannot resize an array that is exporting buffers"); return -1; @@ -2796,7 +2806,7 @@ array_ass_subscr(PyObject *op, PyObject *item, PyObject *value) /* Issue #4509: If the array has exported buffers and the slice assignment would change the size of the array, fail early to make sure we don't modify it. */ - if ((needed == 0 || slicelength != needed) && self->ob_exports > 0) { + if ((needed == 0 || slicelength != needed) && array_ObExports(self) > 0) { PyErr_SetString(PyExc_BufferError, "cannot resize an array that is exporting buffers"); return -1; @@ -2910,7 +2920,7 @@ array_buffer_getbuf(PyObject *op, Py_buffer *view, int flags) view->format = (char *)self->ob_descr->typecode; } - self->ob_exports++; + array_ObExportsInc(self); return 0; } @@ -2918,7 +2928,7 @@ static void array_buffer_relbuf(PyObject *op, Py_buffer *Py_UNUSED(view)) { arrayobject *self = arrayobject_CAST(op); - self->ob_exports--; + array_ObExportsDec(self); } static PyObject * From 4046984e9e0fcccf19ec576f98f440d1b86253d8 Mon Sep 17 00:00:00 2001 From: edward_xu Date: Fri, 18 Sep 2026 22:59:40 +0800 Subject: [PATCH 2/2] add blurb --- .../next/Library/2026-09-18-22-56-45.gh-issue-154524.qbkTRn.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-09-18-22-56-45.gh-issue-154524.qbkTRn.rst diff --git a/Misc/NEWS.d/next/Library/2026-09-18-22-56-45.gh-issue-154524.qbkTRn.rst b/Misc/NEWS.d/next/Library/2026-09-18-22-56-45.gh-issue-154524.qbkTRn.rst new file mode 100644 index 000000000000000..131a4ef5fd75a01 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-18-22-56-45.gh-issue-154524.qbkTRn.rst @@ -0,0 +1,2 @@ +Fix a data race in the :class:`array.array` buffer export counter when buffers +are acquired and released concurrently in free-threaded builds.