Skip to content

Commit 06a9d42

Browse files
authored
Merge pull request #30420 from charris/backport-30418
BUG: fix double evaluation in PyArrayScalar_RETURN_BOOL_FROM_LONG (#30418)
2 parents 30819cd + c364639 commit 06a9d42

2 files changed

Lines changed: 56 additions & 3 deletions

File tree

numpy/_core/include/numpy/arrayscalars.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,11 @@ typedef struct {
173173
#define PyArrayScalar_True ((PyObject *)(&(_PyArrayScalar_BoolValues[1])))
174174
#define PyArrayScalar_FromLong(i) \
175175
((PyObject *)(&(_PyArrayScalar_BoolValues[((i)!=0)])))
176-
#define PyArrayScalar_RETURN_BOOL_FROM_LONG(i) \
177-
return Py_INCREF(PyArrayScalar_FromLong(i)), \
178-
PyArrayScalar_FromLong(i)
176+
#define PyArrayScalar_RETURN_BOOL_FROM_LONG(i) do { \
177+
PyObject *obj = PyArrayScalar_FromLong(i); \
178+
Py_INCREF(obj); \
179+
return obj; \
180+
} while (0)
179181
#define PyArrayScalar_RETURN_FALSE \
180182
return Py_INCREF(PyArrayScalar_False), \
181183
PyArrayScalar_False
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import pytest
2+
3+
import numpy as np
4+
from numpy.testing import IS_WASM
5+
6+
pytestmark = pytest.mark.thread_unsafe(
7+
reason="tests in this module are explicitly multi-processed"
8+
)
9+
10+
def bool_array_writer(shm_name, n):
11+
# writer routine for test_read_write_bool_array
12+
import time
13+
from multiprocessing import shared_memory
14+
shm = shared_memory.SharedMemory(name=shm_name)
15+
arr = np.ndarray(n, dtype=np.bool_, buffer=shm.buf)
16+
for i in range(n):
17+
arr[i] = True
18+
time.sleep(0.00001)
19+
20+
def bool_array_reader(shm_name, n):
21+
# reader routine for test_read_write_bool_array
22+
from multiprocessing import shared_memory
23+
shm = shared_memory.SharedMemory(name=shm_name)
24+
arr = np.ndarray(n, dtype=np.bool_, buffer=shm.buf)
25+
for i in range(n):
26+
while not arr[i]:
27+
pass
28+
29+
@pytest.mark.skipif(IS_WASM,
30+
reason="WASM does not support _posixshmem")
31+
def test_read_write_bool_array():
32+
# See: gh-30389
33+
#
34+
# Prior to Python 3.13, boolean scalar singletons (np.True / np.False) were
35+
# regular reference-counted objects. Due to the double evaluation in
36+
# PyArrayScalar_RETURN_BOOL_FROM_LONG, concurrent reads and writes of a
37+
# boolean array could corrupt their refcounts, potentially causing a crash
38+
# (e.g., `free(): invalid pointer`).
39+
#
40+
# This test creates a multi-process race between a writer and a reader to
41+
# ensure that NumPy does not exhibit such failures.
42+
from concurrent.futures import ProcessPoolExecutor
43+
from multiprocessing import shared_memory
44+
n = 10000
45+
shm = shared_memory.SharedMemory(create=True, size=n)
46+
with ProcessPoolExecutor(max_workers=2) as executor:
47+
f_writer = executor.submit(bool_array_writer, shm.name, n)
48+
f_reader = executor.submit(bool_array_reader, shm.name, n)
49+
shm.unlink()
50+
f_writer.result()
51+
f_reader.result()

0 commit comments

Comments
 (0)