Skip to content

Commit 0273e05

Browse files
authored
ENH: fix thread-unsafe C API usages (numpy#27145)
Ref numpy#26159 See also the CPython HOWTO on this topic: https://docs.python.org/3.13/howto/free-threading-extensions.html#freethreading-extensions-howto. The remaining usages of PyDict_GetItem and PyDict_Next are all around the fields attribute of structured dtypes. I'm pretty sure that dictionary is effectively frozen after the DType is constructed, so I don't worry about those uses. It's not straightforward to write tests for this, I'm just applying static refactorings in places where the refactoring shouldn't introduce new reference counting bugs. * ENH: fix thread-unsafe C API usages * ENH: use critical sections in einsum * BUG: fix error handling in loadtxt C code * revert einsum changes
1 parent fc8a569 commit 0273e05

File tree

2 files changed

+26
-11
lines changed

2 files changed

+26
-11
lines changed

numpy/_core/src/multiarray/array_coercion.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <Python.h>
77

88
#include "numpy/npy_3kcompat.h"
9+
#include "npy_pycompat.h"
910

1011
#include "lowlevel_strided_loops.h"
1112
#include "numpy/arrayobject.h"
@@ -224,24 +225,23 @@ npy_discover_dtype_from_pytype(PyTypeObject *pytype)
224225
PyObject *DType;
225226

226227
if (pytype == &PyArray_Type) {
227-
DType = Py_None;
228+
DType = Py_NewRef(Py_None);
228229
}
229230
else if (pytype == &PyFloat_Type) {
230-
DType = (PyObject *)&PyArray_PyFloatDType;
231+
DType = Py_NewRef((PyObject *)&PyArray_PyFloatDType);
231232
}
232233
else if (pytype == &PyLong_Type) {
233-
DType = (PyObject *)&PyArray_PyLongDType;
234+
DType = Py_NewRef((PyObject *)&PyArray_PyLongDType);
234235
}
235236
else {
236-
DType = PyDict_GetItem(_global_pytype_to_type_dict,
237-
(PyObject *)pytype);
237+
int res = PyDict_GetItemRef(_global_pytype_to_type_dict,
238+
(PyObject *)pytype, (PyObject **)&DType);
238239

239-
if (DType == NULL) {
240-
/* the python type is not known */
240+
if (res <= 0) {
241+
/* the python type is not known or an error was set */
241242
return NULL;
242243
}
243244
}
244-
Py_INCREF(DType);
245245
assert(DType == Py_None || PyObject_TypeCheck(DType, (PyTypeObject *)&PyArrayDTypeMeta_Type));
246246
return (PyArray_DTypeMeta *)DType;
247247
}

numpy/_core/src/multiarray/textreading/rows.c

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,18 @@ create_conv_funcs(
5858

5959
PyObject *key, *value;
6060
Py_ssize_t pos = 0;
61+
int error = 0;
62+
#if Py_GIL_DISABLED
63+
Py_BEGIN_CRITICAL_SECTION(converters);
64+
#endif
6165
while (PyDict_Next(converters, &pos, &key, &value)) {
6266
Py_ssize_t column = PyNumber_AsSsize_t(key, PyExc_IndexError);
6367
if (column == -1 && PyErr_Occurred()) {
6468
PyErr_Format(PyExc_TypeError,
6569
"keys of the converters dictionary must be integers; "
6670
"got %.100R", key);
67-
goto error;
71+
error = 1;
72+
break;
6873
}
6974
if (usecols != NULL) {
7075
/*
@@ -92,7 +97,8 @@ create_conv_funcs(
9297
PyErr_Format(PyExc_ValueError,
9398
"converter specified for column %zd, which is invalid "
9499
"for the number of fields %zd.", column, num_fields);
95-
goto error;
100+
error = 1;
101+
break;
96102
}
97103
if (column < 0) {
98104
column += num_fields;
@@ -102,11 +108,20 @@ create_conv_funcs(
102108
PyErr_Format(PyExc_TypeError,
103109
"values of the converters dictionary must be callable, "
104110
"but the value associated with key %R is not", key);
105-
goto error;
111+
error = 1;
112+
break;
106113
}
107114
Py_INCREF(value);
108115
conv_funcs[column] = value;
109116
}
117+
#if Py_GIL_DISABLED
118+
Py_END_CRITICAL_SECTION();
119+
#endif
120+
121+
if (error) {
122+
goto error;
123+
}
124+
110125
return conv_funcs;
111126

112127
error:

0 commit comments

Comments
 (0)