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
19 changes: 19 additions & 0 deletions Lib/test/test_free_threading/test_array.py
Original file line number Diff line number Diff line change
@@ -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,))
Original file line number Diff line number Diff line change
@@ -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.
20 changes: 15 additions & 5 deletions Modules/arraymodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1154,7 +1164,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;
Expand Down Expand Up @@ -2805,7 +2815,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;
Expand Down Expand Up @@ -2919,15 +2929,15 @@ 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;
}

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 *
Expand Down
Loading