Skip to content

Commit 0cd1541

Browse files
authored
[mypyc] Make function wrappers thread-safe on free-threaded builds (#21620)
The implementation had multiple race conditions. I used coding agent assist.
1 parent 22a9cfd commit 0cd1541

1 file changed

Lines changed: 39 additions & 6 deletions

File tree

mypyc/lib-rt/function_wrapper.c

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ static void CPyFunction_dealloc(CPyFunction *m) {
2929
}
3030

3131
static PyObject* CPyFunction_repr(CPyFunction *op) {
32-
return PyUnicode_FromFormat("<function %U at %p>", op->func_name, (void *)op);
32+
// Use helper to get name for free threading safety.
33+
PyObject *name = CPyFunction_get_name((PyObject *)op, NULL);
34+
if (unlikely(name == NULL)) {
35+
return NULL;
36+
}
37+
PyObject *result = PyUnicode_FromFormat("<function %U at %p>", name, (void *)op);
38+
Py_DECREF(name);
39+
return result;
3340
}
3441

3542
static PyObject* CPyFunction_call(PyObject *func, PyObject *args, PyObject *kw) {
@@ -59,13 +66,15 @@ static PyMemberDef CPyFunction_members[] = {
5966
PyObject* CPyFunction_get_name(PyObject *op, void *context) {
6067
(void)context;
6168
CPyFunction *func = (CPyFunction *)op;
69+
PyObject *result;
70+
Py_BEGIN_CRITICAL_SECTION(op);
6271
if (unlikely(func->func_name == NULL)) {
6372
func->func_name = PyUnicode_InternFromString(((PyCFunctionObject *)func)->m_ml->ml_name);
64-
if (unlikely(func->func_name == NULL))
65-
return NULL;
6673
}
67-
Py_INCREF(func->func_name);
68-
return func->func_name;
74+
result = func->func_name;
75+
Py_XINCREF(result);
76+
Py_END_CRITICAL_SECTION();
77+
return result;
6978
}
7079

7180
int CPyFunction_set_name(PyObject *op, PyObject *value, void *context) {
@@ -77,8 +86,13 @@ int CPyFunction_set_name(PyObject *op, PyObject *value, void *context) {
7786
}
7887

7988
Py_INCREF(value);
80-
Py_XDECREF(func->func_name);
89+
// Decref outside critical section, since it could run arbitrary code.
90+
PyObject *old;
91+
Py_BEGIN_CRITICAL_SECTION(op);
92+
old = func->func_name;
8193
func->func_name = value;
94+
Py_END_CRITICAL_SECTION();
95+
Py_XDECREF(old);
8296
return 0;
8397
}
8498

@@ -232,12 +246,31 @@ PyObject* CPyFunction_New(PyObject *module, const char *filename, const char *fu
232246
PyObject *code = NULL, *op = NULL;
233247
bool set_self = false;
234248

249+
#ifdef Py_GIL_DISABLED
250+
// Double-checked locking: the common case (type already created) is a
251+
// lock-free atomic load. Only the first-time initialization takes the
252+
// mutex, which serializes concurrent creators.
253+
if (!_Py_atomic_load_ptr_acquire(&CPyFunctionType)) {
254+
static PyMutex type_init_mutex = {0};
255+
PyMutex_Lock(&type_init_mutex);
256+
if (!CPyFunctionType) {
257+
PyTypeObject *type = (PyTypeObject *)PyType_FromSpec(&CPyFunction_spec);
258+
if (unlikely(!type)) {
259+
PyMutex_Unlock(&type_init_mutex);
260+
goto err;
261+
}
262+
_Py_atomic_store_ptr_release(&CPyFunctionType, type);
263+
}
264+
PyMutex_Unlock(&type_init_mutex);
265+
}
266+
#else
235267
if (!CPyFunctionType) {
236268
CPyFunctionType = (PyTypeObject *)PyType_FromSpec(&CPyFunction_spec);
237269
if (unlikely(!CPyFunctionType)) {
238270
goto err;
239271
}
240272
}
273+
#endif
241274

242275
method = CPyMethodDef_New(funcname, func, func_flags, func_doc);
243276
if (unlikely(!method)) {

0 commit comments

Comments
 (0)