mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	gh-76785: Consolidate Some Interpreter-related Testing Helpers (gh-117485)
This eliminates the duplication of functionally identical helpers in the _testinternalcapi and _xxsubinterpreters modules.
This commit is contained in:
		
							parent
							
								
									f341d6017d
								
							
						
					
					
						commit
						857d3151c9
					
				
					 10 changed files with 527 additions and 313 deletions
				
			
		|  | @ -73,7 +73,7 @@ def __str__(self): | |||
| 
 | ||||
| def create(): | ||||
|     """Return a new (idle) Python interpreter.""" | ||||
|     id = _interpreters.create(isolated=True) | ||||
|     id = _interpreters.create(reqrefs=True) | ||||
|     return Interpreter(id) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -109,13 +109,13 @@ def __new__(cls, id, /): | |||
|             assert hasattr(self, '_ownsref') | ||||
|         except KeyError: | ||||
|             # This may raise InterpreterNotFoundError: | ||||
|             _interpreters._incref(id) | ||||
|             _interpreters.incref(id) | ||||
|             try: | ||||
|                 self = super().__new__(cls) | ||||
|                 self._id = id | ||||
|                 self._ownsref = True | ||||
|             except BaseException: | ||||
|                 _interpreters._deccref(id) | ||||
|                 _interpreters.decref(id) | ||||
|                 raise | ||||
|             _known[id] = self | ||||
|         return self | ||||
|  | @ -142,7 +142,7 @@ def _decref(self): | |||
|             return | ||||
|         self._ownsref = False | ||||
|         try: | ||||
|             _interpreters._decref(self.id) | ||||
|             _interpreters.decref(self.id) | ||||
|         except InterpreterNotFoundError: | ||||
|             pass | ||||
| 
 | ||||
