From f4de1f7e2f5703468deaa75dc8f5f42193d1ad0d Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Tue, 30 May 2023 17:49:11 +0800 Subject: [PATCH 1/8] clear module state after use --- Lib/test/test_import/__init__.py | 8 ++++++++ Modules/_testsinglephase.c | 21 ++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index e2384a08ecaa90..84e2fcf700df7b 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2330,6 +2330,9 @@ def test_variants(self): self.assertIs(basic.look_up_self(), basic_lookedup) self.assertEqual(basic.initialized_count(), expected_init_count) + loaded.module._clear_module_state() + + def test_basic_reloaded(self): # m_copy is copied into the existing module object. # Global state is not changed. @@ -2413,6 +2416,11 @@ def test_with_reinit_reloaded(self): self.assertIs(reloaded.snapshot.cached, reloaded.module) + if name == f'{self.NAME}_with_state': + loaded.module._clear_module_state() + reloaded.module._clear_module_state() + + # Currently, for every single-phrase init module loaded # in multiple interpreters, those interpreters share a # PyModuleDef for that object, which can be a problem. diff --git a/Modules/_testsinglephase.c b/Modules/_testsinglephase.c index 8e6973f0b052cc..363ddb79689c86 100644 --- a/Modules/_testsinglephase.c +++ b/Modules/_testsinglephase.c @@ -248,6 +248,24 @@ basic__clear_globals(PyObject *self, PyObject *Py_UNUSED(ignored)) basic__clear_globals_doc} +PyDoc_STRVAR(basic__clear_module_state_doc, "_clear_module_state()\n\ +\n\ +Free the module state and set it to uninitialized."); + +static PyObject* +basic__clear_module_state(PyObject *self, PyObject *Py_UNUSED(ignored)) { + module_state *state = get_module_state(self); + if (state != NULL) { + clear_state(state); + } + Py_RETURN_NONE; +} + +#define _CLEAR_MODULE_STATE_METHODDEF \ + {"_clear_module_state", basic__clear_module_state, METH_NOARGS, \ + basic__clear_module_state_doc} + + /*********************************************/ /* the _testsinglephase module (and aliases) */ /*********************************************/ @@ -408,7 +426,7 @@ PyInit__testsinglephase_with_reinit(void) /* the _testsinglephase_with_state module */ /******************************************/ -/* This ia less typical of legacy extensions in the wild: +/* This is a less typical of legacy extensions in the wild: - single-phase init (same as _testsinglephase above) - has some module state - supports repeated initialization @@ -424,6 +442,7 @@ static PyMethodDef TestMethods_WithState[] = { LOOK_UP_SELF_METHODDEF, SUM_METHODDEF, STATE_INITIALIZED_METHODDEF, + _CLEAR_MODULE_STATE_METHODDEF, {NULL, NULL} /* sentinel */ }; From bd32c138c1a4f4e99b62991fe9db9876138f20aa Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 30 May 2023 12:12:52 +0200 Subject: [PATCH 2/8] PEP 7 --- Modules/_testsinglephase.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/_testsinglephase.c b/Modules/_testsinglephase.c index 363ddb79689c86..89265ff6fe391c 100644 --- a/Modules/_testsinglephase.c +++ b/Modules/_testsinglephase.c @@ -252,8 +252,9 @@ PyDoc_STRVAR(basic__clear_module_state_doc, "_clear_module_state()\n\ \n\ Free the module state and set it to uninitialized."); -static PyObject* -basic__clear_module_state(PyObject *self, PyObject *Py_UNUSED(ignored)) { +static PyObject * +basic__clear_module_state(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ module_state *state = get_module_state(self); if (state != NULL) { clear_state(state); From ac72ac41c39a8cdae4999047a4b83a7fa70483c9 Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Tue, 30 May 2023 18:41:01 +0800 Subject: [PATCH 3/8] use `unittest.addCleanup` --- Lib/test/test_import/__init__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 84e2fcf700df7b..a198a10182e0ac 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2320,6 +2320,7 @@ def test_variants(self): self.add_module_cleanup(name) with self.subTest(name): loaded = self.load(name) + self.addCleanup(loaded.module._clear_module_state) self.check_common(loaded) self.assertIsNot(loaded.snapshot.state_initialized, None) @@ -2330,8 +2331,6 @@ def test_variants(self): self.assertIs(basic.look_up_self(), basic_lookedup) self.assertEqual(basic.initialized_count(), expected_init_count) - loaded.module._clear_module_state() - def test_basic_reloaded(self): # m_copy is copied into the existing module object. @@ -2391,6 +2390,10 @@ def test_with_reinit_reloaded(self): loaded = self.load(name) reloaded = self.re_load(name, loaded.module) + if name == f'{self.NAME}_with_state': + self.addCleanup(loaded.module._clear_module_state) + self.addCleanup(reloaded.module._clear_module_state) + self.check_common(loaded) self.check_common(reloaded) @@ -2416,10 +2419,6 @@ def test_with_reinit_reloaded(self): self.assertIs(reloaded.snapshot.cached, reloaded.module) - if name == f'{self.NAME}_with_state': - loaded.module._clear_module_state() - reloaded.module._clear_module_state() - # Currently, for every single-phrase init module loaded # in multiple interpreters, those interpreters share a From 764be66faf06d057b53e978b3e0c6d903cff86b4 Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Tue, 30 May 2023 18:49:47 +0800 Subject: [PATCH 4/8] Update Modules/_testsinglephase.c --- Modules/_testsinglephase.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_testsinglephase.c b/Modules/_testsinglephase.c index 89265ff6fe391c..dca7abff89146e 100644 --- a/Modules/_testsinglephase.c +++ b/Modules/_testsinglephase.c @@ -427,7 +427,7 @@ PyInit__testsinglephase_with_reinit(void) /* the _testsinglephase_with_state module */ /******************************************/ -/* This is a less typical of legacy extensions in the wild: +/* This is less typical of legacy extensions in the wild: - single-phase init (same as _testsinglephase above) - has some module state - supports repeated initialization From 52f46f22ffc410ab1b7dec8f9b641ca563b0fd70 Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Tue, 30 May 2023 18:50:06 +0800 Subject: [PATCH 5/8] Update Lib/test/test_import/__init__.py --- Lib/test/test_import/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index a198a10182e0ac..446115a9ebf1ac 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2419,7 +2419,6 @@ def test_with_reinit_reloaded(self): self.assertIs(reloaded.snapshot.cached, reloaded.module) - # Currently, for every single-phrase init module loaded # in multiple interpreters, those interpreters share a # PyModuleDef for that object, which can be a problem. From daebdf74b4e5af24671412f347726dff06f8470c Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Tue, 30 May 2023 18:50:25 +0800 Subject: [PATCH 6/8] Update Lib/test/test_import/__init__.py --- Lib/test/test_import/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 446115a9ebf1ac..f99f8b2b238c0a 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2331,7 +2331,6 @@ def test_variants(self): self.assertIs(basic.look_up_self(), basic_lookedup) self.assertEqual(basic.initialized_count(), expected_init_count) - def test_basic_reloaded(self): # m_copy is copied into the existing module object. # Global state is not changed. From a0f5bb7c3247ebd17b06800148eecce694cfc9ae Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Tue, 30 May 2023 18:54:38 +0800 Subject: [PATCH 7/8] Update Lib/test/test_import/__init__.py Co-authored-by: Erlend E. Aasland --- Lib/test/test_import/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index f99f8b2b238c0a..d0ad978e72847c 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2389,7 +2389,7 @@ def test_with_reinit_reloaded(self): loaded = self.load(name) reloaded = self.re_load(name, loaded.module) - if name == f'{self.NAME}_with_state': + if name.endswith("_with_state"): self.addCleanup(loaded.module._clear_module_state) self.addCleanup(reloaded.module._clear_module_state) From 26a9ede0800fdb6ba15d161ee3fa7a85024a82e9 Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Tue, 30 May 2023 20:14:08 +0800 Subject: [PATCH 8/8] tell by `has_state` instead of name --- Lib/test/test_import/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index d0ad978e72847c..227c912bc8c322 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2380,17 +2380,18 @@ def test_with_reinit_reloaded(self): # Keep a reference around. basic = self.load(self.NAME) - for name in [ - f'{self.NAME}_with_reinit', # m_size == 0 - f'{self.NAME}_with_state', # m_size > 0 + for name, has_state in [ + (f'{self.NAME}_with_reinit', False), # m_size == 0 + (f'{self.NAME}_with_state', True), # m_size > 0 ]: self.add_module_cleanup(name) - with self.subTest(name): + with self.subTest(name=name, has_state=has_state): loaded = self.load(name) - reloaded = self.re_load(name, loaded.module) - - if name.endswith("_with_state"): + if has_state: self.addCleanup(loaded.module._clear_module_state) + + reloaded = self.re_load(name, loaded.module) + if has_state: self.addCleanup(reloaded.module._clear_module_state) self.check_common(loaded)