Skip to content

Commit 5ed7827

Browse files
authored
bpo-44441: _PyImport_Fini2() resets PyImport_Inittab (GH-26874) (GH-26878)
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. (cherry picked from commit 489699c)
1 parent fcde2c6 commit 5ed7827

File tree

6 files changed

+88
-10
lines changed

6 files changed

+88
-10
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: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -730,9 +730,12 @@ Function to initialize Python:
730730
The caller is responsible to handle exceptions (error or exit) using
731731
:c:func:`PyStatus_Exception` and :c:func:`Py_ExitStatusException`.
732732
733-
If ``PyImport_FrozenModules``, ``PyImport_AppendInittab()`` or
734-
``PyImport_ExtendInittab()`` are used, they must be set or called after Python
735-
preinitialization and before the Python initialization.
733+
If :c:func:`PyImport_FrozenModules`, :c:func:`PyImport_AppendInittab` or
734+
:c:func:`PyImport_ExtendInittab` are used, they must be set or called after
735+
Python preinitialization and before the Python initialization. If Python is
736+
initialized multiple times, :c:func:`PyImport_AppendInittab` or
737+
:c:func:`PyImport_ExtendInittab` must be called before each Python
738+
initialization.
736739
737740
Example setting the program name::
738741

Lib/test/test_embed.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
# _PyCoreConfig_InitIsolatedConfig()
2929
API_ISOLATED = 3
3030

31+
INIT_LOOPS = 16
32+
3133

3234
def debug_build(program):
3335
program = os.path.basename(program)
@@ -107,21 +109,21 @@ def run_repeated_init_and_subinterpreters(self):
107109
self.assertEqual(err, "")
108110

109111
# The output from _testembed looks like this:
110-
# --- Pass 0 ---
112+
# --- Pass 1 ---
111113
# interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
112114
# interp 1 <0x1d4f690>, thread state <0x1d35350>: id(modules) = 139650431165784
113115
# interp 2 <0x1d5a690>, thread state <0x1d99ed0>: id(modules) = 139650413140368
114116
# interp 3 <0x1d4f690>, thread state <0x1dc3340>: id(modules) = 139650412862200
115117
# interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
116-
# --- Pass 1 ---
118+
# --- Pass 2 ---
117119
# ...
118120

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

124-
numloops = 0
126+
numloops = 1
125127
current_run = []
126128
for line in out.splitlines():
127129
if line == "--- Pass {} ---".format(numloops):
@@ -155,6 +157,8 @@ def run_repeated_init_and_subinterpreters(self):
155157

156158

157159
class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
160+
maxDiff = 100 * 50
161+
158162
def test_subinterps_main(self):
159163
for run in self.run_repeated_init_and_subinterpreters():
160164
main = run[0]
@@ -190,6 +194,14 @@ def test_subinterps_distinct_state(self):
190194
self.assertNotEqual(sub.tstate, main.tstate)
191195
self.assertNotEqual(sub.modules, main.modules)
192196

197+
def test_repeated_init_and_inittab(self):
198+
out, err = self.run_embedded_interpreter("test_repeated_init_and_inittab")
199+
self.assertEqual(err, "")
200+
201+
lines = [f"--- Pass {i} ---" for i in range(1, INIT_LOOPS+1)]
202+
lines = "\n".join(lines) + "\n"
203+
self.assertEqual(out, lines)
204+
193205
def test_forced_io_encoding(self):
194206
# Checks forced configuration of embedded interpreter IO streams
195207
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,9 @@
2222
/* Use path starting with "./" avoids a search along the PATH */
2323
#define PROGRAM_NAME L"./_testembed"
2424

