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(): | def create(): | ||||||
|     """Return a new (idle) Python interpreter.""" |     """Return a new (idle) Python interpreter.""" | ||||||
|     id = _interpreters.create(isolated=True) |     id = _interpreters.create(reqrefs=True) | ||||||
|     return Interpreter(id) |     return Interpreter(id) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -109,13 +109,13 @@ def __new__(cls, id, /): | ||||||
|             assert hasattr(self, '_ownsref') |             assert hasattr(self, '_ownsref') | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             # This may raise InterpreterNotFoundError: |             # This may raise InterpreterNotFoundError: | ||||||
|             _interpreters._incref(id) |             _interpreters.incref(id) | ||||||
|             try: |             try: | ||||||
|                 self = super().__new__(cls) |                 self = super().__new__(cls) | ||||||
|                 self._id = id |                 self._id = id | ||||||
|                 self._ownsref = True |                 self._ownsref = True | ||||||
|             except BaseException: |             except BaseException: | ||||||
|                 _interpreters._deccref(id) |                 _interpreters.decref(id) | ||||||
|                 raise |                 raise | ||||||
|             _known[id] = self |             _known[id] = self | ||||||
|         return self |         return self | ||||||
|  | @ -142,7 +142,7 @@ def _decref(self): | ||||||
|             return |             return | ||||||
|         self._ownsref = False |         self._ownsref = False | ||||||
|         try: |         try: | ||||||
|             _interpreters._decref(self.id) |             _interpreters.decref(self.id) | ||||||
|         except InterpreterNotFoundError: |         except InterpreterNotFoundError: | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -584,7 +584,7 @@ def f(): | ||||||
|     def test_create_daemon_thread(self): |     def test_create_daemon_thread(self): | ||||||
|         with self.subTest('isolated'): |         with self.subTest('isolated'): | ||||||
|             expected = 'spam spam spam spam spam' |             expected = 'spam spam spam spam spam' | ||||||
|             subinterp = interpreters.create(isolated=True) |             subinterp = interpreters.create('isolated') | ||||||
|             script, file = _captured_script(f""" |             script, file = _captured_script(f""" | ||||||
|                 import threading |                 import threading | ||||||
|                 def f(): |                 def f(): | ||||||
|  | @ -604,7 +604,7 @@ def f(): | ||||||
|             self.assertEqual(out, expected) |             self.assertEqual(out, expected) | ||||||
| 
 | 
 | ||||||
|         with self.subTest('not isolated'): |         with self.subTest('not isolated'): | ||||||
|             subinterp = interpreters.create(isolated=False) |             subinterp = interpreters.create('legacy') | ||||||
|             script, file = _captured_script(""" |             script, file = _captured_script(""" | ||||||
|                 import threading |                 import threading | ||||||
|                 def f(): |                 def f(): | ||||||
|  |  | ||||||
|  | @ -2204,6 +2204,7 @@ def test_module_state_shared_in_global(self): | ||||||
|         self.assertEqual(main_attr_id, subinterp_attr_id) |         self.assertEqual(main_attr_id, subinterp_attr_id) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @requires_subinterpreters | ||||||
| class InterpreterConfigTests(unittest.TestCase): | class InterpreterConfigTests(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|     supported = { |     supported = { | ||||||
|  | @ -2277,11 +2278,11 @@ def check(name, expected): | ||||||
|             expected = self.supported[expected] |             expected = self.supported[expected] | ||||||
|             args = (name,) if name else () |             args = (name,) if name else () | ||||||
| 
 | 
 | ||||||
|             config1 = _testinternalcapi.new_interp_config(*args) |             config1 = _interpreters.new_config(*args) | ||||||
|             self.assert_ns_equal(config1, expected) |             self.assert_ns_equal(config1, expected) | ||||||
|             self.assertIsNot(config1, expected) |             self.assertIsNot(config1, expected) | ||||||
| 
 | 
 | ||||||
|             config2 = _testinternalcapi.new_interp_config(*args) |             config2 = _interpreters.new_config(*args) | ||||||
|             self.assert_ns_equal(config2, expected) |             self.assert_ns_equal(config2, expected) | ||||||
|             self.assertIsNot(config2, expected) |             self.assertIsNot(config2, expected) | ||||||
|             self.assertIsNot(config2, config1) |             self.assertIsNot(config2, config1) | ||||||
|  | @ -2298,7 +2299,7 @@ def test_update_from_dict(self): | ||||||
|             with self.subTest(f'noop ({name})'): |             with self.subTest(f'noop ({name})'): | ||||||
|                 expected = vanilla |                 expected = vanilla | ||||||
|                 overrides = vars(vanilla) |                 overrides = vars(vanilla) | ||||||
|                 config = _testinternalcapi.new_interp_config(name, **overrides) |                 config = _interpreters.new_config(name, **overrides) | ||||||
|                 self.assert_ns_equal(config, expected) |                 self.assert_ns_equal(config, expected) | ||||||
| 
 | 
 | ||||||
|             with self.subTest(f'change all ({name})'): |             with self.subTest(f'change all ({name})'): | ||||||
|  | @ -2308,7 +2309,7 @@ def test_update_from_dict(self): | ||||||
|                         continue |                         continue | ||||||
|                     overrides['gil'] = gil |                     overrides['gil'] = gil | ||||||
|                     expected = types.SimpleNamespace(**overrides) |                     expected = types.SimpleNamespace(**overrides) | ||||||
|                     config = _testinternalcapi.new_interp_config( |                     config = _interpreters.new_config( | ||||||
|                                                             name, **overrides) |                                                             name, **overrides) | ||||||
|                     self.assert_ns_equal(config, expected) |                     self.assert_ns_equal(config, expected) | ||||||
| 
 | 
 | ||||||
|  | @ -2324,14 +2325,14 @@ def test_update_from_dict(self): | ||||||
|                         expected = types.SimpleNamespace( |                         expected = types.SimpleNamespace( | ||||||
|                             **dict(vars(vanilla), **overrides), |                             **dict(vars(vanilla), **overrides), | ||||||
|                         ) |                         ) | ||||||
|                         config = _testinternalcapi.new_interp_config( |                         config = _interpreters.new_config( | ||||||
|                                                             name, **overrides) |                                                             name, **overrides) | ||||||
|                         self.assert_ns_equal(config, expected) |                         self.assert_ns_equal(config, expected) | ||||||
| 
 | 
 | ||||||
|         with self.subTest('unsupported field'): |         with self.subTest('unsupported field'): | ||||||
|             for name in self.supported: |             for name in self.supported: | ||||||
|                 with self.assertRaises(ValueError): |                 with self.assertRaises(ValueError): | ||||||
|                     _testinternalcapi.new_interp_config(name, spam=True) |                     _interpreters.new_config(name, spam=True) | ||||||
| 
 | 
 | ||||||
|         # Bad values for bool fields. |         # Bad values for bool fields. | ||||||
|         for field, value in vars(self.supported['empty']).items(): |         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()]: |             for value in [1, '', 'spam', 1.0, None, object()]: | ||||||
|                 with self.subTest(f'unsupported value ({field}={value!r})'): |                 with self.subTest(f'unsupported value ({field}={value!r})'): | ||||||
|                     with self.assertRaises(TypeError): |                     with self.assertRaises(TypeError): | ||||||
|                         _testinternalcapi.new_interp_config(**{field: value}) |                         _interpreters.new_config(**{field: value}) | ||||||
| 
 | 
 | ||||||
|         # Bad values for .gil. |         # Bad values for .gil. | ||||||
|         for value in [True, 1, 1.0, None, object()]: |         for value in [True, 1, 1.0, None, object()]: | ||||||
|             with self.subTest(f'unsupported value(gil={value!r})'): |             with self.subTest(f'unsupported value(gil={value!r})'): | ||||||
|                 with self.assertRaises(TypeError): |                 with self.assertRaises(TypeError): | ||||||
|                     _testinternalcapi.new_interp_config(gil=value) |                     _interpreters.new_config(gil=value) | ||||||
|         for value in ['', 'spam']: |         for value in ['', 'spam']: | ||||||
|             with self.subTest(f'unsupported value (gil={value!r})'): |             with self.subTest(f'unsupported value (gil={value!r})'): | ||||||
|                 with self.assertRaises(ValueError): |                 with self.assertRaises(ValueError): | ||||||
|                     _testinternalcapi.new_interp_config(gil=value) |                     _interpreters.new_config(gil=value) | ||||||
| 
 | 
 | ||||||
|     @requires_subinterpreters |  | ||||||
|     def test_interp_init(self): |     def test_interp_init(self): | ||||||
|         questionable = [ |         questionable = [ | ||||||
|             # strange |             # strange | ||||||
|  | @ -2412,11 +2412,10 @@ def check(config): | ||||||
|                 with self.subTest(f'valid: {config}'): |                 with self.subTest(f'valid: {config}'): | ||||||
|                     check(config) |                     check(config) | ||||||
| 
 | 
 | ||||||
|     @requires_subinterpreters |  | ||||||
|     def test_get_config(self): |     def test_get_config(self): | ||||||
|         @contextlib.contextmanager |         @contextlib.contextmanager | ||||||
|         def new_interp(config): |         def new_interp(config): | ||||||
|             interpid = _testinternalcapi.new_interpreter(config) |             interpid = _interpreters.create(config, reqrefs=False) | ||||||
|             try: |             try: | ||||||
|                 yield interpid |                 yield interpid | ||||||
|             finally: |             finally: | ||||||
|  | @ -2426,32 +2425,32 @@ def new_interp(config): | ||||||
|                     pass |                     pass | ||||||
| 
 | 
 | ||||||
|         with self.subTest('main'): |         with self.subTest('main'): | ||||||
|             expected = _testinternalcapi.new_interp_config('legacy') |             expected = _interpreters.new_config('legacy') | ||||||
|             expected.gil = 'own' |             expected.gil = 'own' | ||||||
|             interpid = _interpreters.get_main() |             interpid = _interpreters.get_main() | ||||||
|             config = _testinternalcapi.get_interp_config(interpid) |             config = _interpreters.get_config(interpid) | ||||||
|             self.assert_ns_equal(config, expected) |             self.assert_ns_equal(config, expected) | ||||||
| 
 | 
 | ||||||
|         with self.subTest('isolated'): |         with self.subTest('isolated'): | ||||||
|             expected = _testinternalcapi.new_interp_config('isolated') |             expected = _interpreters.new_config('isolated') | ||||||
|             with new_interp('isolated') as interpid: |             with new_interp('isolated') as interpid: | ||||||
|                 config = _testinternalcapi.get_interp_config(interpid) |                 config = _interpreters.get_config(interpid) | ||||||
|             self.assert_ns_equal(config, expected) |             self.assert_ns_equal(config, expected) | ||||||
| 
 | 
 | ||||||
|         with self.subTest('legacy'): |         with self.subTest('legacy'): | ||||||
|             expected = _testinternalcapi.new_interp_config('legacy') |             expected = _interpreters.new_config('legacy') | ||||||
|             with new_interp('legacy') as interpid: |             with new_interp('legacy') as interpid: | ||||||
|                 config = _testinternalcapi.get_interp_config(interpid) |                 config = _interpreters.get_config(interpid) | ||||||
|             self.assert_ns_equal(config, expected) |             self.assert_ns_equal(config, expected) | ||||||
| 
 | 
 | ||||||
|         with self.subTest('custom'): |         with self.subTest('custom'): | ||||||
|             orig = _testinternalcapi.new_interp_config( |             orig = _interpreters.new_config( | ||||||
|                 'empty', |                 'empty', | ||||||
|                 use_main_obmalloc=True, |                 use_main_obmalloc=True, | ||||||
|                 gil='shared', |                 gil='shared', | ||||||
|             ) |             ) | ||||||
|             with new_interp(orig) as interpid: |             with new_interp(orig) as interpid: | ||||||
|                 config = _testinternalcapi.get_interp_config(interpid) |                 config = _interpreters.get_config(interpid) | ||||||
|             self.assert_ns_equal(config, orig) |             self.assert_ns_equal(config, orig) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -2529,14 +2528,19 @@ def test_lookup_destroyed(self): | ||||||
|         self.assertFalse( |         self.assertFalse( | ||||||
|             _testinternalcapi.interpreter_exists(interpid)) |             _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): |     def test_linked_lifecycle_does_not_exist(self): | ||||||
|         exists = _testinternalcapi.interpreter_exists |         exists = _testinternalcapi.interpreter_exists | ||||||
|         is_linked = _testinternalcapi.interpreter_refcount_linked |         is_linked = _testinternalcapi.interpreter_refcount_linked | ||||||
|         link = _testinternalcapi.link_interpreter_refcount |         link = _testinternalcapi.link_interpreter_refcount | ||||||
|         unlink = _testinternalcapi.unlink_interpreter_refcount |         unlink = _testinternalcapi.unlink_interpreter_refcount | ||||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount |         get_refcount, incref, decref = self.get_refcount_helpers() | ||||||
|         incref = _testinternalcapi.interpreter_incref |  | ||||||
|         decref = _testinternalcapi.interpreter_decref |  | ||||||
| 
 | 
 | ||||||
|         with self.subTest('never existed'): |         with self.subTest('never existed'): | ||||||
|             interpid = _testinternalcapi.unused_interpreter_id() |             interpid = _testinternalcapi.unused_interpreter_id() | ||||||
|  | @ -2578,8 +2582,7 @@ def test_linked_lifecycle_initial(self): | ||||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount |         get_refcount = _testinternalcapi.get_interpreter_refcount | ||||||
| 
 | 
 | ||||||
|         # A new interpreter will start out not linked, with a refcount of 0. |         # A new interpreter will start out not linked, with a refcount of 0. | ||||||
|         interpid = _testinternalcapi.new_interpreter() |         interpid = self.new_interpreter() | ||||||
|         self.add_interp_cleanup(interpid) |  | ||||||
|         linked = is_linked(interpid) |         linked = is_linked(interpid) | ||||||
|         refcount = get_refcount(interpid) |         refcount = get_refcount(interpid) | ||||||
| 
 | 
 | ||||||
|  | @ -2589,12 +2592,9 @@ def test_linked_lifecycle_initial(self): | ||||||
|     def test_linked_lifecycle_never_linked(self): |     def test_linked_lifecycle_never_linked(self): | ||||||
|         exists = _testinternalcapi.interpreter_exists |         exists = _testinternalcapi.interpreter_exists | ||||||
|         is_linked = _testinternalcapi.interpreter_refcount_linked |         is_linked = _testinternalcapi.interpreter_refcount_linked | ||||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount |         get_refcount, incref, decref = self.get_refcount_helpers() | ||||||
|         incref = _testinternalcapi.interpreter_incref |  | ||||||
|         decref = _testinternalcapi.interpreter_decref |  | ||||||
| 
 | 
 | ||||||
|         interpid = _testinternalcapi.new_interpreter() |         interpid = self.new_interpreter() | ||||||
|         self.add_interp_cleanup(interpid) |  | ||||||
| 
 | 
 | ||||||
|         # Incref will not automatically link it. |         # Incref will not automatically link it. | ||||||
|         incref(interpid) |         incref(interpid) | ||||||
|  | @ -2618,8 +2618,7 @@ def test_linked_lifecycle_link_unlink(self): | ||||||
|         link = _testinternalcapi.link_interpreter_refcount |         link = _testinternalcapi.link_interpreter_refcount | ||||||
|         unlink = _testinternalcapi.unlink_interpreter_refcount |         unlink = _testinternalcapi.unlink_interpreter_refcount | ||||||
| 
 | 
 | ||||||
|         interpid = _testinternalcapi.new_interpreter() |         interpid = self.new_interpreter() | ||||||
|         self.add_interp_cleanup(interpid) |  | ||||||
| 
 | 
 | ||||||
|         # Linking at refcount 0 does not destroy the interpreter. |         # Linking at refcount 0 does not destroy the interpreter. | ||||||
|         link(interpid) |         link(interpid) | ||||||
|  | @ -2639,12 +2638,9 @@ def test_linked_lifecycle_link_incref_decref(self): | ||||||
|         exists = _testinternalcapi.interpreter_exists |         exists = _testinternalcapi.interpreter_exists | ||||||
|         is_linked = _testinternalcapi.interpreter_refcount_linked |         is_linked = _testinternalcapi.interpreter_refcount_linked | ||||||
|         link = _testinternalcapi.link_interpreter_refcount |         link = _testinternalcapi.link_interpreter_refcount | ||||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount |         get_refcount, incref, decref = self.get_refcount_helpers() | ||||||
|         incref = _testinternalcapi.interpreter_incref |  | ||||||
|         decref = _testinternalcapi.interpreter_decref |  | ||||||
| 
 | 
 | ||||||
|         interpid = _testinternalcapi.new_interpreter() |         interpid = self.new_interpreter() | ||||||
|         self.add_interp_cleanup(interpid) |  | ||||||
| 
 | 
 | ||||||
|         # Linking it will not change the refcount. |         # Linking it will not change the refcount. | ||||||
|         link(interpid) |         link(interpid) | ||||||
|  | @ -2666,11 +2662,9 @@ def test_linked_lifecycle_link_incref_decref(self): | ||||||
|     def test_linked_lifecycle_incref_link(self): |     def test_linked_lifecycle_incref_link(self): | ||||||
|         is_linked = _testinternalcapi.interpreter_refcount_linked |         is_linked = _testinternalcapi.interpreter_refcount_linked | ||||||
|         link = _testinternalcapi.link_interpreter_refcount |         link = _testinternalcapi.link_interpreter_refcount | ||||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount |         get_refcount, incref, _ = self.get_refcount_helpers() | ||||||
|         incref = _testinternalcapi.interpreter_incref |  | ||||||
| 
 | 
 | ||||||
|         interpid = _testinternalcapi.new_interpreter() |         interpid = self.new_interpreter() | ||||||
|         self.add_interp_cleanup(interpid) |  | ||||||
| 
 | 
 | ||||||
|         incref(interpid) |         incref(interpid) | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|  | @ -2688,12 +2682,9 @@ def test_linked_lifecycle_link_incref_unlink_decref(self): | ||||||
|         is_linked = _testinternalcapi.interpreter_refcount_linked |         is_linked = _testinternalcapi.interpreter_refcount_linked | ||||||
|         link = _testinternalcapi.link_interpreter_refcount |         link = _testinternalcapi.link_interpreter_refcount | ||||||
|         unlink = _testinternalcapi.unlink_interpreter_refcount |         unlink = _testinternalcapi.unlink_interpreter_refcount | ||||||
|         get_refcount = _testinternalcapi.get_interpreter_refcount |         get_refcount, incref, decref = self.get_refcount_helpers() | ||||||
|         incref = _testinternalcapi.interpreter_incref |  | ||||||
|         decref = _testinternalcapi.interpreter_decref |  | ||||||
| 
 | 
 | ||||||
|         interpid = _testinternalcapi.new_interpreter() |         interpid = self.new_interpreter() | ||||||
|         self.add_interp_cleanup(interpid) |  | ||||||
| 
 | 
 | ||||||
|         link(interpid) |         link(interpid) | ||||||
|         self.assertTrue( |         self.assertTrue( | ||||||
|  |  | ||||||
|  | @ -2163,7 +2163,7 @@ def re_load(self, name, mod): | ||||||
|     # subinterpreters |     # subinterpreters | ||||||
| 
 | 
 | ||||||
|     def add_subinterpreter(self): |     def add_subinterpreter(self): | ||||||
|         interpid = _interpreters.create(isolated=False) |         interpid = _interpreters.create('legacy') | ||||||
|         def ensure_destroyed(): |         def ensure_destroyed(): | ||||||
|             try: |             try: | ||||||
|                 _interpreters.destroy(interpid) |                 _interpreters.destroy(interpid) | ||||||
|  |  | ||||||
|  | @ -656,7 +656,7 @@ def test_magic_number(self): | ||||||
| class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase): | class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|     def run_with_own_gil(self, script): |     def run_with_own_gil(self, script): | ||||||
|         interpid = _interpreters.create(isolated=True) |         interpid = _interpreters.create('isolated') | ||||||
|         def ensure_destroyed(): |         def ensure_destroyed(): | ||||||
|             try: |             try: | ||||||
|                 _interpreters.destroy(interpid) |                 _interpreters.destroy(interpid) | ||||||
|  | @ -669,7 +669,7 @@ def ensure_destroyed(): | ||||||
|                 raise ImportError(excsnap.msg) |                 raise ImportError(excsnap.msg) | ||||||
| 
 | 
 | ||||||
|     def run_with_shared_gil(self, script): |     def run_with_shared_gil(self, script): | ||||||
|         interpid = _interpreters.create(isolated=False) |         interpid = _interpreters.create('legacy') | ||||||
|         def ensure_destroyed(): |         def ensure_destroyed(): | ||||||
|             try: |             try: | ||||||
|                 _interpreters.destroy(interpid) |                 _interpreters.destroy(interpid) | ||||||
|  |  | ||||||
|  | @ -1,13 +1,14 @@ | ||||||
| import os | import os | ||||||
| import pickle | import pickle | ||||||
| import threading |  | ||||||
| from textwrap import dedent | from textwrap import dedent | ||||||
|  | import threading | ||||||
|  | import types | ||||||
| import unittest | import unittest | ||||||
| 
 | 
 | ||||||
| from test import support | from test import support | ||||||
| from test.support import import_helper | from test.support import import_helper | ||||||
| # Raise SkipTest if subinterpreters not supported. | # 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 import interpreters | ||||||
| from test.support.interpreters import InterpreterNotFoundError | from test.support.interpreters import InterpreterNotFoundError | ||||||
| from .utils import _captured_script, _run_output, _running, TestBase | from .utils import _captured_script, _run_output, _running, TestBase | ||||||
|  | @ -932,6 +933,212 @@ class SubBytes(bytes): | ||||||
|                     interpreters.is_shareable(obj)) |                     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__': | if __name__ == '__main__': | ||||||
|     # Test needs to be a package, so we can do relative imports. |     # Test needs to be a package, so we can do relative imports. | ||||||
|     unittest.main() |     unittest.main() | ||||||
|  |  | ||||||
|  | @ -28,9 +28,9 @@ def tearDown(self): | ||||||
| 
 | 
 | ||||||
| class LowLevelTests(TestBase): | class LowLevelTests(TestBase): | ||||||
| 
 | 
 | ||||||
|     # The behaviors in the low-level module is important in as much |     # The behaviors in the low-level module are important in as much | ||||||
|     # as it is exercised by the high-level module.  Therefore the |     # as they are exercised by the high-level module.  Therefore the | ||||||
|     # most # important testing happens in the high-level tests. |     # most important testing happens in the high-level tests. | ||||||
|     # These low-level tests cover corner cases that are not |     # These low-level tests cover corner cases that are not | ||||||
|     # encountered by the high-level module, thus they |     # encountered by the high-level module, thus they | ||||||
|     # mostly shouldn't matter as much. |     # mostly shouldn't matter as much. | ||||||
|  |  | ||||||
|  | @ -68,6 +68,9 @@ def run(): | ||||||
| 
 | 
 | ||||||
| class TestBase(unittest.TestCase): | class TestBase(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|  |     def tearDown(self): | ||||||
|  |         clean_up_interpreters() | ||||||
|  | 
 | ||||||
|     def pipe(self): |     def pipe(self): | ||||||
|         def ensure_closed(fd): |         def ensure_closed(fd): | ||||||
|             try: |             try: | ||||||
|  | @ -156,5 +159,19 @@ def assert_python_failure(self, *argv): | ||||||
|         self.assertNotEqual(exitcode, 0) |         self.assertNotEqual(exitcode, 0) | ||||||
|         return stdout, stderr |         return stdout, stderr | ||||||
| 
 | 
 | ||||||
|     def tearDown(self): |     def assert_ns_equal(self, ns1, ns2, msg=None): | ||||||
|         clean_up_interpreters() |         # 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_initconfig.h"    // _Py_GetConfigsAsDict() | ||||||
| #include "pycore_interp.h"        // _PyInterpreterState_GetConfigCopy() | #include "pycore_interp.h"        // _PyInterpreterState_GetConfigCopy() | ||||||
| #include "pycore_long.h"          // _PyLong_Sign() | #include "pycore_long.h"          // _PyLong_Sign() | ||||||
| #include "pycore_namespace.h"     // _PyNamespace_New() |  | ||||||
| #include "pycore_object.h"        // _PyObject_IsFreed() | #include "pycore_object.h"        // _PyObject_IsFreed() | ||||||
| #include "pycore_optimizer.h"     // _Py_UopsSymbol, etc. | #include "pycore_optimizer.h"     // _Py_UopsSymbol, etc. | ||||||
| #include "pycore_pathconfig.h"    // _PyPathConfig_ClearGlobal() | #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 * | static PyObject * | ||||||
| get_interp_settings(PyObject *self, PyObject *args) | 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. */ | /* To run some code in a sub-interpreter. */ | ||||||
| static PyObject * | static PyObject * | ||||||
| run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) | 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; |     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; |         return NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -1546,58 +1430,6 @@ unused_interpreter_id(PyObject *self, PyObject *Py_UNUSED(ignored)) | ||||||
|     return PyLong_FromLongLong(interpid); |     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 * | static PyObject * | ||||||
| interpreter_exists(PyObject *self, PyObject *idobj) | interpreter_exists(PyObject *self, PyObject *idobj) | ||||||
| { | { | ||||||
|  | @ -1660,28 +1492,6 @@ interpreter_refcount_linked(PyObject *self, PyObject *idobj) | ||||||
|     Py_RETURN_FALSE; |     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 | static void | ||||||
| _xid_capsule_destructor(PyObject *capsule) | _xid_capsule_destructor(PyObject *capsule) | ||||||
|  | @ -1928,23 +1738,16 @@ static PyMethodDef module_functions[] = { | ||||||
|     {"get_object_dict_values", get_object_dict_values, METH_O}, |     {"get_object_dict_values", get_object_dict_values, METH_O}, | ||||||
|     {"hamt", new_hamt, METH_NOARGS}, |     {"hamt", new_hamt, METH_NOARGS}, | ||||||
|     {"dict_getitem_knownhash",  dict_getitem_knownhash,          METH_VARARGS}, |     {"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", |     {"run_in_subinterp_with_config", | ||||||
|      _PyCFunction_CAST(run_in_subinterp_with_config), |      _PyCFunction_CAST(run_in_subinterp_with_config), | ||||||
|      METH_VARARGS | METH_KEYWORDS}, |      METH_VARARGS | METH_KEYWORDS}, | ||||||
|     {"normalize_interp_id", normalize_interp_id, METH_O}, |     {"normalize_interp_id", normalize_interp_id, METH_O}, | ||||||
|     {"unused_interpreter_id", unused_interpreter_id, METH_NOARGS}, |     {"unused_interpreter_id", unused_interpreter_id, METH_NOARGS}, | ||||||
|     {"new_interpreter", new_interpreter, METH_VARARGS}, |  | ||||||
|     {"interpreter_exists", interpreter_exists, METH_O}, |     {"interpreter_exists", interpreter_exists, METH_O}, | ||||||
|     {"get_interpreter_refcount", get_interpreter_refcount, METH_O}, |     {"get_interpreter_refcount", get_interpreter_refcount, METH_O}, | ||||||
|     {"link_interpreter_refcount", link_interpreter_refcount,     METH_O}, |     {"link_interpreter_refcount", link_interpreter_refcount,     METH_O}, | ||||||
|     {"unlink_interpreter_refcount", unlink_interpreter_refcount, METH_O}, |     {"unlink_interpreter_refcount", unlink_interpreter_refcount, METH_O}, | ||||||
|     {"interpreter_refcount_linked", interpreter_refcount_linked, 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}, |     {"compile_perf_trampoline_entry", compile_perf_trampoline_entry, METH_VARARGS}, | ||||||
|     {"perf_trampoline_set_persist_after_fork", perf_trampoline_set_persist_after_fork, METH_VARARGS}, |     {"perf_trampoline_set_persist_after_fork", perf_trampoline_set_persist_after_fork, METH_VARARGS}, | ||||||
|     {"get_crossinterp_data",    get_crossinterp_data,            METH_VARARGS}, |     {"get_crossinterp_data",    get_crossinterp_data,            METH_VARARGS}, | ||||||
|  |  | ||||||
|  | @ -12,8 +12,10 @@ | ||||||
| #include "pycore_initconfig.h"    // _PyErr_SetFromPyStatus() | #include "pycore_initconfig.h"    // _PyErr_SetFromPyStatus() | ||||||
| #include "pycore_long.h"          // _PyLong_IsNegative() | #include "pycore_long.h"          // _PyLong_IsNegative() | ||||||
| #include "pycore_modsupport.h"    // _PyArg_BadArgument() | #include "pycore_modsupport.h"    // _PyArg_BadArgument() | ||||||
|  | #include "pycore_namespace.h"     // _PyNamespace_New() | ||||||
| #include "pycore_pybuffer.h"      // _PyBuffer_ReleaseInInterpreterAndRawFree() | #include "pycore_pybuffer.h"      // _PyBuffer_ReleaseInInterpreterAndRawFree() | ||||||
| #include "pycore_pyerrors.h"      // _Py_excinfo | #include "pycore_pyerrors.h"      // _Py_excinfo | ||||||
|  | #include "pycore_pylifecycle.h"   // _PyInterpreterConfig_AsDict() | ||||||
| #include "pycore_pystate.h"       // _PyInterpreterState_SetRunningMain() | #include "pycore_pystate.h"       // _PyInterpreterState_SetRunningMain() | ||||||
| 
 | 
 | ||||||
| #include "marshal.h"              // PyMarshal_ReadObjectFromString() | #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 ************************************************/ | /* 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 | static int | ||||||
| _run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags) | _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 ********************************************************/ | /* module level code ********************************************************/ | ||||||
| 
 | 
 | ||||||
| static PyObject * | 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}; |     PyInterpreterConfig config; | ||||||
|     int isolated = 1; |     if (init_named_config(&config, name) < 0) { | ||||||
|     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|$i:create", kwlist, |  | ||||||
|                                      &isolated)) { |  | ||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Create and initialize the new interpreter.
 |     if (overrides != NULL && PyDict_GET_SIZE(overrides) > 0) { | ||||||
|     PyThreadState *save_tstate = PyThreadState_Get(); |         if (_PyInterpreterConfig_UpdateFromDict(&config, overrides) < 0) { | ||||||
|     assert(save_tstate != NULL); |             return NULL; | ||||||
|     const PyInterpreterConfig config = isolated |         } | ||||||
|         ? (PyInterpreterConfig)_PyInterpreterConfig_INIT |     } | ||||||
|         : (PyInterpreterConfig)_PyInterpreterConfig_LEGACY_INIT; |  | ||||||
| 
 | 
 | ||||||
|     // XXX Possible GILState issues?
 |     PyObject *dict = _PyInterpreterConfig_AsDict(&config); | ||||||
|     PyThreadState *tstate = NULL; |     if (dict == NULL) { | ||||||
|     PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config); |         return NULL; | ||||||
|     PyThreadState_Swap(save_tstate); |     } | ||||||
|     if (PyStatus_Exception(status)) { | 
 | ||||||
|         /* Since no new thread state was created, there is no exception to
 |     PyObject *configobj = _PyNamespace_New(dict); | ||||||
|            propagate; raise a fresh one after swapping in the old thread |     Py_DECREF(dict); | ||||||
|            state. */ |     return configobj; | ||||||
|         _PyErr_SetFromPyStatus(status); | } | ||||||
|  | 
 | ||||||
|  | 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(); |         PyObject *exc = PyErr_GetRaisedException(); | ||||||
|  |         assert(exc != NULL); | ||||||
|         PyErr_SetString(PyExc_RuntimeError, "interpreter creation failed"); |         PyErr_SetString(PyExc_RuntimeError, "interpreter creation failed"); | ||||||
|         _PyErr_ChainExceptions1(exc); |         _PyErr_ChainExceptions1(exc); | ||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
|     assert(tstate != NULL); |  | ||||||
| 
 | 
 | ||||||
|     PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); |     if (reqrefs) { | ||||||
|     PyObject *idobj = get_interpid_obj(interp); |         // Decref to 0 will destroy the interpreter.
 | ||||||
|     if (idobj == NULL) { |         _PyInterpreterState_RequireIDRef(interp, 1); | ||||||
|         // XXX Possible GILState issues?
 |  | ||||||
|         save_tstate = PyThreadState_Swap(tstate); |  | ||||||
|         Py_EndInterpreter(tstate); |  | ||||||
|         PyThreadState_Swap(save_tstate); |  | ||||||
|         return NULL; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     PyThreadState_Swap(tstate); |  | ||||||
|     PyThreadState_Clear(tstate); |  | ||||||
|     PyThreadState_Swap(save_tstate); |  | ||||||
|     PyThreadState_Delete(tstate); |  | ||||||
| 
 |  | ||||||
|     _PyInterpreterState_RequireIDRef(interp, 1); |  | ||||||
|     return idobj; |     return idobj; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| PyDoc_STRVAR(create_doc, | PyDoc_STRVAR(create_doc, | ||||||
| "create() -> ID\n\
 | "create([config], *, reqrefs=False) -> ID\n\
 | ||||||
| \n\ | \n\ | ||||||
| Create a new interpreter and return a unique generated ID.\n\ | Create a new interpreter and return a unique generated ID.\n\ | ||||||
| \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 * | static PyObject * | ||||||
|  | @ -1008,12 +1153,57 @@ Return whether or not the identified interpreter is running."); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| static PyObject * | static PyObject * | ||||||
| interp_incref(PyObject *self, PyObject *args, PyObject *kwds) | interp_get_config(PyObject *self, PyObject *args, PyObject *kwds) | ||||||
| { | { | ||||||
|     static char *kwlist[] = {"id", NULL}; |     static char *kwlist[] = {"id", NULL}; | ||||||
|     PyObject *id; |     PyObject *idobj = NULL; | ||||||
|     if (!PyArg_ParseTupleAndKeywords(args, kwds, |     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; |         return NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -1021,8 +1211,10 @@ interp_incref(PyObject *self, PyObject *args, PyObject *kwds) | ||||||
|     if (interp == NULL) { |     if (interp == NULL) { | ||||||
|         return 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); |     _PyInterpreterState_IDIncref(interp); | ||||||
| 
 | 
 | ||||||
|  | @ -1036,7 +1228,7 @@ interp_decref(PyObject *self, PyObject *args, PyObject *kwds) | ||||||
|     static char *kwlist[] = {"id", NULL}; |     static char *kwlist[] = {"id", NULL}; | ||||||
|     PyObject *id; |     PyObject *id; | ||||||
|     if (!PyArg_ParseTupleAndKeywords(args, kwds, |     if (!PyArg_ParseTupleAndKeywords(args, kwds, | ||||||
|                                      "O:_incref", kwlist, &id)) { |                                      "O:decref", kwlist, &id)) { | ||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -1051,6 +1243,8 @@ interp_decref(PyObject *self, PyObject *args, PyObject *kwds) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| static PyMethodDef module_functions[] = { | static PyMethodDef module_functions[] = { | ||||||
|  |     {"new_config",                _PyCFunction_CAST(interp_new_config), | ||||||
|  |      METH_VARARGS | METH_KEYWORDS, new_config_doc}, | ||||||
|     {"create",                    _PyCFunction_CAST(interp_create), |     {"create",                    _PyCFunction_CAST(interp_create), | ||||||
|      METH_VARARGS | METH_KEYWORDS, create_doc}, |      METH_VARARGS | METH_KEYWORDS, create_doc}, | ||||||
|     {"destroy",                   _PyCFunction_CAST(interp_destroy), |     {"destroy",                   _PyCFunction_CAST(interp_destroy), | ||||||
|  | @ -1064,6 +1258,8 @@ static PyMethodDef module_functions[] = { | ||||||
| 
 | 
 | ||||||
|     {"is_running",                _PyCFunction_CAST(interp_is_running), |     {"is_running",                _PyCFunction_CAST(interp_is_running), | ||||||
|      METH_VARARGS | METH_KEYWORDS, is_running_doc}, |      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), |     {"exec",                      _PyCFunction_CAST(interp_exec), | ||||||
|      METH_VARARGS | METH_KEYWORDS, exec_doc}, |      METH_VARARGS | METH_KEYWORDS, exec_doc}, | ||||||
|     {"call",                      _PyCFunction_CAST(interp_call), |     {"call",                      _PyCFunction_CAST(interp_call), | ||||||
|  | @ -1078,9 +1274,9 @@ static PyMethodDef module_functions[] = { | ||||||
|     {"is_shareable",              _PyCFunction_CAST(object_is_shareable), |     {"is_shareable",              _PyCFunction_CAST(object_is_shareable), | ||||||
|      METH_VARARGS | METH_KEYWORDS, is_shareable_doc}, |      METH_VARARGS | METH_KEYWORDS, is_shareable_doc}, | ||||||
| 
 | 
 | ||||||
|     {"_incref",                   _PyCFunction_CAST(interp_incref), |     {"incref",                    _PyCFunction_CAST(interp_incref), | ||||||
|      METH_VARARGS | METH_KEYWORDS, NULL}, |      METH_VARARGS | METH_KEYWORDS, NULL}, | ||||||
|     {"_decref",                   _PyCFunction_CAST(interp_decref), |     {"decref",                    _PyCFunction_CAST(interp_decref), | ||||||
|      METH_VARARGS | METH_KEYWORDS, NULL}, |      METH_VARARGS | METH_KEYWORDS, NULL}, | ||||||
| 
 | 
 | ||||||
|     {NULL,                        NULL}           /* sentinel */ |     {NULL,                        NULL}           /* sentinel */ | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Eric Snow
						Eric Snow