|  |  | |||
|  | @ -584,7 +584,7 @@ def f(): | |||
|     def test_create_daemon_thread(self): | ||||
|         with self.subTest('isolated'): | ||||
|             expected = 'spam spam spam spam spam' | ||||
|             subinterp = interpreters.create(isolated=True) | ||||
|             subinterp = interpreters.create('isolated') | ||||
|             script, file = _captured_script(f""" | ||||
|                 import threading | ||||
|                 def f(): | ||||
|  | @ -604,7 +604,7 @@ def f(): | |||
|             self.assertEqual(out, expected) | ||||
| 
 | ||||
|         with self.subTest('not isolated'): | ||||
|             subinterp = interpreters.create(isolated=False) | ||||
|             subinterp = interpreters.create('legacy') | ||||
|             script, file = _captured_script(""" | ||||
|                 import threading | ||||
|                 def f(): | ||||
|  |  | |||
|  | @ -2204,6 +2204,7 @@ def test_module_state_shared_in_global(self): | |||
|         self.assertEqual(main_attr_id, subinterp_attr_id) | ||||
| 
 | ||||
| 
 | ||||
| @requires_subinterpreters | ||||
| class InterpreterConfigTests(unittest.TestCase): | ||||
| 
 | ||||
|     supported = { | ||||
|  | @ -2277,11 +2278,11 @@ def check(name, expected): | |||
|             expected = self.supported[expected] | ||||
|             args = (name,) if name else () | ||||
| 
 | ||||
|             config1 = _testinternalcapi.new_interp_config(*args) | ||||
|             config1 = _interpreters.new_config(*args) | ||||
|             self.assert_ns_equal(config1, expected) | ||||
|             self.assertIsNot(config1, expected) | ||||
| 
 | ||||
|             config2 = _testinternalcapi.new_interp_config(*args) | ||||
|             config2 = _interpreters.new_config(*args) | ||||
|             self.assert_ns_equal(config2, expected) | ||||
|             self.assertIsNot(config2, expected) | ||||
|             self.assertIsNot(config2, config1) | ||||
|  | @ -2298,7 +2299,7 @@ def test_update_from_dict(self): | |||
|             with self.subTest(f'noop ({name})'): | ||||
|                 expected = vanilla | ||||
|                 overrides = vars(vanilla) | ||||
|                 config = _testinternalcapi.new_interp_config(name, **overrides) | ||||
|                 config = _interpreters.new_config(name, **overrides) | ||||
|                 self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|             with self.subTest(f'change all ({name})'): | ||||
|  | @ -2308,7 +2309,7 @@ def test_update_from_dict(self): | |||
|                         continue | ||||
|                     overrides['gil'] = gil | ||||
|                     expected = types.SimpleNamespace(**overrides) | ||||
|                     config = _testinternalcapi.new_interp_config( | ||||
|                     config = _interpreters.new_config( | ||||
|                                                             name, **overrides) | ||||
|                     self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|  | @ -2324,14 +2325,14 @@ def test_update_from_dict(self): | |||
|                         expected = types.SimpleNamespace( | ||||
|                             **dict(vars(vanilla), **overrides), | ||||
|                         ) | ||||
|                         config = _testinternalcapi.new_interp_config( | ||||
|                         config = _interpreters.new_config( | ||||
|                                                             name, **overrides) | ||||
|                         self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|         with self.subTest('unsupported field'): | ||||
|             for name in self.supported: | ||||
|                 with self.assertRaises(ValueError): | ||||
|                     _testinternalcapi.new_interp_config(name, spam=True) | ||||
|                     _interpreters.new_config(name, spam=True) | ||||
| 
 | ||||
|         # Bad values for bool fields. | ||||
|         for field, value in vars(self.supported['empty']).items(): | ||||
|  | @ -2341,19 +2342,18 @@ def test_update_from_dict(self): | |||
|             for value in [1, '', 'spam', 1.0, None, object()]: | ||||
|                 with self.subTest(f'unsupported value ({field}={value!r})'): | ||||
|                     with self.assertRaises(TypeError): | ||||
|                         _testinternalcapi.new_interp_config(**{field: value}) | ||||
|                         _interpreters.new_config(**{field: value}) | ||||
| 
 | ||||
|         # Bad values for .gil. | ||||
|         for value in [True, 1, 1.0, None, object()]: | ||||
|             with self.subTest(f'unsupported value(gil={value!r})'): | ||||
|                 with self.assertRaises(TypeError): | ||||
|                     _testinternalcapi.new_interp_config(gil=value) | ||||
|                     _interpreters.new_config(gil=value) | ||||
|         for value in ['', 'spam']: | ||||
|             with self.subTest(f'unsupported value (gil={value!r})'): | ||||
|                 with self.assertRaises(ValueError): | ||||
|                     _testinternalcapi.new_interp_config(gil=value) | ||||
|                     _interpreters.new_config(gil=value) | ||||
| 
 | ||||
|     @requires_subinterpreters | ||||
|     def test_interp_init(self): | ||||
|         questionable = [ | ||||
|             # strange | ||||
|  | @ -2412,11 +2412,10 @@ def check(config): | |||
|                 with self.subTest(f'valid: {config}'): | ||||
|                     check(config) | ||||
| 
 | ||||
|     @requires_subinterpreters | ||||
|     def test_get_config(self): | ||||
|         @contextlib.contextmanager | ||||
|         def new_interp(config): | ||||
|             interpid = _testinternalcapi.new_interpreter(config) | ||||
|             interpid = _interpreters.create(config, reqrefs=False) | ||||
|             try: | ||||
|                 yield interpid | ||||
|             finally: | ||||
|  | @ -2426,32 +2425,32 @@ def new_interp(config): | |||
|                     pass | ||||
| 
 | ||||
|         with self.subTest('main'): | ||||
|             expected = _testinternalcapi.new_interp_config('legacy') | ||||
|             expected = _interpreters.new_config('legacy') | ||||
|             expected.gil = 'own' | ||||
|             interpid = _interpreters.get_main() | ||||
|             config = _testinternalcapi.get_interp_config(interpid) | ||||
|             config = _interpreters.get_config(interpid) | ||||
|             self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|         with self.subTest('isolated'): | ||||
|             expected = _testinternalcapi.new_interp_config('isolated') | ||||
|             expected = _interpreters.new_config('isolated') | ||||
|             with new_interp('isolated') as interpid: | ||||
|                 config = _testinternalcapi.get_interp_config(interpid) | ||||
|                 config = _interpreters.get_config(interpid) | ||||
|             self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|         with self.subTest('legacy'): | ||||
|             expected = _testinternalcapi.new_interp_config('legacy') | ||||
|             expected = _interpreters.new_config('legacy') | ||||
|             with new_interp('legacy') as interpid: | ||||
|                 config = _testinternalcapi.get_interp_config(interpid) | ||||
|                 config = _interpreters.get_config(interpid) | ||||
|             self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|         with self.subTest('custom'): | ||||
|             orig = _testinternalcapi.new_interp_config( | ||||
|             orig = _interpreters.new_config( | ||||
|                 'empty', | ||||
|                 use_main_obmalloc=True, | ||||
|                 gil='shared', | ||||
|             ) | ||||
|             with new_interp(orig) as interpid: | ||||
|                 config = _testinternalcapi.get_interp_config(interpid) | ||||
|                 config = _interpreters.get_config(interpid) | ||||
|             self.assert_ns_equal(config, orig) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -2529,14 +2528,19 @@ def test_lookup_destroyed(self): | |||
|         self.assertFalse( | ||||
|             _testinternalcapi.interpreter_exists(interpid)) | ||||
| 
 | ||||
|     def get_refcount_helpers(self): | ||||
|         return ( | ||||
|             _testinternalcapi.get_interpreter_refcount, | ||||
|             (lambda id: _interpreters.incref(id, implieslink=False)), | ||||
|             _interpreters.decref, | ||||
|         ) | ||||
| 
 | ||||
|     def test_linked_lifecycle_does_not_exist(self): | ||||
|         exists = _testinternalcapi.interpreter_exists | ||||
|         is_linked = _testinternalcapi.interpreter_refcount_linked | ||||
|         link = _testinternalcapi.link_interpreter_refcount | ||||
|         unlink = _testinternalcapi.unlink_interpreter_refcount | ||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount | ||||
|         incref = _testinternalcapi.interpreter_incref | ||||
|         decref = _testinternalcapi.interpreter_decref | ||||
|         get_refcount, incref, decref = self.get_refcount_helpers() | ||||
| 
 | ||||
|         with self.subTest('never existed'): | ||||
|             interpid = _testinternalcapi.unused_interpreter_id() | ||||
|  | @ -2578,8 +2582,7 @@ def test_linked_lifecycle_initial(self): | |||
|         get_refcount = _testinternalcapi.get_interpreter_refcount | ||||
| 
 | ||||
|         # A new interpreter will start out not linked, with a refcount of 0. | ||||
|         interpid = _testinternalcapi.new_interpreter() | ||||
|         self.add_interp_cleanup(interpid) | ||||
|         interpid = self.new_interpreter() | ||||
|         linked = is_linked(interpid) | ||||
|         refcount = get_refcount(interpid) | ||||
| 
 | ||||
|  | @ -2589,12 +2592,9 @@ def test_linked_lifecycle_initial(self): | |||
|     def test_linked_lifecycle_never_linked(self): | ||||
|         exists = _testinternalcapi.interpreter_exists | ||||
|         is_linked = _testinternalcapi.interpreter_refcount_linked | ||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount | ||||
|         incref = _testinternalcapi.interpreter_incref | ||||
|         decref = _testinternalcapi.interpreter_decref | ||||
|         get_refcount, incref, decref = self.get_refcount_helpers() | ||||
| 
 | ||||
|         interpid = _testinternalcapi.new_interpreter() | ||||
|         self.add_interp_cleanup(interpid) | ||||
|         interpid = self.new_interpreter() | ||||
| 
 | ||||
|         # Incref will not automatically link it. | ||||
|         incref(interpid) | ||||
|  | @ -2618,8 +2618,7 @@ def test_linked_lifecycle_link_unlink(self): | |||
|         link = _testinternalcapi.link_interpreter_refcount | ||||
|         unlink = _testinternalcapi.unlink_interpreter_refcount | ||||
| 
 | ||||
|         interpid = _testinternalcapi.new_interpreter() | ||||
|         self.add_interp_cleanup(interpid) | ||||
|         interpid = self.new_interpreter() | ||||
| 
 | ||||
|         # Linking at refcount 0 does not destroy the interpreter. | ||||
|         link(interpid) | ||||
|  | @ -2639,12 +2638,9 @@ def test_linked_lifecycle_link_incref_decref(self): | |||
|         exists = _testinternalcapi.interpreter_exists | ||||
|         is_linked = _testinternalcapi.interpreter_refcount_linked | ||||
|         link = _testinternalcapi.link_interpreter_refcount | ||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount | ||||
|         incref = _testinternalcapi.interpreter_incref | ||||
|         decref = _testinternalcapi.interpreter_decref | ||||
|         get_refcount, incref, decref = self.get_refcount_helpers() | ||||
| 
 | ||||
|         interpid = _testinternalcapi.new_interpreter() | ||||
|         self.add_interp_cleanup(interpid) | ||||
|         interpid = self.new_interpreter() | ||||
| 
 | ||||
|         # Linking it will not change the refcount. | ||||
|         link(interpid) | ||||
|  | @ -2666,11 +2662,9 @@ def test_linked_lifecycle_link_incref_decref(self): | |||
|     def test_linked_lifecycle_incref_link(self): | ||||
|         is_linked = _testinternalcapi.interpreter_refcount_linked | ||||
|         link = _testinternalcapi.link_interpreter_refcount | ||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount | ||||
|         incref = _testinternalcapi.interpreter_incref | ||||
|         get_refcount, incref, _ = self.get_refcount_helpers() | ||||
| 
 | ||||
|         interpid = _testinternalcapi.new_interpreter() | ||||
|         self.add_interp_cleanup(interpid) | ||||
|         interpid = self.new_interpreter() | ||||
| 
 | ||||
|         incref(interpid) | ||||
|         self.assertEqual( | ||||
|  | @ -2688,12 +2682,9 @@ def test_linked_lifecycle_link_incref_unlink_decref(self): | |||
|         is_linked = _testinternalcapi.interpreter_refcount_linked | ||||
|         link = _testinternalcapi.link_interpreter_refcount | ||||
|         unlink = _testinternalcapi.unlink_interpreter_refcount | ||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount | ||||
|         incref = _testinternalcapi.interpreter_incref | ||||
|         decref = _testinternalcapi.interpreter_decref | ||||
|         get_refcount, incref, decref = self.get_refcount_helpers() | ||||
| 
 | ||||
|         interpid = _testinternalcapi.new_interpreter() | ||||
|         self.add_interp_cleanup(interpid) | ||||
|         interpid = self.new_interpreter() | ||||
| 
 | ||||
|         link(interpid) | ||||
|         self.assertTrue( | ||||
|  |  | |||
|  | @ -2163,7 +2163,7 @@ def re_load(self, name, mod): | |||
|     # subinterpreters | ||||
| 
 | ||||
|     def add_subinterpreter(self): | ||||
|         interpid = _interpreters.create(isolated=False) | ||||
|         interpid = _interpreters.create('legacy') | ||||
|         def ensure_destroyed(): | ||||
|             try: | ||||
|                 _interpreters.destroy(interpid) | ||||
|  |  | |||
|  | @ -656,7 +656,7 @@ def test_magic_number(self): | |||
| class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase): | ||||
| 
 | ||||
|     def run_with_own_gil(self, script): | ||||
|         interpid = _interpreters.create(isolated=True) | ||||
|         interpid = _interpreters.create('isolated') | ||||
|         def ensure_destroyed(): | ||||
|             try: | ||||
|                 _interpreters.destroy(interpid) | ||||
|  | @ -669,7 +669,7 @@ def ensure_destroyed(): | |||
|                 raise ImportError(excsnap.msg) | ||||
| 
 | ||||
|     def run_with_shared_gil(self, script): | ||||
|         interpid = _interpreters.create(isolated=False) | ||||
|         interpid = _interpreters.create('legacy') | ||||
|         def ensure_destroyed(): | ||||
|             try: | ||||
|                 _interpreters.destroy(interpid) | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| import os | ||||
| import pickle | ||||
| import threading | ||||
| from textwrap import dedent | ||||
| import threading | ||||
| import types | ||||
| import unittest | ||||
| 
 | ||||
| from test import support | ||||
| from test.support import import_helper | ||||
| # Raise SkipTest if subinterpreters not supported. | ||||
| import_helper.import_module('_xxsubinterpreters') | ||||
| _interpreters = import_helper.import_module('_xxsubinterpreters') | ||||
| from test.support import interpreters | ||||
| from test.support.interpreters import InterpreterNotFoundError | ||||
| from .utils import _captured_script, _run_output, _running, TestBase | ||||
|  | @ -932,6 +933,212 @@ class SubBytes(bytes): | |||
|                     interpreters.is_shareable(obj)) | ||||
| 
 | ||||
| 
 | ||||
| class LowLevelTests(TestBase): | ||||
| 
 | ||||
|     # The behaviors in the low-level module are important in as much | ||||
|     # as they are exercised by the high-level module.  Therefore the | ||||
|     # most important testing happens in the high-level tests. | ||||
|     # These low-level tests cover corner cases that are not | ||||
|     # encountered by the high-level module, thus they | ||||
|     # mostly shouldn't matter as much. | ||||
| 
 | ||||
|     def test_new_config(self): | ||||
|         # This test overlaps with | ||||
|         # test.test_capi.test_misc.InterpreterConfigTests. | ||||
| 
 | ||||
|         default = _interpreters.new_config('isolated') | ||||
|         with self.subTest('no arg'): | ||||
|             config = _interpreters.new_config() | ||||
|             self.assert_ns_equal(config, default) | ||||
|             self.assertIsNot(config, default) | ||||
| 
 | ||||
|         with self.subTest('default'): | ||||
|             config1 = _interpreters.new_config('default') | ||||
|             self.assert_ns_equal(config1, default) | ||||
|             self.assertIsNot(config1, default) | ||||
| 
 | ||||
|             config2 = _interpreters.new_config('default') | ||||
|             self.assert_ns_equal(config2, config1) | ||||
|             self.assertIsNot(config2, config1) | ||||
| 
 | ||||
|         for arg in ['', 'default']: | ||||
|             with self.subTest(f'default ({arg!r})'): | ||||
|                 config = _interpreters.new_config(arg) | ||||
|                 self.assert_ns_equal(config, default) | ||||
|                 self.assertIsNot(config, default) | ||||
| 
 | ||||
|         supported = { | ||||
|             'isolated': types.SimpleNamespace( | ||||
|                 use_main_obmalloc=False, | ||||
|                 allow_fork=False, | ||||
|                 allow_exec=False, | ||||
|                 allow_threads=True, | ||||
|                 allow_daemon_threads=False, | ||||
|                 check_multi_interp_extensions=True, | ||||
|                 gil='own', | ||||
|             ), | ||||
|             'legacy': types.SimpleNamespace( | ||||
|                 use_main_obmalloc=True, | ||||
|                 allow_fork=True, | ||||
|                 allow_exec=True, | ||||
|                 allow_threads=True, | ||||
|                 allow_daemon_threads=True, | ||||
|                 check_multi_interp_extensions=False, | ||||
|                 gil='shared', | ||||
|             ), | ||||
|             'empty': types.SimpleNamespace( | ||||
|                 use_main_obmalloc=False, | ||||
|                 allow_fork=False, | ||||
|                 allow_exec=False, | ||||
|                 allow_threads=False, | ||||
|                 allow_daemon_threads=False, | ||||
|                 check_multi_interp_extensions=False, | ||||
|                 gil='default', | ||||
|             ), | ||||
|         } | ||||
|         gil_supported = ['default', 'shared', 'own'] | ||||
| 
 | ||||
|         for name, vanilla in supported.items(): | ||||
|             with self.subTest(f'supported ({name})'): | ||||
|                 expected = vanilla | ||||
|                 config1 = _interpreters.new_config(name) | ||||
|                 self.assert_ns_equal(config1, expected) | ||||
|                 self.assertIsNot(config1, expected) | ||||
| 
 | ||||
|                 config2 = _interpreters.new_config(name) | ||||
|                 self.assert_ns_equal(config2, config1) | ||||
|                 self.assertIsNot(config2, config1) | ||||
| 
 | ||||
|             with self.subTest(f'noop override ({name})'): | ||||
|                 expected = vanilla | ||||
|                 overrides = vars(vanilla) | ||||
|                 config = _interpreters.new_config(name, **overrides) | ||||
|                 self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|             with self.subTest(f'override all ({name})'): | ||||
|                 overrides = {k: not v for k, v in vars(vanilla).items()} | ||||
|                 for gil in gil_supported: | ||||
|                     if vanilla.gil == gil: | ||||
|                         continue | ||||
|                     overrides['gil'] = gil | ||||
|                     expected = types.SimpleNamespace(**overrides) | ||||
|                     config = _interpreters.new_config(name, **overrides) | ||||
|                     self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|             # Override individual fields. | ||||
|             for field, old in vars(vanilla).items(): | ||||
|                 if field == 'gil': | ||||
|                     values = [v for v in gil_supported if v != old] | ||||
|                 else: | ||||
|                     values = [not old] | ||||
|                 for val in values: | ||||
|                     with self.subTest(f'{name}.{field} ({old!r} -> {val!r})'): | ||||
|                         overrides = {field: val} | ||||
|                         expected = types.SimpleNamespace( | ||||
|                             **dict(vars(vanilla), **overrides), | ||||
|                         ) | ||||
|                         config = _interpreters.new_config(name, **overrides) | ||||
|                         self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|         with self.subTest('extra override'): | ||||
|             with self.assertRaises(ValueError): | ||||
|                 _interpreters.new_config(spam=True) | ||||
| 
 | ||||
|         # Bad values for bool fields. | ||||
|         for field, value in vars(supported['empty']).items(): | ||||
|             if field == 'gil': | ||||
|                 continue | ||||
|             assert isinstance(value, bool) | ||||
|             for value in [1, '', 'spam', 1.0, None, object()]: | ||||
|                 with self.subTest(f'bad override ({field}={value!r})'): | ||||
|                     with self.assertRaises(TypeError): | ||||
|                         _interpreters.new_config(**{field: value}) | ||||
| 
 | ||||
|         # Bad values for .gil. | ||||
|         for value in [True, 1, 1.0, None, object()]: | ||||
|             with self.subTest(f'bad override (gil={value!r})'): | ||||
|                 with self.assertRaises(TypeError): | ||||
|                     _interpreters.new_config(gil=value) | ||||
|         for value in ['', 'spam']: | ||||
|             with self.subTest(f'bad override (gil={value!r})'): | ||||
|                 with self.assertRaises(ValueError): | ||||
|                     _interpreters.new_config(gil=value) | ||||
| 
 | ||||
|     def test_get_config(self): | ||||
|         # This test overlaps with | ||||
|         # test.test_capi.test_misc.InterpreterConfigTests. | ||||
| 
 | ||||
|         with self.subTest('main'): | ||||
|             expected = _interpreters.new_config('legacy') | ||||
|             expected.gil = 'own' | ||||
|             interpid = _interpreters.get_main() | ||||
|             config = _interpreters.get_config(interpid) | ||||
|             self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|         with self.subTest('isolated'): | ||||
|             expected = _interpreters.new_config('isolated') | ||||
|             interpid = _interpreters.create('isolated') | ||||
|             config = _interpreters.get_config(interpid) | ||||
|             self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|         with self.subTest('legacy'): | ||||
|             expected = _interpreters.new_config('legacy') | ||||
|             interpid = _interpreters.create('legacy') | ||||
|             config = _interpreters.get_config(interpid) | ||||
|             self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|     def test_create(self): | ||||
|         isolated = _interpreters.new_config('isolated') | ||||
|         legacy = _interpreters.new_config('legacy') | ||||
|         default = isolated | ||||
| 
 | ||||
|         with self.subTest('no arg'): | ||||
|             interpid = _interpreters.create() | ||||
|             config = _interpreters.get_config(interpid) | ||||
|             self.assert_ns_equal(config, default) | ||||
| 
 | ||||
|         with self.subTest('arg: None'): | ||||
|             interpid = _interpreters.create(None) | ||||
|             config = _interpreters.get_config(interpid) | ||||
|             self.assert_ns_equal(config, default) | ||||
| 
 | ||||
|         with self.subTest('arg: \'empty\''): | ||||
|             with self.assertRaises(RuntimeError): | ||||
|                 # The "empty" config isn't viable on its own. | ||||
|                 _interpreters.create('empty') | ||||
| 
 | ||||
|         for arg, expected in { | ||||
|             '': default, | ||||
|             'default': default, | ||||
|             'isolated': isolated, | ||||
|             'legacy': legacy, | ||||
|         }.items(): | ||||
|             with self.subTest(f'str arg: {arg!r}'): | ||||
|                 interpid = _interpreters.create(arg) | ||||
|                 config = _interpreters.get_config(interpid) | ||||
|                 self.assert_ns_equal(config, expected) | ||||
| 
 | ||||
|         with self.subTest('custom'): | ||||
|             orig = _interpreters.new_config('empty') | ||||
|             orig.use_main_obmalloc = True | ||||
|             orig.gil = 'shared' | ||||
|             interpid = _interpreters.create(orig) | ||||
|             config = _interpreters.get_config(interpid) | ||||
|             self.assert_ns_equal(config, orig) | ||||
| 
 | ||||
|         with self.subTest('missing fields'): | ||||
|             orig = _interpreters.new_config() | ||||
|             del orig.gil | ||||
|             with self.assertRaises(ValueError): | ||||
|                 _interpreters.create(orig) | ||||
| 
 | ||||
|         with self.subTest('extra fields'): | ||||
|             orig = _interpreters.new_config() | ||||
|             orig.spam = True | ||||
|             with self.assertRaises(ValueError): | ||||
|                 _interpreters.create(orig) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     # Test needs to be a package, so we can do relative imports. | ||||
|     unittest.main() | ||||
|  |  | |||
|  | @ -28,9 +28,9 @@ def tearDown(self): | |||
| 
 | ||||
| class LowLevelTests(TestBase): | ||||
| 
 | ||||
|     # The behaviors in the low-level module is important in as much | ||||
|     # as it is exercised by the high-level module.  Therefore the | ||||
|     # most # important testing happens in the high-level tests. | ||||
|     # The behaviors in the low-level module are important in as much | ||||
|     # as they are exercised by the high-level module.  Therefore the | ||||
|     # most important testing happens in the high-level tests. | ||||
|     # These low-level tests cover corner cases that are not | ||||
|     # encountered by the high-level module, thus they | ||||
|     # mostly shouldn't matter as much. | ||||
|  |  | |||
|  | @ -68,6 +68,9 @@ def run(): | |||
| 
 | ||||
| class TestBase(unittest.TestCase): | ||||
| 
 | ||||
|     def tearDown(self): | ||||
|         clean_up_interpreters() | ||||
| 
 | ||||
|     def pipe(self): | ||||
|         def ensure_closed(fd): | ||||
|             try: | ||||
|  | @ -156,5 +159,19 @@ def assert_python_failure(self, *argv): | |||
|         self.assertNotEqual(exitcode, 0) | ||||
|         return stdout, stderr | ||||
| 
 | ||||
|     def tearDown(self): | ||||
|         clean_up_interpreters() | ||||
|     def assert_ns_equal(self, ns1, ns2, msg=None): | ||||
|         # This is mostly copied from TestCase.assertDictEqual. | ||||
|         self.assertEqual(type(ns1), type(ns2)) | ||||
|         if ns1 == ns2: | ||||
|             return | ||||
| 
 | ||||
|         import difflib | ||||
|         import pprint | ||||
|         from unittest.util import _common_shorten_repr | ||||
|         standardMsg = '%s != %s' % _common_shorten_repr(ns1, ns2) | ||||
|         diff = ('\n' + '\n'.join(difflib.ndiff( | ||||
|                        pprint.pformat(vars(ns1)).splitlines(), | ||||
|                        pprint.pformat(vars(ns2)).splitlines()))) | ||||
|         diff = f'namespace({diff})' | ||||
|         standardMsg = self._truncateMessage(standardMsg, diff) | ||||
|         self.fail(self._formatMessage(msg, standardMsg)) | ||||
|  |  | |||
|  | @ -23,7 +23,6 @@ | |||
| #include "pycore_initconfig.h"    // _Py_GetConfigsAsDict() | ||||
| #include "pycore_interp.h"        // _PyInterpreterState_GetConfigCopy() | ||||
| #include "pycore_long.h"          // _PyLong_Sign() | ||||
| #include "pycore_namespace.h"     // _PyNamespace_New() | ||||
| #include "pycore_object.h"        // _PyObject_IsFreed() | ||||
| #include "pycore_optimizer.h"     // _Py_UopsSymbol, etc. | ||||
| #include "pycore_pathconfig.h"    // _PyPathConfig_ClearGlobal() | ||||
|  | @ -831,6 +830,7 @@ _testinternalcapi_assemble_code_object_impl(PyObject *module, | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| // Maybe this could be replaced by get_interpreter_config()?
 | ||||
| static PyObject * | ||||
| get_interp_settings(PyObject *self, PyObject *args) | ||||
| { | ||||
|  | @ -1357,129 +1357,6 @@ dict_getitem_knownhash(PyObject *self, PyObject *args) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| static int | ||||
| init_named_interp_config(PyInterpreterConfig *config, const char *name) | ||||
| { | ||||
|     if (name == NULL) { | ||||
|         name = "isolated"; | ||||
|     } | ||||
| 
 | ||||
|     if (strcmp(name, "isolated") == 0) { | ||||
|         *config = (PyInterpreterConfig)_PyInterpreterConfig_INIT; | ||||
|     } | ||||
|     else if (strcmp(name, "legacy") == 0) { | ||||
|         *config = (PyInterpreterConfig)_PyInterpreterConfig_LEGACY_INIT; | ||||
|     } | ||||
|     else if (strcmp(name, "empty") == 0) { | ||||
|         *config = (PyInterpreterConfig){0}; | ||||
|     } | ||||
|     else { | ||||
|         PyErr_Format(PyExc_ValueError, | ||||
|                      "unsupported config name '%s'", name); | ||||
|         return -1; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static PyObject * | ||||
| new_interp_config(PyObject *self, PyObject *args, PyObject *kwds) | ||||
| { | ||||
|     const char *name = NULL; | ||||
|     if (!PyArg_ParseTuple(args, "|s:new_config", &name)) { | ||||
|         return NULL; | ||||
|     } | ||||
|     PyObject *overrides = kwds; | ||||
| 
 | ||||
|     if (name == NULL) { | ||||
|         name = "isolated"; | ||||
|     } | ||||
| 
 | ||||
|     PyInterpreterConfig config; | ||||
|     if (init_named_interp_config(&config, name) < 0) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     if (overrides != NULL && PyDict_GET_SIZE(overrides) > 0) { | ||||
|         if (_PyInterpreterConfig_UpdateFromDict(&config, overrides) < 0) { | ||||
|             return NULL; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     PyObject *dict = _PyInterpreterConfig_AsDict(&config); | ||||
|     if (dict == NULL) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     PyObject *configobj = _PyNamespace_New(dict); | ||||
|     Py_DECREF(dict); | ||||
|     return configobj; | ||||
| } | ||||
| 
 | ||||
| static PyObject * | ||||
| get_interp_config(PyObject *self, PyObject *args, PyObject *kwds) | ||||
| { | ||||
|     static char *kwlist[] = {"id", NULL}; | ||||
|     PyObject *idobj = NULL; | ||||
|     if (!PyArg_ParseTupleAndKeywords(args, kwds, | ||||
|                                      "O:get_config", kwlist, &idobj)) | ||||
|     { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     PyInterpreterState *interp; | ||||
|     if (idobj == NULL) { | ||||
|         interp = PyInterpreterState_Get(); | ||||
|     } | ||||
|     else { | ||||
|         interp = _PyInterpreterState_LookUpIDObject(idobj); | ||||
|         if (interp == NULL) { | ||||
|             return NULL; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     PyInterpreterConfig config; | ||||
|     if (_PyInterpreterConfig_InitFromState(&config, interp) < 0) { | ||||
|         return NULL; | ||||
|     } | ||||
|     PyObject *dict = _PyInterpreterConfig_AsDict(&config); | ||||
|     if (dict == NULL) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     PyObject *configobj = _PyNamespace_New(dict); | ||||
|     Py_DECREF(dict); | ||||
|     return configobj; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| interp_config_from_object(PyObject *configobj, PyInterpreterConfig *config) | ||||
| { | ||||
|     if (configobj == NULL || configobj == Py_None) { | ||||
|         if (init_named_interp_config(config, NULL) < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
|     else if (PyUnicode_Check(configobj)) { | ||||
|         if (init_named_interp_config(config, PyUnicode_AsUTF8(configobj)) < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         PyObject *dict = PyObject_GetAttrString(configobj, "__dict__"); | ||||
|         if (dict == NULL) { | ||||
|             PyErr_Format(PyExc_TypeError, "bad config %R", configobj); | ||||
|             return -1; | ||||
|         } | ||||
|         int res = _PyInterpreterConfig_InitFromDict(config, dict); | ||||
|         Py_DECREF(dict); | ||||
|         if (res < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* To run some code in a sub-interpreter. */ | ||||
| static PyObject * | ||||
| run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) | ||||
|  | @ -1495,7 +1372,14 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) | |||
|     } | ||||
| 
 | ||||
|     PyInterpreterConfig config; | ||||
|     if (interp_config_from_object(configobj, &config) < 0) { | ||||
|     PyObject *dict = PyObject_GetAttrString(configobj, "__dict__"); | ||||
|     if (dict == NULL) { | ||||
|         PyErr_Format(PyExc_TypeError, "bad config %R", configobj); | ||||
|         return NULL; | ||||
|     } | ||||
|     int res = _PyInterpreterConfig_InitFromDict(&config, dict); | ||||
|     Py_DECREF(dict); | ||||
|     if (res < 0) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|  | @ -1546,58 +1430,6 @@ unused_interpreter_id(PyObject *self, PyObject *Py_UNUSED(ignored)) | |||
|     return PyLong_FromLongLong(interpid); | ||||
| } | ||||
| 
 | ||||
| static PyObject * | ||||
| new_interpreter(PyObject *self, PyObject *args) | ||||
| { | ||||
|     PyObject *configobj = NULL; | ||||
|     if (!PyArg_ParseTuple(args, "|O:new_interpreter", &configobj)) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     PyInterpreterConfig config; | ||||
|     if (interp_config_from_object(configobj, &config) < 0) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     // Unlike _interpreters.create(), we do not automatically link
 | ||||
|     // the interpreter to its refcount.
 | ||||
|     PyThreadState *save_tstate = PyThreadState_Get(); | ||||
|     PyThreadState *tstate = NULL; | ||||
|     PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config); | ||||
|     PyThreadState_Swap(save_tstate); | ||||
|     if (PyStatus_Exception(status)) { | ||||
|         _PyErr_SetFromPyStatus(status); | ||||
|         return NULL; | ||||
|     } | ||||
|     PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); | ||||
| 
 | ||||
|     if (_PyInterpreterState_IDInitref(interp) < 0) { | ||||
|         goto error; | ||||
|     } | ||||
| 
 | ||||
|     int64_t interpid = PyInterpreterState_GetID(interp); | ||||
|     if (interpid < 0) { | ||||
|         goto error; | ||||
|     } | ||||
|     PyObject *idobj = PyLong_FromLongLong(interpid); | ||||
|     if (idobj == NULL) { | ||||
|         goto error; | ||||
|     } | ||||
| 
 | ||||
|     PyThreadState_Swap(tstate); | ||||
|     PyThreadState_Clear(tstate); | ||||
|     PyThreadState_Swap(save_tstate); | ||||
|     PyThreadState_Delete(tstate); | ||||
| 
 | ||||
|     return idobj; | ||||
| 
 | ||||
| error: | ||||
|     save_tstate = PyThreadState_Swap(tstate); | ||||
|     Py_EndInterpreter(tstate); | ||||
|     PyThreadState_Swap(save_tstate); | ||||
|     return NULL; | ||||
| } | ||||
| 
 | ||||
| static PyObject * | ||||
| interpreter_exists(PyObject *self, PyObject *idobj) | ||||
| { | ||||
|  | @ -1660,28 +1492,6 @@ interpreter_refcount_linked(PyObject *self, PyObject *idobj) | |||
|     Py_RETURN_FALSE; | ||||
| } | ||||
| 
 | ||||
| static PyObject * | ||||
| interpreter_incref(PyObject *self, PyObject *idobj) | ||||
| { | ||||
|     PyInterpreterState *interp = _PyInterpreterState_LookUpIDObject(idobj); | ||||
|     if (interp == NULL) { | ||||
|         return NULL; | ||||
|     } | ||||
|     _PyInterpreterState_IDIncref(interp); | ||||
|     Py_RETURN_NONE; | ||||
| } | ||||
| 
 | ||||
| static PyObject * | ||||
| interpreter_decref(PyObject *self, PyObject *idobj) | ||||
| { | ||||
|     PyInterpreterState *interp = _PyInterpreterState_LookUpIDObject(idobj); | ||||
|     if (interp == NULL) { | ||||
|         return NULL; | ||||
|     } | ||||
|     _PyInterpreterState_IDDecref(interp); | ||||
|     Py_RETURN_NONE; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void | ||||
| _xid_capsule_destructor(PyObject *capsule) | ||||
|  | @ -1928,23 +1738,16 @@ static PyMethodDef module_functions[] = { | |||
|     {"get_object_dict_values", get_object_dict_values, METH_O}, | ||||
|     {"hamt", new_hamt, METH_NOARGS}, | ||||
|     {"dict_getitem_knownhash",  dict_getitem_knownhash,          METH_VARARGS}, | ||||
|     {"new_interp_config", _PyCFunction_CAST(new_interp_config), | ||||
|      METH_VARARGS | METH_KEYWORDS}, | ||||
|     {"get_interp_config", _PyCFunction_CAST(get_interp_config), | ||||
|      METH_VARARGS | METH_KEYWORDS}, | ||||
|     {"run_in_subinterp_with_config", | ||||
|      _PyCFunction_CAST(run_in_subinterp_with_config), | ||||
|      METH_VARARGS | METH_KEYWORDS}, | ||||
|     {"normalize_interp_id", normalize_interp_id, METH_O}, | ||||
|     {"unused_interpreter_id", unused_interpreter_id, METH_NOARGS}, | ||||
|     {"new_interpreter", new_interpreter, METH_VARARGS}, | ||||
|     {"interpreter_exists", interpreter_exists, METH_O}, | ||||
|     {"get_interpreter_refcount", get_interpreter_refcount, METH_O}, | ||||
|     {"link_interpreter_refcount", link_interpreter_refcount,     METH_O}, | ||||
|     {"unlink_interpreter_refcount", unlink_interpreter_refcount, METH_O}, | ||||
|     {"interpreter_refcount_linked", interpreter_refcount_linked, METH_O}, | ||||
|     {"interpreter_incref", interpreter_incref, METH_O}, | ||||
|     {"interpreter_decref", interpreter_decref, METH_O}, | ||||
|     {"compile_perf_trampoline_entry", compile_perf_trampoline_entry, METH_VARARGS}, | ||||
|     {"perf_trampoline_set_persist_after_fork", perf_trampoline_set_persist_after_fork, METH_VARARGS}, | ||||
|     {"get_crossinterp_data",    get_crossinterp_data,            METH_VARARGS}, | ||||
|  |  | |||
|  | @ -12,8 +12,10 @@ | |||
| #include "pycore_initconfig.h"    // _PyErr_SetFromPyStatus() | ||||
| #include "pycore_long.h"          // _PyLong_IsNegative() | ||||
| #include "pycore_modsupport.h"    // _PyArg_BadArgument() | ||||
| #include "pycore_namespace.h"     // _PyNamespace_New() | ||||
| #include "pycore_pybuffer.h"      // _PyBuffer_ReleaseInInterpreterAndRawFree() | ||||
| #include "pycore_pyerrors.h"      // _Py_excinfo | ||||
| #include "pycore_pylifecycle.h"   // _PyInterpreterConfig_AsDict() | ||||
| #include "pycore_pystate.h"       // _PyInterpreterState_SetRunningMain() | ||||
| 
 | ||||
| #include "marshal.h"              // PyMarshal_ReadObjectFromString() | ||||
|  | @ -367,6 +369,115 @@ get_code_str(PyObject *arg, Py_ssize_t *len_p, PyObject **bytes_p, int *flags_p) | |||
| 
 | ||||
| /* interpreter-specific code ************************************************/ | ||||
| 
 | ||||
| static int | ||||
| init_named_config(PyInterpreterConfig *config, const char *name) | ||||
| { | ||||
|     if (name == NULL | ||||
|             || strcmp(name, "") == 0 | ||||
|             || strcmp(name, "default") == 0) | ||||
|     { | ||||
|         name = "isolated"; | ||||
|     } | ||||
| 
 | ||||
|     if (strcmp(name, "isolated") == 0) { | ||||
|         *config = (PyInterpreterConfig)_PyInterpreterConfig_INIT; | ||||
|     } | ||||
|     else if (strcmp(name, "legacy") == 0) { | ||||
|         *config = (PyInterpreterConfig)_PyInterpreterConfig_LEGACY_INIT; | ||||
|     } | ||||
|     else if (strcmp(name, "empty") == 0) { | ||||
|         *config = (PyInterpreterConfig){0}; | ||||
|     } | ||||
|     else { | ||||
|         PyErr_Format(PyExc_ValueError, | ||||
|                      "unsupported config name '%s'", name); | ||||
|         return -1; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| config_from_object(PyObject *configobj, PyInterpreterConfig *config) | ||||
| { | ||||
|     if (configobj == NULL || configobj == Py_None) { | ||||
|         if (init_named_config(config, NULL) < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
|     else if (PyUnicode_Check(configobj)) { | ||||
|         if (init_named_config(config, PyUnicode_AsUTF8(configobj)) < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         PyObject *dict = PyObject_GetAttrString(configobj, "__dict__"); | ||||
|         if (dict == NULL) { | ||||
|             PyErr_Format(PyExc_TypeError, "bad config %R", configobj); | ||||
|             return -1; | ||||
|         } | ||||
|         int res = _PyInterpreterConfig_InitFromDict(config, dict); | ||||
|         Py_DECREF(dict); | ||||
|         if (res < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static PyInterpreterState * | ||||
| new_interpreter(PyInterpreterConfig *config, PyObject **p_idobj,  PyThreadState **p_tstate) | ||||
| { | ||||
|     PyThreadState *save_tstate = PyThreadState_Get(); | ||||
|     assert(save_tstate != NULL); | ||||
|     PyThreadState *tstate = NULL; | ||||
|     // XXX Possible GILState issues?
 | ||||
|     PyStatus status = Py_NewInterpreterFromConfig(&tstate, config); | ||||
|     PyThreadState_Swap(save_tstate); | ||||
|     if (PyStatus_Exception(status)) { | ||||
|         /* Since no new thread state was created, there is no exception to
 | ||||
|            propagate; raise a fresh one after swapping in the old thread | ||||
|            state. */ | ||||
|         _PyErr_SetFromPyStatus(status); | ||||
|         return NULL; | ||||
|     } | ||||
|     assert(tstate != NULL); | ||||
|     PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); | ||||
| 
 | ||||
|     if (_PyInterpreterState_IDInitref(interp) < 0) { | ||||
|         goto error; | ||||
|     } | ||||
| 
 | ||||
|     if (p_idobj != NULL) { | ||||
|         // We create the object using the original interpreter.
 | ||||
|         PyObject *idobj = get_interpid_obj(interp); | ||||
|         if (idobj == NULL) { | ||||
|             goto error; | ||||
|         } | ||||
|         *p_idobj = idobj; | ||||
|     } | ||||
| 
 | ||||
|     if (p_tstate != NULL) { | ||||
|         *p_tstate = tstate; | ||||
|     } | ||||
|     else { | ||||
|         PyThreadState_Swap(tstate); | ||||
|         PyThreadState_Clear(tstate); | ||||
|         PyThreadState_Swap(save_tstate); | ||||
|         PyThreadState_Delete(tstate); | ||||
|     } | ||||
| 
 | ||||
|     return interp; | ||||
| 
 | ||||
| error: | ||||
|     // XXX Possible GILState issues?
 | ||||
|     save_tstate = PyThreadState_Swap(tstate); | ||||
|     Py_EndInterpreter(tstate); | ||||
|     PyThreadState_Swap(save_tstate); | ||||
|     return NULL; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static int | ||||
| _run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags) | ||||
| { | ||||
|  | @ -436,64 +547,98 @@ _run_in_interpreter(PyInterpreterState *interp, | |||
| /* module level code ********************************************************/ | ||||
| 
 | ||||
| static PyObject * | ||||
| interp_create(PyObject *self, PyObject *args, PyObject *kwds) | ||||
| interp_new_config(PyObject *self, PyObject *args, PyObject *kwds) | ||||
| { | ||||
|     const char *name = NULL; | ||||
|     if (!PyArg_ParseTuple(args, "|s:" MODULE_NAME_STR ".new_config", | ||||
|                           &name)) | ||||
|     { | ||||
|         return NULL; | ||||
|     } | ||||
|     PyObject *overrides = kwds; | ||||
| 
 | ||||
|     static char *kwlist[] = {"isolated", NULL}; | ||||
|     int isolated = 1; | ||||
|     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|$i:create", kwlist, | ||||
|                                      &isolated)) { | ||||
|     PyInterpreterConfig config; | ||||
|     if (init_named_config(&config, name) < 0) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     // Create and initialize the new interpreter.
 | ||||
|     PyThreadState *save_tstate = PyThreadState_Get(); | ||||
|     assert(save_tstate != NULL); | ||||
|     const PyInterpreterConfig config = isolated | ||||
|         ? (PyInterpreterConfig)_PyInterpreterConfig_INIT | ||||
|         : (PyInterpreterConfig)_PyInterpreterConfig_LEGACY_INIT; | ||||
|     if (overrides != NULL && PyDict_GET_SIZE(overrides) > 0) { | ||||
|         if (_PyInterpreterConfig_UpdateFromDict(&config, overrides) < 0) { | ||||
|             return NULL; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // XXX Possible GILState issues?
 | ||||
|     PyThreadState *tstate = NULL; | ||||
|     PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config); | ||||
|     PyThreadState_Swap(save_tstate); | ||||
|     if (PyStatus_Exception(status)) { | ||||
|         /* Since no new thread state was created, there is no exception to
 | ||||
|            propagate; raise a fresh one after swapping in the old thread | ||||
|            state. */ | ||||
|         _PyErr_SetFromPyStatus(status); | ||||
|     PyObject *dict = _PyInterpreterConfig_AsDict(&config); | ||||
|     if (dict == NULL) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     PyObject *configobj = _PyNamespace_New(dict); | ||||
|     Py_DECREF(dict); | ||||
|     return configobj; | ||||
| } | ||||
| 
 | ||||
| PyDoc_STRVAR(new_config_doc, | ||||
| "new_config(name='isolated', /, **overrides) -> type.SimpleNamespace\n\
 | ||||
| \n\ | ||||
| Return a representation of a new PyInterpreterConfig.\n\ | ||||
| \n\ | ||||
| The name determines the initial values of the config.  Supported named\n\ | ||||
| configs are: default, isolated, legacy, and empty.\n\ | ||||
| \n\ | ||||
| Any keyword arguments are set on the corresponding config fields,\n\ | ||||
| overriding the initial values."); | ||||
| 
 | ||||
| 
 | ||||
| static PyObject * | ||||
| interp_create(PyObject *self, PyObject *args, PyObject *kwds) | ||||
| { | ||||
|     static char *kwlist[] = {"config", "reqrefs", NULL}; | ||||
|     PyObject *configobj = NULL; | ||||
|     int reqrefs = 0; | ||||
|     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O$p:create", kwlist, | ||||
|                                      &configobj, &reqrefs)) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     PyInterpreterConfig config; | ||||
|     if (config_from_object(configobj, &config) < 0) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     PyObject *idobj = NULL; | ||||
|     PyInterpreterState *interp = new_interpreter(&config, &idobj, NULL); | ||||
|     if (interp == NULL) { | ||||
|         // XXX Move the chained exception to interpreters.create()?
 | ||||
|         PyObject *exc = PyErr_GetRaisedException(); | ||||
|         assert(exc != NULL); | ||||
|         PyErr_SetString(PyExc_RuntimeError, "interpreter creation failed"); | ||||
|         _PyErr_ChainExceptions1(exc); | ||||
|         return NULL; | ||||
|     } | ||||
|     assert(tstate != NULL); | ||||
| 
 | ||||
|     PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); | ||||
|     PyObject *idobj = get_interpid_obj(interp); | ||||
|     if (idobj == NULL) { | ||||
|         // XXX Possible GILState issues?
 | ||||
|         save_tstate = PyThreadState_Swap(tstate); | ||||
|         Py_EndInterpreter(tstate); | ||||
|         PyThreadState_Swap(save_tstate); | ||||
|         return NULL; | ||||
|     if (reqrefs) { | ||||
|         // Decref to 0 will destroy the interpreter.
 | ||||
|         _PyInterpreterState_RequireIDRef(interp, 1); | ||||
|     } | ||||
| 
 | ||||
|     PyThreadState_Swap(tstate); | ||||
|     PyThreadState_Clear(tstate); | ||||
|     PyThreadState_Swap(save_tstate); | ||||
|     PyThreadState_Delete(tstate); | ||||
| 
 | ||||
|     _PyInterpreterState_RequireIDRef(interp, 1); | ||||
|     return idobj; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| PyDoc_STRVAR(create_doc, | ||||
| "create() -> ID\n\
 | ||||
| "create([config], *, reqrefs=False) -> ID\n\
 | ||||
| \n\ | ||||
| Create a new interpreter and return a unique generated ID.\n\ | ||||
| \n\ | ||||
| The caller is responsible for destroying the interpreter before exiting."); | ||||
| The caller is responsible for destroying the interpreter before exiting,\n\ | ||||
| typically by using _interpreters.destroy().  This can be managed \n\ | ||||
| automatically by passing \"reqrefs=True\" and then using _incref() and\n\
 | ||||
| _decref()` appropriately.\n\ | ||||
| \n\ | ||||
| \"config\" must be a valid interpreter config or the name of a\n\
 | ||||
| predefined config (\"isolated\" or \"legacy\").  The default\n\
 | ||||
| is \"isolated\"."); | ||||
| 
 | ||||
| 
 | ||||
| static PyObject * | ||||
|  | @ -1008,12 +1153,57 @@ Return whether or not the identified interpreter is running."); | |||
| 
 | ||||
| 
 | ||||
| static PyObject * | ||||
| interp_incref(PyObject *self, PyObject *args, PyObject *kwds) | ||||
| interp_get_config(PyObject *self, PyObject *args, PyObject *kwds) | ||||
| { | ||||
|     static char *kwlist[] = {"id", NULL}; | ||||
|     PyObject *id; | ||||
|     PyObject *idobj = NULL; | ||||
|     if (!PyArg_ParseTupleAndKeywords(args, kwds, | ||||
|                                      "O:_incref", kwlist, &id)) { | ||||
|                                      "O:get_config", kwlist, &idobj)) | ||||
|     { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     PyInterpreterState *interp; | ||||
|     if (idobj == NULL) { | ||||
|         interp = PyInterpreterState_Get(); | ||||
|     } | ||||
|     else { | ||||
|         interp = _PyInterpreterState_LookUpIDObject(idobj); | ||||
|         if (interp == NULL) { | ||||
|             return NULL; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     PyInterpreterConfig config; | ||||
|     if (_PyInterpreterConfig_InitFromState(&config, interp) < 0) { | ||||
|         return NULL; | ||||
|     } | ||||
|     PyObject *dict = _PyInterpreterConfig_AsDict(&config); | ||||
|     if (dict == NULL) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     PyObject *configobj = _PyNamespace_New(dict); | ||||
|     Py_DECREF(dict); | ||||
|     return configobj; | ||||
| } | ||||
| 
 | ||||
| PyDoc_STRVAR(get_config_doc, | ||||
| "get_config(id) -> types.SimpleNamespace\n\
 | ||||
| \n\ | ||||
| Return a representation of the config used to initialize the interpreter."); | ||||
| 
 | ||||
| 
 | ||||
| static PyObject * | ||||
| interp_incref(PyObject *self, PyObject *args, PyObject *kwds) | ||||
| { | ||||
|     static char *kwlist[] = {"id", "implieslink",  NULL}; | ||||
|     PyObject *id; | ||||
|     int implieslink = 0; | ||||
|     if (!PyArg_ParseTupleAndKeywords(args, kwds, | ||||
|                                      "O|$p:incref", kwlist, | ||||
|                                      &id, &implieslink)) | ||||
|     { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|  | @ -1021,8 +1211,10 @@ interp_incref(PyObject *self, PyObject *args, PyObject *kwds) | |||
|     if (interp == NULL) { | ||||
|         return NULL; | ||||
|     } | ||||
|     if (_PyInterpreterState_IDInitref(interp) < 0) { | ||||
|         return NULL; | ||||
| 
 | ||||
|     if (implieslink) { | ||||
|         // Decref to 0 will destroy the interpreter.
 | ||||
|         _PyInterpreterState_RequireIDRef(interp, 1); | ||||
|     } | ||||
|     _PyInterpreterState_IDIncref(interp); | ||||
| 
 | ||||
|  | @ -1036,7 +1228,7 @@ interp_decref(PyObject *self, PyObject *args, PyObject *kwds) | |||
|     static char *kwlist[] = {"id", NULL}; | ||||
|     PyObject *id; | ||||
|     if (!PyArg_ParseTupleAndKeywords(args, kwds, | ||||
|                                      "O:_incref", kwlist, &id)) { | ||||
|                                      "O:decref", kwlist, &id)) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|  | @ -1051,6 +1243,8 @@ interp_decref(PyObject *self, PyObject *args, PyObject *kwds) | |||
| 
 | ||||
| 
 | ||||
| static PyMethodDef module_functions[] = { | ||||
|     {"new_config",                _PyCFunction_CAST(interp_new_config), | ||||
|      METH_VARARGS | METH_KEYWORDS, new_config_doc}, | ||||
|     {"create",                    _PyCFunction_CAST(interp_create), | ||||
|      METH_VARARGS | METH_KEYWORDS, create_doc}, | ||||
|     {"destroy",                   _PyCFunction_CAST(interp_destroy), | ||||
|  | @ -1064,6 +1258,8 @@ static PyMethodDef module_functions[] = { | |||
| 
 | ||||
|     {"is_running",                _PyCFunction_CAST(interp_is_running), | ||||
|      METH_VARARGS | METH_KEYWORDS, is_running_doc}, | ||||
|     {"get_config",                _PyCFunction_CAST(interp_get_config), | ||||
|      METH_VARARGS | METH_KEYWORDS, get_config_doc}, | ||||
|     {"exec",                      _PyCFunction_CAST(interp_exec), | ||||
|      METH_VARARGS | METH_KEYWORDS, exec_doc}, | ||||
|     {"call",                      _PyCFunction_CAST(interp_call), | ||||
|  | @ -1078,9 +1274,9 @@ static PyMethodDef module_functions[] = { | |||
|     {"is_shareable",              _PyCFunction_CAST(object_is_shareable), | ||||
|      METH_VARARGS | METH_KEYWORDS, is_shareable_doc}, | ||||
| 
 | ||||
|     {"_incref",                   _PyCFunction_CAST(interp_incref), | ||||
|     {"incref",                    _PyCFunction_CAST(interp_incref), | ||||
|      METH_VARARGS | METH_KEYWORDS, NULL}, | ||||
|     {"_decref",                   _PyCFunction_CAST(interp_decref), | ||||
|     {"decref",                    _PyCFunction_CAST(interp_decref), | ||||
|      METH_VARARGS | METH_KEYWORDS, NULL}, | ||||
| 
 | ||||
|     {NULL,                        NULL}           /* sentinel */ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Eric Snow
						Eric Snow