25+
#define INIT_LOOPS 16
26+
27+
2528
static void _testembed_Py_Initialize(void)
2629
{
2730
Py_SetProgramName(PROGRAM_NAME);
@@ -54,9 +57,8 @@ static int test_repeated_init_and_subinterpreters(void)
5457
{
5558
PyThreadState *mainstate, *substate;
5659
PyGILState_STATE gilstate;
57-
int i, j;
5860

59-
for (i=0; i<15; i++) {
61+
for (int i=1; i <= INIT_LOOPS; i++) {
6062
printf("--- Pass %d ---\n", i);
6163
_testembed_Py_Initialize();
6264
mainstate = PyThreadState_Get();
@@ -67,7 +69,7 @@ static int test_repeated_init_and_subinterpreters(void)
6769
print_subinterp();
6870
PyThreadState_Swap(NULL);
6971

70-
for (j=0; j<3; j++) {
72+
for (int j=0; j<3; j++) {
7173
substate = Py_NewInterpreter();
7274
print_subinterp();
7375
Py_EndInterpreter(substate);
@@ -83,6 +85,20 @@ static int test_repeated_init_and_subinterpreters(void)
8385
return 0;
8486
}
8587

88+
#define EMBEDDED_EXT_NAME "embedded_ext"
89+
90+
static PyModuleDef embedded_ext = {
91+
PyModuleDef_HEAD_INIT,
92+
.m_name = EMBEDDED_EXT_NAME,
93+
.m_size = 0,
94+
};
95+
96+
static PyObject*
97+
PyInit_embedded_ext(void)
98+
{
99+
return PyModule_Create(&embedded_ext);
100+
}
101+
86102
/*****************************************************
87103
* Test forcing a particular IO encoding
88104
*****************************************************/
@@ -1641,6 +1657,39 @@ static int test_get_argc_argv(void)
16411657
}
16421658

16431659

1660+
static int test_repeated_init_and_inittab(void)
1661+
{
1662+
// bpo-44441: Py_RunMain() must reset PyImport_Inittab at exit.
1663+
// It must be possible to call PyImport_AppendInittab() or
1664+
// PyImport_ExtendInittab() before each Python initialization.
1665+
for (int i=1; i <= INIT_LOOPS; i++) {
1666+
printf("--- Pass %d ---\n", i);
1667+
1668+
// Call PyImport_AppendInittab() at each iteration
1669+
if (PyImport_AppendInittab(EMBEDDED_EXT_NAME,
1670+
&PyInit_embedded_ext) != 0) {
1671+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
1672+
return 1;
1673+
}
1674+
1675+
// Initialize Python
1676+
wchar_t* argv[] = {PROGRAM_NAME, L"-c", L"pass"};
1677+
PyConfig config;
1678+
PyConfig_InitPythonConfig(&config);
1679+
config.isolated = 1;
1680+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
1681+
init_from_config_clear(&config);
1682+
1683+
// Py_RunMain() calls _PyImport_Fini2() which resets PyImport_Inittab
1684+
int exitcode = Py_RunMain();
1685+
if (exitcode != 0) {
1686+
return exitcode;
1687+
}
1688+
}
1689+
return 0;
1690+
}
1691+
1692+
16441693
/* *********************************************************
16451694
* List of test cases and the function that implements it.
16461695
*
@@ -1660,8 +1709,10 @@ struct TestCase
16601709
};
16611710

16621711
static struct TestCase TestCases[] = {
1712+
// Python initialization
16631713
{"test_forced_io_encoding", test_forced_io_encoding},
16641714
{"test_repeated_init_and_subinterpreters", test_repeated_init_and_subinterpreters},
1715+
{"test_repeated_init_and_inittab", test_repeated_init_and_inittab},
16651716
{"test_pre_initialization_api", test_pre_initialization_api},
16661717
{"test_pre_initialization_sys_options", test_pre_initialization_sys_options},
16671718
{"test_bpo20891", test_bpo20891},
@@ -1700,6 +1751,7 @@ static struct TestCase TestCases[] = {
17001751
{"test_run_main", test_run_main},
17011752
{"test_get_argc_argv", test_get_argc_argv},
17021753

1754+
// Audit
17031755
{"test_open_code_hook", test_open_code_hook},
17041756
{"test_audit", test_audit},
17051757
{"test_audit_subinterpreter", test_audit_subinterpreter},

Python/import.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ _PyImport_Fini2(void)
296296
PyMemAllocatorEx old_alloc;
297297
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
298298

299+
// Reset PyImport_Inittab
300+
PyImport_Inittab = _PyImport_Inittab;
301+
299302
/* Free memory allocated by PyImport_ExtendInittab() */
300303
PyMem_RawFree(inittab_copy);
301304
inittab_copy = NULL;

0 commit comments

Comments
 (0)