[3.13] gh-127020: Make PyCode_GetCode thread-safe for free threading (GH-127043) (GH-127107)

Some fields in PyCodeObject are lazily initialized. Use atomics and
critical sections to make their initializations and accesses thread-safe.
(cherry picked from commit 3926842117)

Co-authored-by: Sam Gross <colesbury@gmail.com>
This commit is contained in:
Miss Islington (bot) 2024-11-21 17:27:36 +01:00 committed by GitHub
parent c09366b1fe
commit c74331413e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 86 additions and 28 deletions

View file

@ -0,0 +1,30 @@
import unittest
from threading import Thread
from unittest import TestCase
from test.support import threading_helper
@threading_helper.requires_working_threading()
class TestCode(TestCase):
def test_code_attrs(self):
"""Test concurrent accesses to lazily initialized code attributes"""
code_objects = []
for _ in range(1000):
code_objects.append(compile("a + b", "<string>", "eval"))
def run_in_thread():
for code in code_objects:
self.assertIsInstance(code.co_code, bytes)
self.assertIsInstance(code.co_freevars, tuple)
self.assertIsInstance(code.co_varnames, tuple)
threads = [Thread(target=run_in_thread) for _ in range(2)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,4 @@
Fix a crash in the free threading build when :c:func:`PyCode_GetCode`,
:c:func:`PyCode_GetVarnames`, :c:func:`PyCode_GetCellvars`, or
:c:func:`PyCode_GetFreevars` were called from multiple threads at the same
time.

View file

@ -304,21 +304,32 @@ validate_and_copy_tuple(PyObject *tup)
} }
static int static int
init_co_cached(PyCodeObject *self) { init_co_cached(PyCodeObject *self)
if (self->_co_cached == NULL) { {
self->_co_cached = PyMem_New(_PyCoCached, 1); _PyCoCached *cached = FT_ATOMIC_LOAD_PTR(self->_co_cached);
if (self->_co_cached == NULL) { if (cached != NULL) {
PyErr_NoMemory(); return 0;
return -1;
}
self->_co_cached->_co_code = NULL;
self->_co_cached->_co_cellvars = NULL;
self->_co_cached->_co_freevars = NULL;
self->_co_cached->_co_varnames = NULL;
} }
return 0;
Py_BEGIN_CRITICAL_SECTION(self);
cached = self->_co_cached;
if (cached == NULL) {
cached = PyMem_New(_PyCoCached, 1);
if (cached == NULL) {
PyErr_NoMemory();
}
else {
cached->_co_code = NULL;
cached->_co_cellvars = NULL;
cached->_co_freevars = NULL;
cached->_co_varnames = NULL;
FT_ATOMIC_STORE_PTR(self->_co_cached, cached);
}
}
Py_END_CRITICAL_SECTION();
return cached != NULL ? 0 : -1;
} }
/****************** /******************
* _PyCode_New() * _PyCode_New()
******************/ ******************/
@ -1544,16 +1555,21 @@ get_cached_locals(PyCodeObject *co, PyObject **cached_field,
{ {
assert(cached_field != NULL); assert(cached_field != NULL);
assert(co->_co_cached != NULL); assert(co->_co_cached != NULL);
if (*cached_field != NULL) { PyObject *varnames = FT_ATOMIC_LOAD_PTR(*cached_field);
return Py_NewRef(*cached_field); if (varnames != NULL) {
return Py_NewRef(varnames);
} }
assert(*cached_field == NULL);
PyObject *varnames = get_localsplus_names(co, kind, num); Py_BEGIN_CRITICAL_SECTION(co);
varnames = *cached_field;
if (varnames == NULL) { if (varnames == NULL) {
return NULL; varnames = get_localsplus_names(co, kind, num);
if (varnames != NULL) {
FT_ATOMIC_STORE_PTR(*cached_field, varnames);
}
} }
*cached_field = Py_NewRef(varnames); Py_END_CRITICAL_SECTION();
return varnames; return Py_XNewRef(varnames);
} }
PyObject * PyObject *
@ -1652,18 +1668,26 @@ _PyCode_GetCode(PyCodeObject *co)
if (init_co_cached(co)) { if (init_co_cached(co)) {
return NULL; return NULL;
} }
if (co->_co_cached->_co_code != NULL) {
return Py_NewRef(co->_co_cached->_co_code); _PyCoCached *cached = co->_co_cached;
PyObject *code = FT_ATOMIC_LOAD_PTR(cached->_co_code);
if (code != NULL) {
return Py_NewRef(code);
} }
PyObject *code = PyBytes_FromStringAndSize((const char *)_PyCode_CODE(co),
_PyCode_NBYTES(co)); Py_BEGIN_CRITICAL_SECTION(co);
code = cached->_co_code;
if (code == NULL) { if (code == NULL) {
return NULL; code = PyBytes_FromStringAndSize((const char *)_PyCode_CODE(co),
_PyCode_NBYTES(co));
if (code != NULL) {
deopt_code(co, (_Py_CODEUNIT *)PyBytes_AS_STRING(code));
assert(cached->_co_code == NULL);
FT_ATOMIC_STORE_PTR(cached->_co_code, code);
}
} }
deopt_code(co, (_Py_CODEUNIT *)PyBytes_AS_STRING(code)); Py_END_CRITICAL_SECTION();
assert(co->_co_cached->_co_code == NULL); return Py_XNewRef(code);
co->_co_cached->_co_code = Py_NewRef(code);
return code;
} }
PyObject * PyObject *