Skip to content

Commit 489699c

Browse files
authored
bpo-44441: _PyImport_Fini2() resets PyImport_Inittab (GH-26874)
Py_RunMain() now resets PyImport_Inittab to its initial value at exit. It must be possible to call PyImport_AppendInittab() or PyImport_ExtendInittab() at each Python initialization.
1 parent 019ad62 commit 489699c

File tree

6 files changed

+85
-8
lines changed

6 files changed

+85
-8
lines changed

Doc/c-api/import.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,8 @@ Importing Modules
299299
field; failure to provide the sentinel value can result in a memory fault.
300300
Returns ``0`` on success or ``-1`` if insufficient memory could be allocated to
301301
extend the internal table. In the event of failure, no modules are added to the
302-
internal table. This should be called before :c:func:`Py_Initialize`.
302+
internal table. This must be called before :c:func:`Py_Initialize`.
303+
304+
If Python is initialized multiple times, :c:func:`PyImport_AppendInittab` or
305+
:c:func:`PyImport_ExtendInittab` must be called before each Python
306+
initialization.

Doc/c-api/init_config.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1193,7 +1193,10 @@ The caller is responsible to handle exceptions (error or exit) using
11931193
11941194
If :c:func:`PyImport_FrozenModules`, :c:func:`PyImport_AppendInittab` or
11951195
:c:func:`PyImport_ExtendInittab` are used, they must be set or called after
1196-
Python preinitialization and before the Python initialization.
1196+
Python preinitialization and before the Python initialization. If Python is
1197+
initialized multiple times, :c:func:`PyImport_AppendInittab` or
1198+
:c:func:`PyImport_ExtendInittab` must be called before each Python
1199+
initialization.
11971200
11981201
The current configuration (``PyConfig`` type) is stored in
11991202
``PyInterpreterState.config``.

Lib/test/test_embed.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
# _PyCoreConfig_InitIsolatedConfig()
3131
API_ISOLATED = 3
3232

33+
INIT_LOOPS = 16
3334
MAX_HASH_SEED = 4294967295
3435

3536

@@ -111,21 +112,21 @@ def run_repeated_init_and_subinterpreters(self):
111112
self.assertEqual(err, "")
112113

113114
# The output from _testembed looks like this:
114-
# --- Pass 0 ---
115+
# --- Pass 1 ---
115116
# interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
116117
# interp 1 <0x1d4f690>, thread state <0x1d35350>: id(modules) = 139650431165784
117118
# interp 2 <0x1d5a690>, thread state <0x1d99ed0>: id(modules) = 139650413140368
118119
# interp 3 <0x1d4f690>, thread state <0x1dc3340>: id(modules) = 139650412862200
119120
# interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
120-
# --- Pass 1 ---
121+
# --- Pass 2 ---
121122
# ...
122123

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

128-
numloops = 0
129+
numloops = 1
129130
current_run = []
130131
for line in out.splitlines():
131132
if line == "--- Pass {} ---".format(numloops):
@@ -159,6 +160,8 @@ def run_repeated_init_and_subinterpreters(self):
159160

160161

161162
class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
163+
maxDiff = 100 * 50
164+
162165
def test_subinterps_main(self):
163166
for run in self.run_repeated_init_and_subinterpreters():
164167
main = run[0]
@@ -194,6 +197,14 @@ def test_subinterps_distinct_state(self):
194197
self.assertNotEqual(sub.tstate, main.tstate)
195198
self.assertNotEqual(sub.modules, main.modules)
196199

200+
def test_repeated_init_and_inittab(self):
201+
out, err = self.run_embedded_interpreter("test_repeated_init_and_inittab")
202+
self.assertEqual(err, "")
203+
204+
lines = [f"--- Pass {i} ---" for i in range(1, INIT_LOOPS+1)]
205+
lines = "\n".join(lines) + "\n"
206+
self.assertEqual(out, lines)
207+
197208
def test_forced_io_encoding(self):
198209
# Checks forced configuration of embedded interpreter IO streams
199210
env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:c:func:`Py_RunMain` now resets :c:data:`PyImport_Inittab` to its initial value
2+
at exit. It must be possible to call :c:func:`PyImport_AppendInittab` or
3+
:c:func:`PyImport_ExtendInittab` at each Python initialization.
4+
Patch by Victor Stinner.

Programs/_testembed.c

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
/* Use path starting with "./" avoids a search along the PATH */
2323
#define PROGRAM_NAME L"./_testembed"
2424

