BUG: fix double evaluation in PyArrayScalar_RETURN_BOOL_FROM_LONG (#30418)
* BUG: fix double evaluation in PyArrayScalar_RETURN_BOOL_FROM_LONG
Closes #30389
* fix: lint errors and wasm import problems
diff --git a/numpy/_core/include/numpy/arrayscalars.h b/numpy/_core/include/numpy/arrayscalars.h
index ff04806..46bc58c 100644
--- a/numpy/_core/include/numpy/arrayscalars.h
+++ b/numpy/_core/include/numpy/arrayscalars.h
@@ -173,9 +173,11 @@
#define PyArrayScalar_True ((PyObject *)(&(_PyArrayScalar_BoolValues[1])))
#define PyArrayScalar_FromLong(i) \
((PyObject *)(&(_PyArrayScalar_BoolValues[((i)!=0)])))
-#define PyArrayScalar_RETURN_BOOL_FROM_LONG(i) \
- return Py_INCREF(PyArrayScalar_FromLong(i)), \
- PyArrayScalar_FromLong(i)
+#define PyArrayScalar_RETURN_BOOL_FROM_LONG(i) do { \
+ PyObject *obj = PyArrayScalar_FromLong(i); \
+ Py_INCREF(obj); \
+ return obj; \
+} while (0)
#define PyArrayScalar_RETURN_FALSE \
return Py_INCREF(PyArrayScalar_False), \
PyArrayScalar_False
diff --git a/numpy/_core/tests/test_multiprocessing.py b/numpy/_core/tests/test_multiprocessing.py
new file mode 100644
index 0000000..2c5c2fc
--- /dev/null
+++ b/numpy/_core/tests/test_multiprocessing.py
@@ -0,0 +1,51 @@
+import pytest
+
+import numpy as np
+from numpy.testing import IS_WASM
+
+pytestmark = pytest.mark.thread_unsafe(
+ reason="tests in this module are explicitly multi-processed"
+)
+
+def bool_array_writer(shm_name, n):
+ # writer routine for test_read_write_bool_array
+ import time
+ from multiprocessing import shared_memory
+ shm = shared_memory.SharedMemory(name=shm_name)
+ arr = np.ndarray(n, dtype=np.bool_, buffer=shm.buf)
+ for i in range(n):
+ arr[i] = True
+ time.sleep(0.00001)
+
+def bool_array_reader(shm_name, n):
+ # reader routine for test_read_write_bool_array
+ from multiprocessing import shared_memory
+ shm = shared_memory.SharedMemory(name=shm_name)
+ arr = np.ndarray(n, dtype=np.bool_, buffer=shm.buf)
+ for i in range(n):
+ while not arr[i]:
+ pass
+
+@pytest.mark.skipif(IS_WASM,
+ reason="WASM does not support _posixshmem")
+def test_read_write_bool_array():
+ # See: gh-30389
+ #
+ # Prior to Python 3.13, boolean scalar singletons (np.True / np.False) were
+ # regular reference-counted objects. Due to the double evaluation in
+ # PyArrayScalar_RETURN_BOOL_FROM_LONG, concurrent reads and writes of a
+ # boolean array could corrupt their refcounts, potentially causing a crash
+ # (e.g., `free(): invalid pointer`).
+ #
+ # This test creates a multi-process race between a writer and a reader to
+ # ensure that NumPy does not exhibit such failures.
+ from concurrent.futures import ProcessPoolExecutor
+ from multiprocessing import shared_memory
+ n = 10000
+ shm = shared_memory.SharedMemory(create=True, size=n)
+ with ProcessPoolExecutor(max_workers=2) as executor:
+ f_writer = executor.submit(bool_array_writer, shm.name, n)
+ f_reader = executor.submit(bool_array_reader, shm.name, n)
+ shm.unlink()
+ f_writer.result()
+ f_reader.result()