Skip to content

Commit 013b892

Browse files
committed
pythongh-127020: Make PyCode_GetCode thread-safe for free threading
Some fields in PyCodeObject are lazily initialized. Use atomics and critical sections to make their initialization and access thread-safe.
1 parent 29cbcbd commit 013b892

File tree

2 files changed

+55
-27
lines changed

2 files changed

+55
-27
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a crash in the free threading build when :c:func:`PyCode_GetCode`,
2+
:c:func:`PyCode_GetVarnames`, :c:func:`PyCode_GetCellvars`, or
3+
:c:func:`PyCode_GetFreevars` were called from multiple threads at the same
4+
time. This fixes a crash when running coveragepy in the free threading build.

Objects/codeobject.c

+51-27
Original file line numberDiff line numberDiff line change
@@ -302,21 +302,32 @@ validate_and_copy_tuple(PyObject *tup)
302302
}
303303

304304
static int
305-
init_co_cached(PyCodeObject *self) {
306-
if (self->_co_cached == NULL) {
307-
self->_co_cached = PyMem_New(_PyCoCached, 1);
308-
if (self->_co_cached == NULL) {
305+
init_co_cached(PyCodeObject *self)
306+
{
307+
_PyCoCached *cached = FT_ATOMIC_LOAD_PTR(self->_co_cached);
308+
if (cached != NULL) {
309+
return 0;
310+
}
311+
312+
Py_BEGIN_CRITICAL_SECTION(self);
313+
cached = self->_co_cached;
314+
if (cached == NULL) {
315+
cached = PyMem_New(_PyCoCached, 1);
316+
if (cached == NULL) {
309317
PyErr_NoMemory();
310-
return -1;
311318
}
312-
self->_co_cached->_co_code = NULL;
313-
self->_co_cached->_co_cellvars = NULL;
314-
self->_co_cached->_co_freevars = NULL;
315-
self->_co_cached->_co_varnames = NULL;
319+
else {
320+
cached->_co_code = NULL;
321+
cached->_co_cellvars = NULL;
322+
cached->_co_freevars = NULL;
323+
cached->_co_varnames = NULL;
324+
FT_ATOMIC_STORE_PTR(self->_co_cached, cached);
325+
}
316326
}
317-
return 0;
318-
327+
Py_END_CRITICAL_SECTION();
328+
return cached != NULL ? 0 : -1;
319329
}
330+
320331
/******************
321332
* _PyCode_New()
322333
******************/
@@ -1571,16 +1582,21 @@ get_cached_locals(PyCodeObject *co, PyObject **cached_field,
15711582
{
15721583
assert(cached_field != NULL);
15731584
assert(co->_co_cached != NULL);
1574-
if (*cached_field != NULL) {
1575-
return Py_NewRef(*cached_field);
1585+
PyObject *varnames = FT_ATOMIC_LOAD_PTR(*cached_field);
1586+
if (varnames != NULL) {
1587+
return Py_NewRef(varnames);
15761588
}
1577-
assert(*cached_field == NULL);
1578-
PyObject *varnames = get_localsplus_names(co, kind, num);
1589+
1590+
Py_BEGIN_CRITICAL_SECTION(co);
1591+
varnames = *cached_field;
15791592
if (varnames == NULL) {
1580-
return NULL;
1593+
varnames = get_localsplus_names(co, kind, num);
1594+
if (varnames != NULL) {
1595+
FT_ATOMIC_STORE_PTR(*cached_field, varnames);
1596+
}
15811597
}
1582-
*cached_field = Py_NewRef(varnames);
1583-
return varnames;
1598+
Py_END_CRITICAL_SECTION();
1599+
return Py_XNewRef(varnames);
15841600
}
15851601

15861602
PyObject *
@@ -1674,18 +1690,26 @@ _PyCode_GetCode(PyCodeObject *co)
16741690
if (init_co_cached(co)) {
16751691
return NULL;
16761692
}
1677-
if (co->_co_cached->_co_code != NULL) {
1678-
return Py_NewRef(co->_co_cached->_co_code);
1693+
1694+
_PyCoCached *cached = co->_co_cached;
1695+
PyObject *code = FT_ATOMIC_LOAD_PTR(cached->_co_code);
1696+
if (code != NULL) {
1697+
return Py_NewRef(code);
16791698
}
1680-
PyObject *code = PyBytes_FromStringAndSize((const char *)_PyCode_CODE(co),
1681-
_PyCode_NBYTES(co));
1699+
1700+
Py_BEGIN_CRITICAL_SECTION(co);
1701+
code = cached->_co_code;
16821702
if (code == NULL) {
1683-
return NULL;
1703+
code = PyBytes_FromStringAndSize((const char *)_PyCode_CODE(co),
1704+
_PyCode_NBYTES(co));
1705+
if (code != NULL) {
1706+
deopt_code(co, (_Py_CODEUNIT *)PyBytes_AS_STRING(code));
1707+
assert(cached->_co_code == NULL);
1708+
FT_ATOMIC_STORE_PTR(cached->_co_code, code);
1709+
}
16841710
}
1685-
deopt_code(co, (_Py_CODEUNIT *)PyBytes_AS_STRING(code));
1686-
assert(co->_co_cached->_co_code == NULL);
1687-
co->_co_cached->_co_code = Py_NewRef(code);
1688-
return code;
1711+
Py_END_CRITICAL_SECTION();
1712+
return Py_XNewRef(code);
16891713
}
16901714

16911715
PyObject *

0 commit comments

Comments
 (0)