Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections import abc
from itertools import combinations
import array
from concurrent.futures import InterpreterPoolExecutor
import gc
import math
import operator
Expand Down Expand Up @@ -800,6 +801,15 @@ def test_c_complex_round_trip(self):
round_trip = struct.unpack(f, struct.pack(f, z))[0]
self.assertComplexesAreIdentical(z, round_trip)

def test_endian_table_init_subinterpreters(self):
# Verify that the _struct extension module can be initialized
# concurrently in subinterpreters (gh-140260).
code = "import struct"
with InterpreterPoolExecutor(max_workers=5) as executor:
results = executor.map(support.run_in_subinterp, [code] * 5)
for ret in results:
self.assertEqual(ret, 0)


class UnpackIteratorTest(unittest.TestCase):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :mod:`struct` data race in endian table initialization with
subinterpreters. Patch by Shamil Abdulaev.
90 changes: 49 additions & 41 deletions Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -2710,47 +2758,7 @@ _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++;
}
}
_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);
Expand Down
1 change: 1 addition & 0 deletions Tools/c-analyzer/cpython/globals-to-fix.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ Modules/_cursesmodule.c - curses_setupterm_called -
Modules/_cursesmodule.c - curses_start_color_called -
Modules/_cursesmodule.c - curses_screen_encoding -
Modules/_elementtree.c - expat_capi -
Modules/_struct.c - endian_tables_init_once -
Modules/readline.c - libedit_append_replace_history_offset -
Modules/readline.c - using_libedit_emulation -
Modules/readline.c - libedit_history_start -
Expand Down
Loading