gh-140260: fix data race in _struct module initialization with subinterpreters (#140909)
This commit is contained in:
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Fix :mod:`struct` data race in endian table initialization with
|
||||
subinterpreters. Patch by Shamil Abdulaev.
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
Reference in New Issue
Block a user