diff --git a/numpy/_core/src/multiarray/array_coercion.c b/numpy/_core/src/multiarray/array_coercion.c index 1b36deca95c2..51aa874bf934 100644 --- a/numpy/_core/src/multiarray/array_coercion.c +++ b/numpy/_core/src/multiarray/array_coercion.c @@ -615,10 +615,13 @@ update_shape(int curr_ndim, int *max_ndim, return success; } - +#ifndef Py_GIL_DISABLED #define COERCION_CACHE_CACHE_SIZE 5 static int _coercion_cache_num = 0; static coercion_cache_obj *_coercion_cache_cache[COERCION_CACHE_CACHE_SIZE]; +#else +#define COERCION_CACHE_CACHE_SIZE 0 +#endif /* * Steals a reference to the object. @@ -629,11 +632,14 @@ npy_new_coercion_cache( coercion_cache_obj ***next_ptr, int ndim) { coercion_cache_obj *cache; +#if COERCION_CACHE_CACHE_SIZE > 0 if (_coercion_cache_num > 0) { _coercion_cache_num--; cache = _coercion_cache_cache[_coercion_cache_num]; } - else { + else +#endif + { cache = PyMem_Malloc(sizeof(coercion_cache_obj)); } if (cache == NULL) { @@ -662,11 +668,14 @@ npy_unlink_coercion_cache(coercion_cache_obj *current) { coercion_cache_obj *next = current->next; Py_DECREF(current->arr_or_sequence); +#if COERCION_CACHE_CACHE_SIZE > 0 if (_coercion_cache_num < COERCION_CACHE_CACHE_SIZE) { _coercion_cache_cache[_coercion_cache_num] = current; _coercion_cache_num++; } - else { + else +#endif + { PyMem_Free(current); } return next; diff --git a/numpy/_core/tests/test_multithreading.py b/numpy/_core/tests/test_multithreading.py new file mode 100644 index 000000000000..8999a18a39ff --- /dev/null +++ b/numpy/_core/tests/test_multithreading.py @@ -0,0 +1,21 @@ +import concurrent.futures + +import numpy as np +import pytest + +from numpy.testing import IS_WASM + +if IS_WASM: + pytest.skip(allow_module_level=True, reason="no threading support in wasm") + + +def test_parallel_errstate_creation(): + # if the coercion cache is enabled and not thread-safe, creating + # RandomState instances simultaneously leads to a data race + def func(seed): + np.random.RandomState(seed) + + with concurrent.futures.ThreadPoolExecutor(max_workers=8) as tpe: + futures = [tpe.submit(func, i) for i in range(500)] + for f in futures: + f.result()