25+
#define INIT_LOOPS 16
26+
2527
// Ignore Py_DEPRECATED() compiler warnings: deprecated functions are
2628
// tested on purpose here.
2729
_Py_COMP_DIAG_PUSH
@@ -67,9 +69,8 @@ static int test_repeated_init_and_subinterpreters(void)
6769
{
6870
PyThreadState *mainstate, *substate;
6971
PyGILState_STATE gilstate;
70-
int i, j;
7172

72-
for (i=0; i<15; i++) {
73+
for (int i=1; i <= INIT_LOOPS; i++) {
7374
printf("--- Pass %d ---\n", i);
7475
_testembed_Py_Initialize();
7576
mainstate = PyThreadState_Get();
@@ -80,7 +81,7 @@ static int test_repeated_init_and_subinterpreters(void)
8081
print_subinterp();
8182
PyThreadState_Swap(NULL);
8283

83-
for (j=0; j<3; j++) {
84+
for (int j=0; j<3; j++) {
8485
substate = Py_NewInterpreter();
8586
print_subinterp();
8687
Py_EndInterpreter(substate);
@@ -96,6 +97,20 @@ static int test_repeated_init_and_subinterpreters(void)
9697
return 0;
9798
}
9899

100+
#define EMBEDDED_EXT_NAME "embedded_ext"
101+
102+
static PyModuleDef embedded_ext = {
103+
PyModuleDef_HEAD_INIT,
104+
.m_name = EMBEDDED_EXT_NAME,
105+
.m_size = 0,
106+
};
107+
108+
static PyObject*
109+
PyInit_embedded_ext(void)
110+
{
111+
return PyModule_Create(&embedded_ext);
112+
}
113+
99114
/*****************************************************
100115
* Test forcing a particular IO encoding
101116
*****************************************************/
@@ -1790,6 +1805,38 @@ static int list_frozen(void)
17901805
}
17911806

17921807

1808+
static int test_repeated_init_and_inittab(void)
1809+
{
1810+
// bpo-44441: Py_RunMain() must reset PyImport_Inittab at exit.
1811+
// It must be possible to call PyImport_AppendInittab() or
1812+
// PyImport_ExtendInittab() before each Python initialization.
1813+
for (int i=1; i <= INIT_LOOPS; i++) {
1814+
printf("--- Pass %d ---\n", i);
1815+
1816+
// Call PyImport_AppendInittab() at each iteration
1817+
if (PyImport_AppendInittab(EMBEDDED_EXT_NAME,
1818+
&PyInit_embedded_ext) != 0) {
1819+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
1820+
return 1;
1821+
}
1822+
1823+
// Initialize Python
1824+
wchar_t* argv[] = {PROGRAM_NAME, L"-c", L"pass"};
1825+
PyConfig config;
1826+
PyConfig_InitPythonConfig(&config);
1827+
config.isolated = 1;
1828+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
1829+
init_from_config_clear(&config);
1830+
1831+
// Py_RunMain() calls _PyImport_Fini2() which resets PyImport_Inittab
1832+
int exitcode = Py_RunMain();
1833+
if (exitcode != 0) {
1834+
return exitcode;
1835+
}
1836+
}
1837+
return 0;
1838+
}
1839+
17931840

17941841
/* *********************************************************
17951842
* List of test cases and the function that implements it.
@@ -1810,8 +1857,10 @@ struct TestCase
18101857
};
18111858

18121859
static struct TestCase TestCases[] = {
1860+
// Python initialization
18131861
{"test_forced_io_encoding", test_forced_io_encoding},
18141862
{"test_repeated_init_and_subinterpreters", test_repeated_init_and_subinterpreters},
1863+
{"test_repeated_init_and_inittab", test_repeated_init_and_inittab},
18151864
{"test_pre_initialization_api", test_pre_initialization_api},
18161865
{"test_pre_initialization_sys_options", test_pre_initialization_sys_options},
18171866
{"test_bpo20891", test_bpo20891},
@@ -1851,6 +1900,7 @@ static struct TestCase TestCases[] = {
18511900
{"test_run_main", test_run_main},
18521901
{"test_get_argc_argv", test_get_argc_argv},
18531902

1903+
// Audit
18541904
{"test_open_code_hook", test_open_code_hook},
18551905
{"test_audit", test_audit},
18561906
{"test_audit_subinterpreter", test_audit_subinterpreter},
@@ -1860,11 +1910,13 @@ static struct TestCase TestCases[] = {
18601910
{"test_audit_run_startup", test_audit_run_startup},
18611911
{"test_audit_run_stdin", test_audit_run_stdin},
18621912

1913+
// Specific C API
18631914
{"test_unicode_id_init", test_unicode_id_init},
18641915
#ifndef MS_WINDOWS
18651916
{"test_frozenmain", test_frozenmain},
18661917
#endif
18671918

1919+
// Command
18681920
{"list_frozen", list_frozen},
18691921
{NULL, NULL}
18701922
};

Python/import.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ _PyImport_Fini2(void)
255255
PyMemAllocatorEx old_alloc;
256256
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
257257

258+
// Reset PyImport_Inittab
259+
PyImport_Inittab = _PyImport_Inittab;
260+
258261
/* Free memory allocated by PyImport_ExtendInittab() */
259262
PyMem_RawFree(inittab_copy);
260263
inittab_copy = NULL;

0 commit comments

Comments
 (0)