1010from test .support import os_helper
1111from test .support import script_helper
1212from test .support import warnings_helper
13+ import textwrap
1314import unittest
1415import warnings
1516imp = warnings_helper .import_deprecated ('imp' )
1617import _imp
18+ import _testinternalcapi
19+ import _xxsubinterpreters as _interpreters
1720
1821
1922OS_PATH_NAME = os .path .__name__
@@ -251,6 +254,71 @@ def test_issue16421_multiple_modules_in_one_dll(self):
251254 with self .assertRaises (ImportError ):
252255 imp .load_dynamic ('nonexistent' , pathname )
253256
257+ @requires_load_dynamic
258+ def test_singlephase_multiple_interpreters (self ):
259+ # Currently, for every single-phrase init module loaded
260+ # in multiple interpreters, those interpreters share a
261+ # PyModuleDef for that object, which can be a problem.
262+
263+ # This single-phase module has global state, which is shared
264+ # by the interpreters.
265+ import _testsinglephase
266+ name = _testsinglephase .__name__
267+ filename = _testsinglephase .__file__
268+
269+ del sys .modules [name ]
270+ _testsinglephase ._clear_globals ()
271+ _testinternalcapi .clear_extension (name , filename )
272+ init_count = _testsinglephase .initialized_count ()
273+ assert init_count == - 1 , (init_count ,)
274+
275+ def clean_up ():
276+ _testsinglephase ._clear_globals ()
277+ _testinternalcapi .clear_extension (name , filename )
278+ self .addCleanup (clean_up )
279+
280+ interp1 = _interpreters .create (isolated = False )
281+ self .addCleanup (_interpreters .destroy , interp1 )
282+ interp2 = _interpreters .create (isolated = False )
283+ self .addCleanup (_interpreters .destroy , interp2 )
284+
285+ script = textwrap .dedent (f'''
286+ import _testsinglephase
287+
288+ expected = %d
289+ init_count = _testsinglephase.initialized_count()
290+ if init_count != expected:
291+ raise Exception(init_count)
292+
293+ lookedup = _testsinglephase.look_up_self()
294+ if lookedup is not _testsinglephase:
295+ raise Exception((_testsinglephase, lookedup))
296+
297+ # Attrs set in the module init func are in m_copy.
298+ _initialized = _testsinglephase._initialized
299+ initialized = _testsinglephase.initialized()
300+ if _initialized != initialized:
301+ raise Exception((_initialized, initialized))
302+
303+ # Attrs set after loading are not in m_copy.
304+ if hasattr(_testsinglephase, 'spam'):
305+ raise Exception(_testsinglephase.spam)
306+ _testsinglephase.spam = expected
307+ ''' )
308+
309+ # Use an interpreter that gets destroyed right away.
310+ ret = support .run_in_subinterp (script % 1 )
311+ self .assertEqual (ret , 0 )
312+
313+ # The module's init func gets run again.
314+ # The module's globals did not get destroyed.
315+ _interpreters .run_string (interp1 , script % 2 )
316+
317+ # The module's init func is not run again.
318+ # The second interpreter copies the module's m_copy.
319+ # However, globals are still shared.
320+ _interpreters .run_string (interp2 , script % 2 )
321+
254322 @requires_load_dynamic
255323 def test_singlephase_variants (self ):
256324 '''Exercise the most meaningful variants described in Python/import.c.'''
@@ -260,6 +328,11 @@ def test_singlephase_variants(self):
260328 fileobj , pathname , _ = imp .find_module (basename )
261329 fileobj .close ()
262330
331+ def clean_up ():
332+ import _testsinglephase
333+ _testsinglephase ._clear_globals ()
334+ self .addCleanup (clean_up )
335+
263336 modules = {}
264337 def load (name ):
265338 assert name not in modules
0 commit comments