summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2021-06-23 15:47:38 (GMT)
committerGitHub <noreply@github.com>2021-06-23 15:47:38 (GMT)
commitece3841d3ddf38875b997eb919488cbb911a66e2 (patch)
tree8368c9c92ded7dc8d7f7878d94d4a72689ae8463
parent88a3342314c8b9ff40a2b6fd4759cfbf64712c67 (diff)
downloadcpython-ece3841d3ddf38875b997eb919488cbb911a66e2.zip
cpython-ece3841d3ddf38875b997eb919488cbb911a66e2.tar.gz
cpython-ece3841d3ddf38875b997eb919488cbb911a66e2.tar.bz2
bpo-44441: _PyImport_Fini2() resets PyImport_Inittab (GH-26874) (GH-26877)
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 489699ca05bed5cfd10e847d8580840812b476cd)
-rw-r--r--Doc/c-api/import.rst6
-rw-r--r--Doc/c-api/init_config.rst5
-rw-r--r--Lib/test/test_embed.py17
-rw-r--r--Misc/NEWS.d/next/C API/2021-06-23-12-12-04.bpo-44441.3p14JB.rst4
-rw-r--r--Programs/_testembed.c59
-rw-r--r--Python/import.c3
6 files changed, 86 insertions, 8 deletions
diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst
index c6fc330..d2ae6b6 100644
--- a/Doc/c-api/import.rst
+++ b/Doc/c-api/import.rst
@@ -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.
diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst
index de85029..fe5b83a 100644
--- a/Doc/c-api/init_config.rst
+++ b/Doc/c-api/init_config.rst
@@ -1193,7 +1193,10 @@ The caller is responsible to handle exceptions (error or exit) using
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.
+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.
The current configuration (``PyConfig`` type) is stored in
``PyInterpreterState.config``.
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index 23cf297..f2cf4a6 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -30,6 +30,7 @@ API_PYTHON = 2
# _PyCoreConfig_InitIsolatedConfig()
API_ISOLATED = 3
+INIT_LOOPS = 16
MAX_HASH_SEED = 4294967295
@@ -111,13 +112,13 @@ class EmbeddingTestsMixin:
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]+)>, "
@@ -125,7 +126,7 @@ class EmbeddingTestsMixin:
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):
@@ -159,6 +160,8 @@ class EmbeddingTestsMixin:
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]
@@ -194,6 +197,14 @@ class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
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")
diff --git a/Misc/NEWS.d/next/C API/2021-06-23-12-12-04.bpo-44441.3p14JB.rst b/Misc/NEWS.d/next/C API/2021-06-23-12-12-04.bpo-44441.3p14JB.rst
new file mode 100644
index 0000000..80c4282
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2021-06-23-12-12-04.bpo-44441.3p14JB.rst
@@ -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.
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 0901933..f8de6bc 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -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);
@@ -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();
@@ -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);
@@ -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
*****************************************************/
@@ -1735,6 +1751,38 @@ static int list_frozen(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.
@@ -1755,8 +1803,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},
@@ -1796,6 +1846,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},
@@ -1805,8 +1856,10 @@ static struct TestCase TestCases[] = {
{"test_audit_run_startup", test_audit_run_startup},
{"test_audit_run_stdin", test_audit_run_stdin},
+ // Specific C API
{"test_unicode_id_init", test_unicode_id_init},
+ // Command
{"list_frozen", list_frozen},
{NULL, NULL}
};
diff --git a/Python/import.c b/Python/import.c
index c4878c6..7301fcc 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -255,6 +255,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;