gh-140260: fix data race in _struct module initialization with subinterpreters (#140909)

This commit is contained in:
Shamil
2025-11-13 14:01:31 +03:00
committed by GitHub
parent 781cc68c3c
commit 63548b3699
4 changed files with 70 additions and 41 deletions

View File

@@ -800,6 +800,23 @@ class StructTest(ComplexesAreIdenticalMixin, unittest.TestCase):
round_trip = struct.unpack(f, struct.pack(f, z))[0]
self.assertComplexesAreIdentical(z, round_trip)
@unittest.skipIf(
support.is_android or support.is_apple_mobile,
"Subinterpreters are not supported on Android and iOS"
)
def test_endian_table_init_subinterpreters(self):
# Verify that the _struct extension module can be initialized
# concurrently in subinterpreters (gh-140260).
try:
from concurrent.futures import InterpreterPoolExecutor
except ImportError:
raise unittest.SkipTest("InterpreterPoolExecutor not available")
code = "import struct"
with InterpreterPoolExecutor(max_workers=5) as executor:
results = executor.map(exec, [code] * 5)
self.assertListEqual(list(results), [None] * 5)
class UnpackIteratorTest(unittest.TestCase):
"""

View File

@@ -0,0 +1,2 @@
Fix :mod:`struct` data race in endian table initialization with
subinterpreters. Patch by Shamil Abdulaev.

View File

@@ -9,6 +9,7 @@
#include "Python.h"
#include "pycore_bytesobject.h" // _PyBytesWriter
#include "pycore_lock.h" // _PyOnceFlag_CallOnce()
#include "pycore_long.h" // _PyLong_AsByteArray()
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
@@ -1505,6 +1506,53 @@ static formatdef lilendian_table[] = {
{0}
};
/* Ensure endian table optimization happens exactly once across all interpreters */
static _PyOnceFlag endian_tables_init_once = {0};
static int
init_endian_tables(void *Py_UNUSED(arg))
{
const formatdef *native = native_table;
formatdef *other, *ptr;
#if PY_LITTLE_ENDIAN
other = lilendian_table;
#else
other = bigendian_table;
#endif
/* Scan through the native table, find a matching
entry in the endian table and swap in the
native implementations whenever possible
(64-bit platforms may not have "standard" sizes) */
while (native->format != '\0' && other->format != '\0') {
ptr = other;
while (ptr->format != '\0') {
if (ptr->format == native->format) {
/* Match faster when formats are
listed in the same order */
if (ptr == other)
other++;
/* Only use the trick if the
size matches */
if (ptr->size != native->size)
break;
/* Skip float and double, could be
"unknown" float format */
if (ptr->format == 'd' || ptr->format == 'f')
break;
/* Skip _Bool, semantics are different for standard size */
if (ptr->format == '?')
break;
ptr->pack = native->pack;
ptr->unpack = native->unpack;
break;
}
ptr++;
}
native++;
}
return 0;
}
static const formatdef *
whichtable(const char **pfmt)
@@ -2710,47 +2758,8 @@ _structmodule_exec(PyObject *m)
return -1;
}
/* Check endian and swap in faster functions */
{
const formatdef *native = native_table;
formatdef *other, *ptr;
#if PY_LITTLE_ENDIAN
other = lilendian_table;
#else
other = bigendian_table;
#endif
/* Scan through the native table, find a matching
entry in the endian table and swap in the
native implementations whenever possible
(64-bit platforms may not have "standard" sizes) */
while (native->format != '\0' && other->format != '\0') {
ptr = other;
while (ptr->format != '\0') {
if (ptr->format == native->format) {
/* Match faster when formats are
listed in the same order */
if (ptr == other)
other++;
/* Only use the trick if the
size matches */
if (ptr->size != native->size)
break;
/* Skip float and double, could be
"unknown" float format */
if (ptr->format == 'd' || ptr->format == 'f')
break;
/* Skip _Bool, semantics are different for standard size */
if (ptr->format == '?')
break;
ptr->pack = native->pack;
ptr->unpack = native->unpack;
break;
}
ptr++;
}
native++;
}
}
/* init cannot fail */
(void)_PyOnceFlag_CallOnce(&endian_tables_init_once, init_endian_tables, NULL);
/* Add some symbolic constants to the module */
state->StructError = PyErr_NewException("struct.error", NULL, NULL);

View File

@@ -24,6 +24,7 @@ Modules/posixmodule.c os_dup2_impl dup3_works -
## guards around resource init
Python/thread_pthread.h PyThread__init_thread lib_initialized -
Modules/_struct.c - endian_tables_init_once -
##-----------------------
## other values (not Python-specific)
1 filename funcname name reason
24 Objects/longobject.c - convwidth_base log_base_BASE -
25 Objects/longobject.c - convmultmax_base convwidth_base -
26 ## cached computed data - set lazily (*after* first init) Objects/longobject.c - convmultmax_base -
27 ## cached computed data - set lazily (*after* first init)
28 # XXX Are these safe relative to write races?
29 Objects/unicodeobject.c - bloom_linebreak -
30 # This is safe: