Skip to content

[3.9] bpo-44441: _PyImport_Fini2() resets PyImport_Inittab (GH-26874) #26878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion Doc/c-api/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,8 @@ Importing Modules
field; failure to provide the sentinel value can result in a memory fault.
Returns ``0`` on success or ``-1`` if insufficient memory could be allocated to
extend the internal table. In the event of failure, no modules are added to the
internal table. This should be called before :c:func:`Py_Initialize`.
internal table. This must be called before :c:func:`Py_Initialize`.

If Python is initialized multiple times, :c:func:`PyImport_AppendInittab` or
:c:func:`PyImport_ExtendInittab` must be called before each Python
initialization.
9 changes: 6 additions & 3 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -730,9 +730,12 @@ Function to initialize Python:
The caller is responsible to handle exceptions (error or exit) using
:c:func:`PyStatus_Exception` and :c:func:`Py_ExitStatusException`.

If ``PyImport_FrozenModules``, ``PyImport_AppendInittab()`` or
``PyImport_ExtendInittab()`` are used, they must be set or called after Python
preinitialization and before the Python initialization.
If :c:func:`PyImport_FrozenModules`, :c:func:`PyImport_AppendInittab` or
:c:func:`PyImport_ExtendInittab` are used, they must be set or called after
Python preinitialization and before the Python initialization. If Python is
initialized multiple times, :c:func:`PyImport_AppendInittab` or
:c:func:`PyImport_ExtendInittab` must be called before each Python
initialization.

Example setting the program name::

Expand Down
18 changes: 15 additions & 3 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
# _PyCoreConfig_InitIsolatedConfig()
API_ISOLATED = 3

INIT_LOOPS = 16


def debug_build(program):
program = os.path.basename(program)
Expand Down Expand Up @@ -107,21 +109,21 @@ def run_repeated_init_and_subinterpreters(self):
self.assertEqual(err, "")

# The output from _testembed looks like this:
# --- Pass 0 ---
# --- Pass 1 ---
# interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
# interp 1 <0x1d4f690>, thread state <0x1d35350>: id(modules) = 139650431165784
# interp 2 <0x1d5a690>, thread state <0x1d99ed0>: id(modules) = 139650413140368
# interp 3 <0x1d4f690>, thread state <0x1dc3340>: id(modules) = 139650412862200
# interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
# --- Pass 1 ---
# --- Pass 2 ---
# ...

interp_pat = (r"^interp (\d+) <(0x[\dA-F]+)>, "
r"thread state <(0x[\dA-F]+)>: "
r"id\(modules\) = ([\d]+)$")
Interp = namedtuple("Interp", "id interp tstate modules")

numloops = 0
numloops = 1
current_run = []
for line in out.splitlines():
if line == "--- Pass {} ---".format(numloops):
Expand Down Expand Up @@ -155,6 +157,8 @@ def run_repeated_init_and_subinterpreters(self):


class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
maxDiff = 100 * 50

def test_subinterps_main(self):
for run in self.run_repeated_init_and_subinterpreters():
main = run[0]
Expand Down Expand Up @@ -190,6 +194,14 @@ def test_subinterps_distinct_state(self):
self.assertNotEqual(sub.tstate, main.tstate)
self.assertNotEqual(sub.modules, main.modules)

def test_repeated_init_and_inittab(self):
out, err = self.run_embedded_interpreter("test_repeated_init_and_inittab")
self.assertEqual(err, "")

lines = [f"--- Pass {i} ---" for i in range(1, INIT_LOOPS+1)]
lines = "\n".join(lines) + "\n"
self.assertEqual(out, lines)

def test_forced_io_encoding(self):
# Checks forced configuration of embedded interpreter IO streams
env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:c:func:`Py_RunMain` now resets :c:data:`PyImport_Inittab` to its initial value
at exit. It must be possible to call :c:func:`PyImport_AppendInittab` or
:c:func:`PyImport_ExtendInittab` at each Python initialization.
Patch by Victor Stinner.
58 changes: 55 additions & 3 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
/* Use path starting with "./" avoids a search along the PATH */
#define PROGRAM_NAME L"./_testembed"

#define INIT_LOOPS 16


static void _testembed_Py_Initialize(void)
{
Py_SetProgramName(PROGRAM_NAME);
Expand Down Expand Up @@ -54,9 +57,8 @@ static int test_repeated_init_and_subinterpreters(void)
{
PyThreadState *mainstate, *substate;
PyGILState_STATE gilstate;
int i, j;

for (i=0; i<15; i++) {
for (int i=1; i <= INIT_LOOPS; i++) {
printf("--- Pass %d ---\n", i);
_testembed_Py_Initialize();
mainstate = PyThreadState_Get();
Expand All @@ -67,7 +69,7 @@ static int test_repeated_init_and_subinterpreters(void)
print_subinterp();
PyThreadState_Swap(NULL);

for (j=0; j<3; j++) {
for (int j=0; j<3; j++) {
substate = Py_NewInterpreter();
print_subinterp();
Py_EndInterpreter(substate);
Expand All @@ -83,6 +85,20 @@ static int test_repeated_init_and_subinterpreters(void)
return 0;
}

#define EMBEDDED_EXT_NAME "embedded_ext"

static PyModuleDef embedded_ext = {
PyModuleDef_HEAD_INIT,
.m_name = EMBEDDED_EXT_NAME,
.m_size = 0,
};

static PyObject*
PyInit_embedded_ext(void)
{
return PyModule_Create(&embedded_ext);
}

/*****************************************************
* Test forcing a particular IO encoding
*****************************************************/
Expand Down Expand Up @@ -1641,6 +1657,39 @@ static int test_get_argc_argv(void)
}


static int test_repeated_init_and_inittab(void)
{
// bpo-44441: Py_RunMain() must reset PyImport_Inittab at exit.
// It must be possible to call PyImport_AppendInittab() or
// PyImport_ExtendInittab() before each Python initialization.
for (int i=1; i <= INIT_LOOPS; i++) {
printf("--- Pass %d ---\n", i);

// Call PyImport_AppendInittab() at each iteration
if (PyImport_AppendInittab(EMBEDDED_EXT_NAME,
&PyInit_embedded_ext) != 0) {
fprintf(stderr, "PyImport_AppendInittab() failed\n");
return 1;
}

// Initialize Python
wchar_t* argv[] = {PROGRAM_NAME, L"-c", L"pass"};
PyConfig config;
PyConfig_InitPythonConfig(&config);
config.isolated = 1;
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
init_from_config_clear(&config);

// Py_RunMain() calls _PyImport_Fini2() which resets PyImport_Inittab
int exitcode = Py_RunMain();
if (exitcode != 0) {
return exitcode;
}
}
return 0;
}


/* *********************************************************
* List of test cases and the function that implements it.
*
Expand All @@ -1660,8 +1709,10 @@ struct TestCase
};

static struct TestCase TestCases[] = {
// Python initialization
{"test_forced_io_encoding", test_forced_io_encoding},
{"test_repeated_init_and_subinterpreters", test_repeated_init_and_subinterpreters},
{"test_repeated_init_and_inittab", test_repeated_init_and_inittab},
{"test_pre_initialization_api", test_pre_initialization_api},
{"test_pre_initialization_sys_options", test_pre_initialization_sys_options},
{"test_bpo20891", test_bpo20891},
Expand Down Expand Up @@ -1700,6 +1751,7 @@ static struct TestCase TestCases[] = {
{"test_run_main", test_run_main},
{"test_get_argc_argv", test_get_argc_argv},

// Audit
{"test_open_code_hook", test_open_code_hook},
{"test_audit", test_audit},
{"test_audit_subinterpreter", test_audit_subinterpreter},
Expand Down
3 changes: 3 additions & 0 deletions Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ _PyImport_Fini2(void)
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);

// Reset PyImport_Inittab
PyImport_Inittab = _PyImport_Inittab;

/* Free memory allocated by PyImport_ExtendInittab() */
PyMem_RawFree(inittab_copy);
inittab_copy = NULL;
Expand Down