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]
|
round_trip = struct.unpack(f, struct.pack(f, z))[0]
|
||||||
self.assertComplexesAreIdentical(z, round_trip)
|
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):
|
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 "Python.h"
|
||||||
#include "pycore_bytesobject.h" // _PyBytesWriter
|
#include "pycore_bytesobject.h" // _PyBytesWriter
|
||||||
|
#include "pycore_lock.h" // _PyOnceFlag_CallOnce()
|
||||||
#include "pycore_long.h" // _PyLong_AsByteArray()
|
#include "pycore_long.h" // _PyLong_AsByteArray()
|
||||||
#include "pycore_moduleobject.h" // _PyModule_GetState()
|
#include "pycore_moduleobject.h" // _PyModule_GetState()
|
||||||
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
|
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
|
||||||
@@ -1505,6 +1506,53 @@ static formatdef lilendian_table[] = {
|
|||||||
{0}
|
{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 *
|
static const formatdef *
|
||||||
whichtable(const char **pfmt)
|
whichtable(const char **pfmt)
|
||||||
@@ -2710,47 +2758,8 @@ _structmodule_exec(PyObject *m)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check endian and swap in faster functions */
|
/* init cannot fail */
|
||||||
{
|
(void)_PyOnceFlag_CallOnce(&endian_tables_init_once, init_endian_tables, NULL);
|
||||||
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++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add some symbolic constants to the module */
|
/* Add some symbolic constants to the module */
|
||||||
state->StructError = PyErr_NewException("struct.error", NULL, NULL);
|
state->StructError = PyErr_NewException("struct.error", NULL, NULL);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ Modules/posixmodule.c os_dup2_impl dup3_works -
|
|||||||
|
|
||||||
## guards around resource init
|
## guards around resource init
|
||||||
Python/thread_pthread.h PyThread__init_thread lib_initialized -
|
Python/thread_pthread.h PyThread__init_thread lib_initialized -
|
||||||
|
Modules/_struct.c - endian_tables_init_once -
|
||||||
|
|
||||||
##-----------------------
|
##-----------------------
|
||||||
## other values (not Python-specific)
|
## other values (not Python-specific)
|
||||||
|
|||||||
|
Reference in New Issue
Block a user