gh-76785: Handle Legacy Interpreters Properly (gh-117490)

This is similar to the situation with threading._DummyThread.  The methods (incl. __del__()) of interpreters.Interpreter objects must be careful with interpreters not created by interpreters.create().  The simplest thing to start with is to disable any method that modifies or runs in the interpreter.  As part of this, the runtime keeps track of where an interpreter was created.  We also handle interpreter "refcounts" properly.
This commit is contained in:
Eric Snow 2024-04-11 17:23:25 -06:00 committed by GitHub
parent fd2bab9d28
commit fd259fdabe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 454 additions and 200 deletions

View file

@ -325,6 +325,7 @@ PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
// Export for _testinternalcapi shared extension // Export for _testinternalcapi shared extension
PyAPI_FUNC(PyInterpreterState *) _PyXI_NewInterpreter( PyAPI_FUNC(PyInterpreterState *) _PyXI_NewInterpreter(
PyInterpreterConfig *config, PyInterpreterConfig *config,
long *maybe_whence,
PyThreadState **p_tstate, PyThreadState **p_tstate,
PyThreadState **p_save_tstate); PyThreadState **p_save_tstate);
PyAPI_FUNC(void) _PyXI_EndInterpreter( PyAPI_FUNC(void) _PyXI_EndInterpreter(

View file

@ -109,7 +109,8 @@ struct _is {
#define _PyInterpreterState_WHENCE_LEGACY_CAPI 2 #define _PyInterpreterState_WHENCE_LEGACY_CAPI 2
#define _PyInterpreterState_WHENCE_CAPI 3 #define _PyInterpreterState_WHENCE_CAPI 3
#define _PyInterpreterState_WHENCE_XI 4 #define _PyInterpreterState_WHENCE_XI 4
#define _PyInterpreterState_WHENCE_MAX 4 #define _PyInterpreterState_WHENCE_STDLIB 5
#define _PyInterpreterState_WHENCE_MAX 5
long _whence; long _whence;
/* Has been initialized to a safe state. /* Has been initialized to a safe state.
@ -316,6 +317,8 @@ PyAPI_FUNC(int) _PyInterpreterState_IDInitref(PyInterpreterState *);
PyAPI_FUNC(int) _PyInterpreterState_IDIncref(PyInterpreterState *); PyAPI_FUNC(int) _PyInterpreterState_IDIncref(PyInterpreterState *);
PyAPI_FUNC(void) _PyInterpreterState_IDDecref(PyInterpreterState *); PyAPI_FUNC(void) _PyInterpreterState_IDDecref(PyInterpreterState *);
PyAPI_FUNC(int) _PyInterpreterState_IsReady(PyInterpreterState *interp);
PyAPI_FUNC(long) _PyInterpreterState_GetWhence(PyInterpreterState *interp); PyAPI_FUNC(long) _PyInterpreterState_GetWhence(PyInterpreterState *interp);
extern void _PyInterpreterState_SetWhence( extern void _PyInterpreterState_SetWhence(
PyInterpreterState *interp, PyInterpreterState *interp,

View file

@ -74,51 +74,77 @@ class ExecutionFailed(InterpreterError):
def create(): def create():
"""Return a new (idle) Python interpreter.""" """Return a new (idle) Python interpreter."""
id = _interpreters.create(reqrefs=True) id = _interpreters.create(reqrefs=True)
return Interpreter(id) return Interpreter(id, _ownsref=True)
def list_all(): def list_all():
"""Return all existing interpreters.""" """Return all existing interpreters."""
return [Interpreter(id) return [Interpreter(id, _whence=whence)
for id, in _interpreters.list_all()] for id, whence in _interpreters.list_all(require_ready=True)]
def get_current(): def get_current():
"""Return the currently running interpreter.""" """Return the currently running interpreter."""
id, = _interpreters.get_current() id, whence = _interpreters.get_current()
return Interpreter(id) return Interpreter(id, _whence=whence)
def get_main(): def get_main():
"""Return the main interpreter.""" """Return the main interpreter."""
id, = _interpreters.get_main() id, whence = _interpreters.get_main()
return Interpreter(id) assert whence == _interpreters.WHENCE_RUNTIME, repr(whence)
return Interpreter(id, _whence=whence)
_known = weakref.WeakValueDictionary() _known = weakref.WeakValueDictionary()
class Interpreter: class Interpreter:
"""A single Python interpreter.""" """A single Python interpreter.
def __new__(cls, id, /): Attributes:
"id" - the unique process-global ID number for the interpreter
"whence" - indicates where the interpreter was created
If the interpreter wasn't created by this module
then any method that modifies the interpreter will fail,
i.e. .close(), .prepare_main(), .exec(), and .call()
"""
_WHENCE_TO_STR = {
_interpreters.WHENCE_UNKNOWN: 'unknown',
_interpreters.WHENCE_RUNTIME: 'runtime init',
_interpreters.WHENCE_LEGACY_CAPI: 'legacy C-API',
_interpreters.WHENCE_CAPI: 'C-API',
_interpreters.WHENCE_XI: 'cross-interpreter C-API',
_interpreters.WHENCE_STDLIB: '_interpreters module',
}
def __new__(cls, id, /, _whence=None, _ownsref=None):
# There is only one instance for any given ID. # There is only one instance for any given ID.
if not isinstance(id, int): if not isinstance(id, int):
raise TypeError(f'id must be an int, got {id!r}') raise TypeError(f'id must be an int, got {id!r}')
id = int(id) id = int(id)
if _whence is None:
if _ownsref:
_whence = _interpreters.WHENCE_STDLIB
else:
_whence = _interpreters.whence(id)
assert _whence in cls._WHENCE_TO_STR, repr(_whence)
if _ownsref is None:
_ownsref = (_whence == _interpreters.WHENCE_STDLIB)
try: try:
self = _known[id] self = _known[id]
assert hasattr(self, '_ownsref') assert hasattr(self, '_ownsref')
except KeyError: except KeyError:
self = super().__new__(cls)
_known[id] = self
self._id = id
self._whence = _whence
self._ownsref = _ownsref
if _ownsref:
# This may raise InterpreterNotFoundError: # This may raise InterpreterNotFoundError:
_interpreters.incref(id) _interpreters.incref(id)
try:
self = super().__new__(cls)
self._id = id
self._ownsref = True
except BaseException:
_interpreters.decref(id)
raise
_known[id] = self
return self return self
def __repr__(self): def __repr__(self):
@ -143,7 +169,7 @@ class Interpreter:
return return
self._ownsref = False self._ownsref = False
try: try:
_interpreters.decref(self.id) _interpreters.decref(self._id)
except InterpreterNotFoundError: except InterpreterNotFoundError:
pass pass
@ -151,17 +177,24 @@ class Interpreter:
def id(self): def id(self):
return self._id return self._id
@property
def whence(self):
return self._WHENCE_TO_STR[self._whence]
def is_running(self): def is_running(self):
"""Return whether or not the identified interpreter is running.""" """Return whether or not the identified interpreter is running."""
return _interpreters.is_running(self._id) return _interpreters.is_running(self._id)
# Everything past here is available only to interpreters created by
# interpreters.create().
def close(self): def close(self):
"""Finalize and destroy the interpreter. """Finalize and destroy the interpreter.
Attempting to destroy the current interpreter results Attempting to destroy the current interpreter results
in an InterpreterError. in an InterpreterError.
""" """
return _interpreters.destroy(self._id) return _interpreters.destroy(self._id, restrict=True)
def prepare_main(self, ns=None, /, **kwargs): def prepare_main(self, ns=None, /, **kwargs):
"""Bind the given values into the interpreter's __main__. """Bind the given values into the interpreter's __main__.
@ -169,7 +202,7 @@ class Interpreter:
The values must be shareable. The values must be shareable.
""" """
ns = dict(ns, **kwargs) if ns is not None else kwargs ns = dict(ns, **kwargs) if ns is not None else kwargs
_interpreters.set___main___attrs(self._id, ns) _interpreters.set___main___attrs(self._id, ns, restrict=True)
def exec(self, code, /): def exec(self, code, /):
"""Run the given source code in the interpreter. """Run the given source code in the interpreter.
@ -189,7 +222,7 @@ class Interpreter:
that time, the previous interpreter is allowed to run that time, the previous interpreter is allowed to run
in other threads. in other threads.
""" """
excinfo = _interpreters.exec(self._id, code) excinfo = _interpreters.exec(self._id, code, restrict=True)
if excinfo is not None: if excinfo is not None:
raise ExecutionFailed(excinfo) raise ExecutionFailed(excinfo)
@ -209,7 +242,7 @@ class Interpreter:
# XXX Support args and kwargs. # XXX Support args and kwargs.
# XXX Support arbitrary callables. # XXX Support arbitrary callables.
# XXX Support returning the return value (e.g. via pickle). # XXX Support returning the return value (e.g. via pickle).
excinfo = _interpreters.call(self._id, callable) excinfo = _interpreters.call(self._id, callable, restrict=True)
if excinfo is not None: if excinfo is not None:
raise ExecutionFailed(excinfo) raise ExecutionFailed(excinfo)

View file

@ -21,6 +21,14 @@ from .utils import (
) )
WHENCE_STR_UNKNOWN = 'unknown'
WHENCE_STR_RUNTIME = 'runtime init'
WHENCE_STR_LEGACY_CAPI = 'legacy C-API'
WHENCE_STR_CAPI = 'C-API'
WHENCE_STR_XI = 'cross-interpreter C-API'
WHENCE_STR_STDLIB = '_interpreters module'
class ModuleTests(TestBase): class ModuleTests(TestBase):
def test_queue_aliases(self): def test_queue_aliases(self):
@ -173,10 +181,11 @@ class GetCurrentTests(TestBase):
text = self.run_temp_from_capi(f""" text = self.run_temp_from_capi(f"""
import {interpreters.__name__} as interpreters import {interpreters.__name__} as interpreters
interp = interpreters.get_current() interp = interpreters.get_current()
print(interp.id) print((interp.id, interp.whence))
""") """)
interpid = eval(text) interpid, whence = eval(text)
self.assertEqual(interpid, expected) self.assertEqual(interpid, expected)
self.assertEqual(whence, WHENCE_STR_CAPI)
class ListAllTests(TestBase): class ListAllTests(TestBase):
@ -228,22 +237,22 @@ class ListAllTests(TestBase):
interpid4 = interpid3 + 1 interpid4 = interpid3 + 1
interpid5 = interpid4 + 1 interpid5 = interpid4 + 1
expected = [ expected = [
(mainid,), (mainid, WHENCE_STR_RUNTIME),
(interpid1,), (interpid1, WHENCE_STR_STDLIB),
(interpid2,), (interpid2, WHENCE_STR_STDLIB),
(interpid3,), (interpid3, WHENCE_STR_STDLIB),
(interpid4,), (interpid4, WHENCE_STR_CAPI),
(interpid5,), (interpid5, WHENCE_STR_STDLIB),
] ]
expected2 = expected[:-2] expected2 = expected[:-2]
text = self.run_temp_from_capi(f""" text = self.run_temp_from_capi(f"""
import {interpreters.__name__} as interpreters import {interpreters.__name__} as interpreters
interp = interpreters.create() interp = interpreters.create()
print( print(
[(i.id,) for i in interpreters.list_all()]) [(i.id, i.whence) for i in interpreters.list_all()])
""") """)
res = eval(text) res = eval(text)
res2 = [(i.id,) for i in interpreters.list_all()] res2 = [(i.id, i.whence) for i in interpreters.list_all()]
self.assertEqual(res, expected) self.assertEqual(res, expected)
self.assertEqual(res2, expected2) self.assertEqual(res2, expected2)
@ -299,6 +308,38 @@ class InterpreterObjectTests(TestBase):
with self.assertRaises(AttributeError): with self.assertRaises(AttributeError):
interp.id = 1_000_000 interp.id = 1_000_000
def test_whence(self):
main = interpreters.get_main()
interp = interpreters.create()
with self.subTest('main'):
self.assertEqual(main.whence, WHENCE_STR_RUNTIME)
with self.subTest('from _interpreters'):
self.assertEqual(interp.whence, WHENCE_STR_STDLIB)
with self.subTest('from C-API'):
text = self.run_temp_from_capi(f"""
import {interpreters.__name__} as interpreters
interp = interpreters.get_current()
print(repr(interp.whence))
""")
whence = eval(text)
self.assertEqual(whence, WHENCE_STR_CAPI)
with self.subTest('readonly'):
for value in [
None,
WHENCE_STR_UNKNOWN,
WHENCE_STR_RUNTIME,
WHENCE_STR_STDLIB,
WHENCE_STR_CAPI,
]:
with self.assertRaises(AttributeError):
interp.whence = value
with self.assertRaises(AttributeError):
main.whence = value
def test_hashable(self): def test_hashable(self):
interp = interpreters.create() interp = interpreters.create()
expected = hash(interp.id) expected = hash(interp.id)
@ -568,41 +609,42 @@ class TestInterpreterClose(TestBase):
with self.subTest('running __main__ (from self)'): with self.subTest('running __main__ (from self)'):
with self.interpreter_from_capi() as interpid: with self.interpreter_from_capi() as interpid:
with self.assertRaisesRegex(ExecutionFailed, with self.assertRaisesRegex(ExecutionFailed,
'InterpreterError.*current'): 'InterpreterError.*unrecognized'):
self.run_from_capi(interpid, script, main=True) self.run_from_capi(interpid, script, main=True)
with self.subTest('running, but not __main__ (from self)'): with self.subTest('running, but not __main__ (from self)'):
with self.assertRaisesRegex(ExecutionFailed, with self.assertRaisesRegex(ExecutionFailed,
'InterpreterError.*current'): 'InterpreterError.*unrecognized'):
self.run_temp_from_capi(script) self.run_temp_from_capi(script)
with self.subTest('running __main__ (from other)'): with self.subTest('running __main__ (from other)'):
with self.interpreter_obj_from_capi() as (interp, interpid): with self.interpreter_obj_from_capi() as (interp, interpid):
with self.running_from_capi(interpid, main=True): with self.running_from_capi(interpid, main=True):
with self.assertRaisesRegex(InterpreterError, 'running'): with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
interp.close() interp.close()
# Make sure it wssn't closed. # Make sure it wssn't closed.
self.assertTrue( self.assertTrue(
interp.is_running()) self.interp_exists(interpid))
# The rest must be skipped until we deal with running threads when # The rest would be skipped until we deal with running threads when
# interp.close() is called. # interp.close() is called. However, the "whence" restrictions
return # trigger first.
with self.subTest('running, but not __main__ (from other)'): with self.subTest('running, but not __main__ (from other)'):
with self.interpreter_obj_from_capi() as (interp, interpid): with self.interpreter_obj_from_capi() as (interp, interpid):
with self.running_from_capi(interpid, main=False): with self.running_from_capi(interpid, main=False):
with self.assertRaisesRegex(InterpreterError, 'not managed'): with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
interp.close() interp.close()
# Make sure it wssn't closed. # Make sure it wssn't closed.
self.assertFalse(interp.is_running()) self.assertTrue(
self.interp_exists(interpid))
with self.subTest('not running (from other)'): with self.subTest('not running (from other)'):
with self.interpreter_obj_from_capi() as (interp, _): with self.interpreter_obj_from_capi() as (interp, interpid):
with self.assertRaisesRegex(InterpreterError, 'not managed'): with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
interp.close() interp.close()
# Make sure it wssn't closed. self.assertTrue(
self.assertFalse(interp.is_running()) self.interp_exists(interpid))
class TestInterpreterPrepareMain(TestBase): class TestInterpreterPrepareMain(TestBase):
@ -671,12 +713,11 @@ class TestInterpreterPrepareMain(TestBase):
@requires_test_modules @requires_test_modules
def test_created_with_capi(self): def test_created_with_capi(self):
with self.interpreter_from_capi() as interpid: with self.interpreter_obj_from_capi() as (interp, interpid):
interp = interpreters.Interpreter(interpid) with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
interp.prepare_main({'spam': True}) interp.prepare_main({'spam': True})
rc = _testinternalcapi.exec_interpreter(interpid, with self.assertRaisesRegex(ExecutionFailed, 'NameError'):
'assert spam is True') self.run_from_capi(interpid, 'assert spam is True')
assert rc == 0, rc
class TestInterpreterExec(TestBase): class TestInterpreterExec(TestBase):
@ -835,7 +876,7 @@ class TestInterpreterExec(TestBase):
def test_created_with_capi(self): def test_created_with_capi(self):
with self.interpreter_obj_from_capi() as (interp, _): with self.interpreter_obj_from_capi() as (interp, _):
with self.assertRaisesRegex(ExecutionFailed, 'it worked'): with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
interp.exec('raise Exception("it worked!")') interp.exec('raise Exception("it worked!")')
# test_xxsubinterpreters covers the remaining # test_xxsubinterpreters covers the remaining
@ -1114,14 +1155,6 @@ class LowLevelTests(TestBase):
# 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.
def interp_exists(self, interpid):
try:
_interpreters.is_running(interpid)
except InterpreterNotFoundError:
return False
else:
return True
def test_new_config(self): def test_new_config(self):
# This test overlaps with # This test overlaps with
# test.test_capi.test_misc.InterpreterConfigTests. # test.test_capi.test_misc.InterpreterConfigTests.
@ -1245,32 +1278,35 @@ class LowLevelTests(TestBase):
_interpreters.new_config(gil=value) _interpreters.new_config(gil=value)
def test_get_main(self): def test_get_main(self):
interpid, = _interpreters.get_main() interpid, whence = _interpreters.get_main()
self.assertEqual(interpid, 0) self.assertEqual(interpid, 0)
self.assertEqual(whence, _interpreters.WHENCE_RUNTIME)
self.assertEqual(
_interpreters.whence(interpid),
_interpreters.WHENCE_RUNTIME)
def test_get_current(self): def test_get_current(self):
with self.subTest('main'): with self.subTest('main'):
main, *_ = _interpreters.get_main() main, *_ = _interpreters.get_main()
interpid, = _interpreters.get_current() interpid, whence = _interpreters.get_current()
self.assertEqual(interpid, main) self.assertEqual(interpid, main)
self.assertEqual(whence, _interpreters.WHENCE_RUNTIME)
script = f""" script = f"""
import {_interpreters.__name__} as _interpreters import {_interpreters.__name__} as _interpreters
interpid, = _interpreters.get_current() interpid, whence = _interpreters.get_current()
print(interpid) print((interpid, whence))
""" """
def parse_stdout(text): def parse_stdout(text):
parts = text.split() interpid, whence = eval(text)
assert len(parts) == 1, parts return interpid, whence
interpid, = parts
interpid = int(interpid)
return interpid,
with self.subTest('from _interpreters'): with self.subTest('from _interpreters'):
orig = _interpreters.create() orig = _interpreters.create()
text = self.run_and_capture(orig, script) text = self.run_and_capture(orig, script)
interpid, = parse_stdout(text) interpid, whence = parse_stdout(text)
self.assertEqual(interpid, orig) self.assertEqual(interpid, orig)
self.assertEqual(whence, _interpreters.WHENCE_STDLIB)
with self.subTest('from C-API'): with self.subTest('from C-API'):
last = 0 last = 0
@ -1278,8 +1314,9 @@ class LowLevelTests(TestBase):
last = max(last, id) last = max(last, id)
expected = last + 1 expected = last + 1
text = self.run_temp_from_capi(script) text = self.run_temp_from_capi(script)
interpid, = parse_stdout(text) interpid, whence = parse_stdout(text)
self.assertEqual(interpid, expected) self.assertEqual(interpid, expected)
self.assertEqual(whence, _interpreters.WHENCE_CAPI)
def test_list_all(self): def test_list_all(self):
mainid, *_ = _interpreters.get_main() mainid, *_ = _interpreters.get_main()
@ -1287,17 +1324,17 @@ class LowLevelTests(TestBase):
interpid2 = _interpreters.create() interpid2 = _interpreters.create()
interpid3 = _interpreters.create() interpid3 = _interpreters.create()
expected = [ expected = [
(mainid,), (mainid, _interpreters.WHENCE_RUNTIME),
(interpid1,), (interpid1, _interpreters.WHENCE_STDLIB),
(interpid2,), (interpid2, _interpreters.WHENCE_STDLIB),
(interpid3,), (interpid3, _interpreters.WHENCE_STDLIB),
] ]
with self.subTest('main'): with self.subTest('main'):
res = _interpreters.list_all() res = _interpreters.list_all()
self.assertEqual(res, expected) self.assertEqual(res, expected)
with self.subTest('from _interpreters'): with self.subTest('via interp from _interpreters'):
text = self.run_and_capture(interpid2, f""" text = self.run_and_capture(interpid2, f"""
import {_interpreters.__name__} as _interpreters import {_interpreters.__name__} as _interpreters
print( print(
@ -1307,15 +1344,15 @@ class LowLevelTests(TestBase):
res = eval(text) res = eval(text)
self.assertEqual(res, expected) self.assertEqual(res, expected)
with self.subTest('from C-API'): with self.subTest('via interp from C-API'):
interpid4 = interpid3 + 1 interpid4 = interpid3 + 1
interpid5 = interpid4 + 1 interpid5 = interpid4 + 1
expected2 = expected + [ expected2 = expected + [
(interpid4,), (interpid4, _interpreters.WHENCE_CAPI),
(interpid5,), (interpid5, _interpreters.WHENCE_STDLIB),
] ]
expected3 = expected + [ expected3 = expected + [
(interpid5,), (interpid5, _interpreters.WHENCE_STDLIB),
] ]
text = self.run_temp_from_capi(f""" text = self.run_temp_from_capi(f"""
import {_interpreters.__name__} as _interpreters import {_interpreters.__name__} as _interpreters
@ -1380,6 +1417,12 @@ class LowLevelTests(TestBase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
_interpreters.create(orig) _interpreters.create(orig)
with self.subTest('whence'):
interpid = _interpreters.create()
self.assertEqual(
_interpreters.whence(interpid),
_interpreters.WHENCE_STDLIB)
@requires_test_modules @requires_test_modules
def test_destroy(self): def test_destroy(self):
with self.subTest('from _interpreters'): with self.subTest('from _interpreters'):
@ -1401,6 +1444,10 @@ class LowLevelTests(TestBase):
with self.subTest('from C-API'): with self.subTest('from C-API'):
interpid = _testinternalcapi.create_interpreter() interpid = _testinternalcapi.create_interpreter()
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
_interpreters.destroy(interpid, restrict=True)
self.assertTrue(
self.interp_exists(interpid))
_interpreters.destroy(interpid) _interpreters.destroy(interpid)
self.assertFalse( self.assertFalse(
self.interp_exists(interpid)) self.interp_exists(interpid))
@ -1433,6 +1480,8 @@ class LowLevelTests(TestBase):
with self.subTest('from C-API'): with self.subTest('from C-API'):
orig = _interpreters.new_config('isolated') orig = _interpreters.new_config('isolated')
with self.interpreter_from_capi(orig) as interpid: with self.interpreter_from_capi(orig) as interpid:
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
_interpreters.get_config(interpid, restrict=True)
config = _interpreters.get_config(interpid) config = _interpreters.get_config(interpid)
self.assert_ns_equal(config, orig) self.assert_ns_equal(config, orig)
@ -1446,10 +1495,10 @@ class LowLevelTests(TestBase):
with self.subTest('stdlib'): with self.subTest('stdlib'):
interpid = _interpreters.create() interpid = _interpreters.create()
whence = _interpreters.whence(interpid) whence = _interpreters.whence(interpid)
self.assertEqual(whence, _interpreters.WHENCE_XI) self.assertEqual(whence, _interpreters.WHENCE_STDLIB)
for orig, name in { for orig, name in {
# XXX Also check WHENCE_UNKNOWN. _interpreters.WHENCE_UNKNOWN: 'not ready',
_interpreters.WHENCE_LEGACY_CAPI: 'legacy C-API', _interpreters.WHENCE_LEGACY_CAPI: 'legacy C-API',
_interpreters.WHENCE_CAPI: 'C-API', _interpreters.WHENCE_CAPI: 'C-API',
_interpreters.WHENCE_XI: 'cross-interpreter C-API', _interpreters.WHENCE_XI: 'cross-interpreter C-API',
@ -1481,10 +1530,11 @@ class LowLevelTests(TestBase):
self.assertEqual(whence, _interpreters.WHENCE_LEGACY_CAPI) self.assertEqual(whence, _interpreters.WHENCE_LEGACY_CAPI)
def test_is_running(self): def test_is_running(self):
with self.subTest('main'): def check(interpid, expected):
interpid, *_ = _interpreters.get_main() with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
_interpreters.is_running(interpid, restrict=True)
running = _interpreters.is_running(interpid) running = _interpreters.is_running(interpid)
self.assertTrue(running) self.assertIs(running, expected)
with self.subTest('from _interpreters (running)'): with self.subTest('from _interpreters (running)'):
interpid = _interpreters.create() interpid = _interpreters.create()
@ -1497,22 +1547,23 @@ class LowLevelTests(TestBase):
running = _interpreters.is_running(interpid) running = _interpreters.is_running(interpid)
self.assertFalse(running) self.assertFalse(running)
with self.subTest('main'):
interpid, *_ = _interpreters.get_main()
check(interpid, True)
with self.subTest('from C-API (running __main__)'): with self.subTest('from C-API (running __main__)'):
with self.interpreter_from_capi() as interpid: with self.interpreter_from_capi() as interpid:
with self.running_from_capi(interpid, main=True): with self.running_from_capi(interpid, main=True):
running = _interpreters.is_running(interpid) check(interpid, True)
self.assertTrue(running)
with self.subTest('from C-API (running, but not __main__)'): with self.subTest('from C-API (running, but not __main__)'):
with self.interpreter_from_capi() as interpid: with self.interpreter_from_capi() as interpid:
with self.running_from_capi(interpid, main=False): with self.running_from_capi(interpid, main=False):
running = _interpreters.is_running(interpid) check(interpid, False)
self.assertFalse(running)
with self.subTest('from C-API (not running)'): with self.subTest('from C-API (not running)'):
with self.interpreter_from_capi() as interpid: with self.interpreter_from_capi() as interpid:
running = _interpreters.is_running(interpid) check(interpid, False)
self.assertFalse(running)
def test_exec(self): def test_exec(self):
with self.subTest('run script'): with self.subTest('run script'):
@ -1549,6 +1600,9 @@ class LowLevelTests(TestBase):
with self.subTest('from C-API'): with self.subTest('from C-API'):
with self.interpreter_from_capi() as interpid: with self.interpreter_from_capi() as interpid:
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
_interpreters.exec(interpid, 'raise Exception("it worked!")',
restrict=True)
exc = _interpreters.exec(interpid, 'raise Exception("it worked!")') exc = _interpreters.exec(interpid, 'raise Exception("it worked!")')
self.assertIsNot(exc, None) self.assertIsNot(exc, None)
self.assertEqual(exc.msg, 'it worked!') self.assertEqual(exc.msg, 'it worked!')
@ -1574,6 +1628,7 @@ class LowLevelTests(TestBase):
errdisplay=exc.errdisplay, errdisplay=exc.errdisplay,
)) ))
@requires_test_modules
def test_set___main___attrs(self): def test_set___main___attrs(self):
with self.subTest('from _interpreters'): with self.subTest('from _interpreters'):
interpid = _interpreters.create() interpid = _interpreters.create()
@ -1595,9 +1650,15 @@ class LowLevelTests(TestBase):
with self.subTest('from C-API'): with self.subTest('from C-API'):
with self.interpreter_from_capi() as interpid: with self.interpreter_from_capi() as interpid:
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
_interpreters.set___main___attrs(interpid, {'spam': True},
restrict=True)
_interpreters.set___main___attrs(interpid, {'spam': True}) _interpreters.set___main___attrs(interpid, {'spam': True})
exc = _interpreters.exec(interpid, 'assert spam is True') rc = _testinternalcapi.exec_interpreter(
self.assertIsNone(exc) interpid,
'assert spam is True',
)
self.assertEqual(rc, 0)
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -509,6 +509,14 @@ class TestBase(unittest.TestCase):
else: else:
return text return text
def interp_exists(self, interpid):
try:
_interpreters.whence(interpid)
except _interpreters.InterpreterNotFoundError:
return False
else:
return True
@requires_test_modules @requires_test_modules
@contextlib.contextmanager @contextlib.contextmanager
def interpreter_from_capi(self, config=None, whence=None): def interpreter_from_capi(self, config=None, whence=None):
@ -545,7 +553,12 @@ class TestBase(unittest.TestCase):
@contextlib.contextmanager @contextlib.contextmanager
def interpreter_obj_from_capi(self, config='legacy'): def interpreter_obj_from_capi(self, config='legacy'):
with self.interpreter_from_capi(config) as interpid: with self.interpreter_from_capi(config) as interpid:
yield interpreters.Interpreter(interpid), interpid interp = interpreters.Interpreter(
interpid,
_whence=_interpreters.WHENCE_CAPI,
_ownsref=False,
)
yield interp, interpid
@contextlib.contextmanager @contextlib.contextmanager
def capturing(self, script): def capturing(self, script):

View file

@ -1394,7 +1394,7 @@ static PyInterpreterState *
_new_interpreter(PyInterpreterConfig *config, long whence) _new_interpreter(PyInterpreterConfig *config, long whence)
{ {
if (whence == _PyInterpreterState_WHENCE_XI) { if (whence == _PyInterpreterState_WHENCE_XI) {
return _PyXI_NewInterpreter(config, NULL, NULL); return _PyXI_NewInterpreter(config, &whence, NULL, NULL);
} }
PyObject *exc = NULL; PyObject *exc = NULL;
PyInterpreterState *interp = NULL; PyInterpreterState *interp = NULL;
@ -1610,7 +1610,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
/* Create an interpreter, staying switched to it. */ /* Create an interpreter, staying switched to it. */
PyInterpreterState *interp = \ PyInterpreterState *interp = \
_PyXI_NewInterpreter(&config, &tstate, &save_tstate); _PyXI_NewInterpreter(&config, NULL, &tstate, &save_tstate);
if (interp == NULL) { if (interp == NULL) {
return NULL; return NULL;
} }

View file

@ -491,6 +491,55 @@ _run_in_interpreter(PyInterpreterState *interp,
/* module level code ********************************************************/ /* module level code ********************************************************/
static long
get_whence(PyInterpreterState *interp)
{
return _PyInterpreterState_GetWhence(interp);
}
static PyInterpreterState *
resolve_interp(PyObject *idobj, int restricted, int reqready, const char *op)
{
PyInterpreterState *interp;
if (idobj == NULL) {
interp = PyInterpreterState_Get();
}
else {
interp = look_up_interp(idobj);
if (interp == NULL) {
return NULL;
}
}
if (reqready && !_PyInterpreterState_IsReady(interp)) {
if (idobj == NULL) {
PyErr_Format(PyExc_InterpreterError,
"cannot %s current interpreter (not ready)", op);
}
else {
PyErr_Format(PyExc_InterpreterError,
"cannot %s interpreter %R (not ready)", op, idobj);
}
return NULL;
}
if (restricted && get_whence(interp) != _PyInterpreterState_WHENCE_STDLIB) {
if (idobj == NULL) {
PyErr_Format(PyExc_InterpreterError,
"cannot %s unrecognized current interpreter", op);
}
else {
PyErr_Format(PyExc_InterpreterError,
"cannot %s unrecognized interpreter %R", op, idobj);
}
return NULL;
}
return interp;
}
static PyObject * static PyObject *
get_summary(PyInterpreterState *interp) get_summary(PyInterpreterState *interp)
{ {
@ -498,8 +547,15 @@ get_summary(PyInterpreterState *interp)
if (idobj == NULL) { if (idobj == NULL) {
return NULL; return NULL;
} }
PyObject *res = PyTuple_Pack(1, idobj); PyObject *whenceobj = PyLong_FromLong(
get_whence(interp));
if (whenceobj == NULL) {
Py_DECREF(idobj); Py_DECREF(idobj);
return NULL;
}
PyObject *res = PyTuple_Pack(2, idobj, whenceobj);
Py_DECREF(idobj);
Py_DECREF(whenceobj);
return res; return res;
} }
@ -564,7 +620,9 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds)
return NULL; return NULL;
} }
PyInterpreterState *interp = _PyXI_NewInterpreter(&config, NULL, NULL); long whence = _PyInterpreterState_WHENCE_STDLIB;
PyInterpreterState *interp = \
_PyXI_NewInterpreter(&config, &whence, NULL, NULL);
if (interp == NULL) { if (interp == NULL) {
// XXX Move the chained exception to interpreters.create()? // XXX Move the chained exception to interpreters.create()?
PyObject *exc = PyErr_GetRaisedException(); PyObject *exc = PyErr_GetRaisedException();
@ -573,6 +631,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds)
_PyErr_ChainExceptions1(exc); _PyErr_ChainExceptions1(exc);
return NULL; return NULL;
} }
assert(_PyInterpreterState_IsReady(interp));
PyObject *idobj = _PyInterpreterState_GetIDObject(interp); PyObject *idobj = _PyInterpreterState_GetIDObject(interp);
if (idobj == NULL) { if (idobj == NULL) {
@ -607,16 +666,20 @@ is \"isolated\".");
static PyObject * static PyObject *
interp_destroy(PyObject *self, PyObject *args, PyObject *kwds) interp_destroy(PyObject *self, PyObject *args, PyObject *kwds)
{ {
static char *kwlist[] = {"id", NULL}; static char *kwlist[] = {"id", "restrict", NULL};
PyObject *id; PyObject *id;
int restricted = 0;
// XXX Use "L" for id? // XXX Use "L" for id?
if (!PyArg_ParseTupleAndKeywords(args, kwds, if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O:destroy", kwlist, &id)) { "O|$p:destroy", kwlist, &id, &restricted))
{
return NULL; return NULL;
} }
// Look up the interpreter. // Look up the interpreter.
PyInterpreterState *interp = look_up_interp(id); int reqready = 0;
PyInterpreterState *interp = \
resolve_interp(id, restricted, reqready, "destroy");
if (interp == NULL) { if (interp == NULL) {
return NULL; return NULL;
} }
@ -647,7 +710,7 @@ interp_destroy(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyDoc_STRVAR(destroy_doc, PyDoc_STRVAR(destroy_doc,
"destroy(id)\n\ "destroy(id, *, restrict=False)\n\
\n\ \n\
Destroy the identified interpreter.\n\ Destroy the identified interpreter.\n\
\n\ \n\
@ -656,23 +719,31 @@ So does an unrecognized ID.");
static PyObject * static PyObject *
interp_list_all(PyObject *self, PyObject *Py_UNUSED(ignored)) interp_list_all(PyObject *self, PyObject *args, PyObject *kwargs)
{ {
PyObject *ids; static char *kwlist[] = {"require_ready", NULL};
PyInterpreterState *interp; int reqready = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"|$p:" MODULE_NAME_STR ".list_all",
kwlist, &reqready))
{
return NULL;
}
ids = PyList_New(0); PyObject *ids = PyList_New(0);
if (ids == NULL) { if (ids == NULL) {
return NULL; return NULL;
} }
interp = PyInterpreterState_Head(); PyInterpreterState *interp = PyInterpreterState_Head();
while (interp != NULL) { while (interp != NULL) {
if (!reqready || _PyInterpreterState_IsReady(interp)) {
PyObject *item = get_summary(interp); PyObject *item = get_summary(interp);
if (item == NULL) { if (item == NULL) {
Py_DECREF(ids); Py_DECREF(ids);
return NULL; return NULL;
} }
// insert at front of list // insert at front of list
int res = PyList_Insert(ids, 0, item); int res = PyList_Insert(ids, 0, item);
Py_DECREF(item); Py_DECREF(item);
@ -680,7 +751,7 @@ interp_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
Py_DECREF(ids); Py_DECREF(ids);
return NULL; return NULL;
} }
}
interp = PyInterpreterState_Next(interp); interp = PyInterpreterState_Next(interp);
} }
@ -688,7 +759,7 @@ interp_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
} }
PyDoc_STRVAR(list_all_doc, PyDoc_STRVAR(list_all_doc,
"list_all() -> [(ID,)]\n\ "list_all() -> [(ID, whence)]\n\
\n\ \n\
Return a list containing the ID of every existing interpreter."); Return a list containing the ID of every existing interpreter.");
@ -700,11 +771,12 @@ interp_get_current(PyObject *self, PyObject *Py_UNUSED(ignored))
if (interp == NULL) { if (interp == NULL) {
return NULL; return NULL;
} }
assert(_PyInterpreterState_IsReady(interp));
return get_summary(interp); return get_summary(interp);
} }
PyDoc_STRVAR(get_current_doc, PyDoc_STRVAR(get_current_doc,
"get_current() -> (ID,)\n\ "get_current() -> (ID, whence)\n\
\n\ \n\
Return the ID of current interpreter."); Return the ID of current interpreter.");
@ -713,26 +785,33 @@ static PyObject *
interp_get_main(PyObject *self, PyObject *Py_UNUSED(ignored)) interp_get_main(PyObject *self, PyObject *Py_UNUSED(ignored))
{ {
PyInterpreterState *interp = _PyInterpreterState_Main(); PyInterpreterState *interp = _PyInterpreterState_Main();
assert(_PyInterpreterState_IsReady(interp));
return get_summary(interp); return get_summary(interp);
} }
PyDoc_STRVAR(get_main_doc, PyDoc_STRVAR(get_main_doc,
"get_main() -> (ID,)\n\ "get_main() -> (ID, whence)\n\
\n\ \n\
Return the ID of main interpreter."); Return the ID of main interpreter.");
static PyObject * static PyObject *
interp_set___main___attrs(PyObject *self, PyObject *args) interp_set___main___attrs(PyObject *self, PyObject *args, PyObject *kwargs)
{ {
static char *kwlist[] = {"id", "updates", "restrict", NULL};
PyObject *id, *updates; PyObject *id, *updates;
if (!PyArg_ParseTuple(args, "OO:" MODULE_NAME_STR ".set___main___attrs", int restricted = 0;
&id, &updates)) if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"OO|$p:" MODULE_NAME_STR ".set___main___attrs",
kwlist, &id, &updates, &restricted))
{ {
return NULL; return NULL;
} }
// Look up the interpreter. // Look up the interpreter.
PyInterpreterState *interp = look_up_interp(id); int reqready = 1;
PyInterpreterState *interp = \
resolve_interp(id, restricted, reqready, "update __main__ for");
if (interp == NULL) { if (interp == NULL) {
return NULL; return NULL;
} }
@ -771,10 +850,11 @@ interp_set___main___attrs(PyObject *self, PyObject *args)
} }
PyDoc_STRVAR(set___main___attrs_doc, PyDoc_STRVAR(set___main___attrs_doc,
"set___main___attrs(id, ns)\n\ "set___main___attrs(id, ns, *, restrict=False)\n\
\n\ \n\
Bind the given attributes in the interpreter's __main__ module."); Bind the given attributes in the interpreter's __main__ module.");
static PyUnicodeObject * static PyUnicodeObject *
convert_script_arg(PyObject *arg, const char *fname, const char *displayname, convert_script_arg(PyObject *arg, const char *fname, const char *displayname,
const char *expected) const char *expected)
@ -852,16 +932,9 @@ convert_code_arg(PyObject *arg, const char *fname, const char *displayname,
} }
static int static int
_interp_exec(PyObject *self, _interp_exec(PyObject *self, PyInterpreterState *interp,
PyObject *id_arg, PyObject *code_arg, PyObject *shared_arg, PyObject *code_arg, PyObject *shared_arg, PyObject **p_excinfo)
PyObject **p_excinfo)
{ {
// Look up the interpreter.
PyInterpreterState *interp = look_up_interp(id_arg);
if (interp == NULL) {
return -1;
}
// Extract code. // Extract code.
Py_ssize_t codestrlen = -1; Py_ssize_t codestrlen = -1;
PyObject *bytes_obj = NULL; PyObject *bytes_obj = NULL;
@ -886,12 +959,21 @@ _interp_exec(PyObject *self,
static PyObject * static PyObject *
interp_exec(PyObject *self, PyObject *args, PyObject *kwds) interp_exec(PyObject *self, PyObject *args, PyObject *kwds)
{ {
static char *kwlist[] = {"id", "code", "shared", NULL}; static char *kwlist[] = {"id", "code", "shared", "restrict", NULL};
PyObject *id, *code; PyObject *id, *code;
PyObject *shared = NULL; PyObject *shared = NULL;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, if (!PyArg_ParseTupleAndKeywords(args, kwds,
"OO|O:" MODULE_NAME_STR ".exec", kwlist, "OO|O$p:" MODULE_NAME_STR ".exec", kwlist,
&id, &code, &shared)) { &id, &code, &shared, &restricted))
{
return NULL;
}
int reqready = 1;
PyInterpreterState *interp = \
resolve_interp(id, restricted, reqready, "exec code for");
if (interp == NULL) {
return NULL; return NULL;
} }
@ -909,7 +991,7 @@ interp_exec(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyObject *excinfo = NULL; PyObject *excinfo = NULL;
int res = _interp_exec(self, id, code, shared, &excinfo); int res = _interp_exec(self, interp, code, shared, &excinfo);
Py_DECREF(code); Py_DECREF(code);
if (res < 0) { if (res < 0) {
assert((excinfo == NULL) != (PyErr_Occurred() == NULL)); assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
@ -919,7 +1001,7 @@ interp_exec(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyDoc_STRVAR(exec_doc, PyDoc_STRVAR(exec_doc,
"exec(id, code, shared=None)\n\ "exec(id, code, shared=None, *, restrict=False)\n\
\n\ \n\
Execute the provided code in the identified interpreter.\n\ Execute the provided code in the identified interpreter.\n\
This is equivalent to running the builtin exec() under the target\n\ This is equivalent to running the builtin exec() under the target\n\
@ -938,13 +1020,24 @@ is ignored, including its __globals__ dict.");
static PyObject * static PyObject *
interp_call(PyObject *self, PyObject *args, PyObject *kwds) interp_call(PyObject *self, PyObject *args, PyObject *kwds)
{ {
static char *kwlist[] = {"id", "callable", "args", "kwargs", NULL}; static char *kwlist[] = {"id", "callable", "args", "kwargs",
"restrict", NULL};
PyObject *id, *callable; PyObject *id, *callable;
PyObject *args_obj = NULL; PyObject *args_obj = NULL;
PyObject *kwargs_obj = NULL; PyObject *kwargs_obj = NULL;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, if (!PyArg_ParseTupleAndKeywords(args, kwds,
"OO|OO:" MODULE_NAME_STR ".call", kwlist, "OO|OO$p:" MODULE_NAME_STR ".call", kwlist,
&id, &callable, &args_obj, &kwargs_obj)) { &id, &callable, &args_obj, &kwargs_obj,
&restricted))
{
return NULL;
}
int reqready = 1;
PyInterpreterState *interp = \
resolve_interp(id, restricted, reqready, "make a call in");
if (interp == NULL) {
return NULL; return NULL;
} }
@ -964,7 +1057,7 @@ interp_call(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyObject *excinfo = NULL; PyObject *excinfo = NULL;
int res = _interp_exec(self, id, code, NULL, &excinfo); int res = _interp_exec(self, interp, code, NULL, &excinfo);
Py_DECREF(code); Py_DECREF(code);
if (res < 0) { if (res < 0) {
assert((excinfo == NULL) != (PyErr_Occurred() == NULL)); assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
@ -974,7 +1067,7 @@ interp_call(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyDoc_STRVAR(call_doc, PyDoc_STRVAR(call_doc,
"call(id, callable, args=None, kwargs=None)\n\ "call(id, callable, args=None, kwargs=None, *, restrict=False)\n\
\n\ \n\
Call the provided object in the identified interpreter.\n\ Call the provided object in the identified interpreter.\n\
Pass the given args and kwargs, if possible.\n\ Pass the given args and kwargs, if possible.\n\
@ -988,12 +1081,21 @@ is ignored, including its __globals__ dict.");
static PyObject * static PyObject *
interp_run_string(PyObject *self, PyObject *args, PyObject *kwds) interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
{ {
static char *kwlist[] = {"id", "script", "shared", NULL}; static char *kwlist[] = {"id", "script", "shared", "restrict", NULL};
PyObject *id, *script; PyObject *id, *script;
PyObject *shared = NULL; PyObject *shared = NULL;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, if (!PyArg_ParseTupleAndKeywords(args, kwds,
"OU|O:" MODULE_NAME_STR ".run_string", kwlist, "OU|O$p:" MODULE_NAME_STR ".run_string",
&id, &script, &shared)) { kwlist, &id, &script, &shared, &restricted))
{
return NULL;
}
int reqready = 1;
PyInterpreterState *interp = \
resolve_interp(id, restricted, reqready, "run a string in");
if (interp == NULL) {
return NULL; return NULL;
} }
@ -1004,7 +1106,7 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyObject *excinfo = NULL; PyObject *excinfo = NULL;
int res = _interp_exec(self, id, script, shared, &excinfo); int res = _interp_exec(self, interp, script, shared, &excinfo);
Py_DECREF(script); Py_DECREF(script);
if (res < 0) { if (res < 0) {
assert((excinfo == NULL) != (PyErr_Occurred() == NULL)); assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
@ -1014,7 +1116,7 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyDoc_STRVAR(run_string_doc, PyDoc_STRVAR(run_string_doc,
"run_string(id, script, shared=None)\n\ "run_string(id, script, shared=None, *, restrict=False)\n\
\n\ \n\
Execute the provided string in the identified interpreter.\n\ Execute the provided string in the identified interpreter.\n\
\n\ \n\
@ -1023,12 +1125,21 @@ Execute the provided string in the identified interpreter.\n\
static PyObject * static PyObject *
interp_run_func(PyObject *self, PyObject *args, PyObject *kwds) interp_run_func(PyObject *self, PyObject *args, PyObject *kwds)
{ {
static char *kwlist[] = {"id", "func", "shared", NULL}; static char *kwlist[] = {"id", "func", "shared", "restrict", NULL};
PyObject *id, *func; PyObject *id, *func;
PyObject *shared = NULL; PyObject *shared = NULL;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, if (!PyArg_ParseTupleAndKeywords(args, kwds,
"OO|O:" MODULE_NAME_STR ".run_func", kwlist, "OO|O$p:" MODULE_NAME_STR ".run_func",
&id, &func, &shared)) { kwlist, &id, &func, &shared, &restricted))
{
return NULL;
}
int reqready = 1;
PyInterpreterState *interp = \
resolve_interp(id, restricted, reqready, "run a function in");
if (interp == NULL) {
return NULL; return NULL;
} }
@ -1040,7 +1151,7 @@ interp_run_func(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyObject *excinfo = NULL; PyObject *excinfo = NULL;
int res = _interp_exec(self, id, (PyObject *)code, shared, &excinfo); int res = _interp_exec(self, interp, (PyObject *)code, shared, &excinfo);
Py_DECREF(code); Py_DECREF(code);
if (res < 0) { if (res < 0) {
assert((excinfo == NULL) != (PyErr_Occurred() == NULL)); assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
@ -1050,7 +1161,7 @@ interp_run_func(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyDoc_STRVAR(run_func_doc, PyDoc_STRVAR(run_func_doc,
"run_func(id, func, shared=None)\n\ "run_func(id, func, shared=None, *, restrict=False)\n\
\n\ \n\
Execute the body of the provided function in the identified interpreter.\n\ Execute the body of the provided function in the identified interpreter.\n\
Code objects are also supported. In both cases, closures and args\n\ Code objects are also supported. In both cases, closures and args\n\
@ -1086,17 +1197,23 @@ False otherwise.");
static PyObject * static PyObject *
interp_is_running(PyObject *self, PyObject *args, PyObject *kwds) interp_is_running(PyObject *self, PyObject *args, PyObject *kwds)
{ {
static char *kwlist[] = {"id", NULL}; static char *kwlist[] = {"id", "restrict", NULL};
PyObject *id; PyObject *id;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O:is_running", kwlist, &id)) { "O|$p:is_running", kwlist,
&id, &restricted))
{
return NULL; return NULL;
} }
PyInterpreterState *interp = look_up_interp(id); int reqready = 1;
PyInterpreterState *interp = \
resolve_interp(id, restricted, reqready, "check if running for");
if (interp == NULL) { if (interp == NULL) {
return NULL; return NULL;
} }
if (is_running_main(interp)) { if (is_running_main(interp)) {
Py_RETURN_TRUE; Py_RETURN_TRUE;
} }
@ -1104,7 +1221,7 @@ interp_is_running(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyDoc_STRVAR(is_running_doc, PyDoc_STRVAR(is_running_doc,
"is_running(id) -> bool\n\ "is_running(id, *, restrict=False) -> bool\n\
\n\ \n\
Return whether or not the identified interpreter is running."); Return whether or not the identified interpreter is running.");
@ -1112,24 +1229,25 @@ Return whether or not the identified interpreter is running.");
static PyObject * static PyObject *
interp_get_config(PyObject *self, PyObject *args, PyObject *kwds) interp_get_config(PyObject *self, PyObject *args, PyObject *kwds)
{ {
static char *kwlist[] = {"id", NULL}; static char *kwlist[] = {"id", "restrict", NULL};
PyObject *idobj = NULL; PyObject *idobj = NULL;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O:get_config", kwlist, &idobj)) "O|$p:get_config", kwlist,
&idobj, &restricted))
{ {
return NULL; return NULL;
} }
if (idobj == Py_None) {
PyInterpreterState *interp; idobj = NULL;
if (idobj == NULL) {
interp = PyInterpreterState_Get();
} }
else {
interp = _PyInterpreterState_LookUpIDObject(idobj); int reqready = 0;
PyInterpreterState *interp = \
resolve_interp(idobj, restricted, reqready, "get the config of");
if (interp == NULL) { if (interp == NULL) {
return NULL; return NULL;
} }
}
PyInterpreterConfig config; PyInterpreterConfig config;
if (_PyInterpreterConfig_InitFromState(&config, interp) < 0) { if (_PyInterpreterConfig_InitFromState(&config, interp) < 0) {
@ -1146,7 +1264,7 @@ interp_get_config(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyDoc_STRVAR(get_config_doc, PyDoc_STRVAR(get_config_doc,
"get_config(id) -> types.SimpleNamespace\n\ "get_config(id, *, restrict=False) -> types.SimpleNamespace\n\
\n\ \n\
Return a representation of the config used to initialize the interpreter."); Return a representation of the config used to initialize the interpreter.");
@ -1167,7 +1285,7 @@ interp_whence(PyObject *self, PyObject *args, PyObject *kwds)
return NULL; return NULL;
} }
long whence = _PyInterpreterState_GetWhence(interp); long whence = get_whence(interp);
return PyLong_FromLong(whence); return PyLong_FromLong(whence);
} }
@ -1180,17 +1298,20 @@ Return an identifier for where the interpreter was created.");
static PyObject * static PyObject *
interp_incref(PyObject *self, PyObject *args, PyObject *kwds) interp_incref(PyObject *self, PyObject *args, PyObject *kwds)
{ {
static char *kwlist[] = {"id", "implieslink", NULL}; static char *kwlist[] = {"id", "implieslink", "restrict", NULL};
PyObject *id; PyObject *id;
int implieslink = 0; int implieslink = 0;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O|$p:incref", kwlist, "O|$pp:incref", kwlist,
&id, &implieslink)) &id, &implieslink, &restricted))
{ {
return NULL; return NULL;
} }
PyInterpreterState *interp = look_up_interp(id); int reqready = 1;
PyInterpreterState *interp = \
resolve_interp(id, restricted, reqready, "incref");
if (interp == NULL) { if (interp == NULL) {
return NULL; return NULL;
} }
@ -1208,17 +1329,22 @@ interp_incref(PyObject *self, PyObject *args, PyObject *kwds)
static PyObject * static PyObject *
interp_decref(PyObject *self, PyObject *args, PyObject *kwds) interp_decref(PyObject *self, PyObject *args, PyObject *kwds)
{ {
static char *kwlist[] = {"id", NULL}; static char *kwlist[] = {"id", "restrict", NULL};
PyObject *id; PyObject *id;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O:decref", kwlist, &id)) { "O|$p:decref", kwlist, &id, &restricted))
{
return NULL; return NULL;
} }
PyInterpreterState *interp = look_up_interp(id); int reqready = 1;
PyInterpreterState *interp = \
resolve_interp(id, restricted, reqready, "decref");
if (interp == NULL) { if (interp == NULL) {
return NULL; return NULL;
} }
_PyInterpreterState_IDDecref(interp); _PyInterpreterState_IDDecref(interp);
Py_RETURN_NONE; Py_RETURN_NONE;
@ -1301,8 +1427,8 @@ static PyMethodDef module_functions[] = {
METH_VARARGS | METH_KEYWORDS, create_doc}, METH_VARARGS | METH_KEYWORDS, create_doc},
{"destroy", _PyCFunction_CAST(interp_destroy), {"destroy", _PyCFunction_CAST(interp_destroy),
METH_VARARGS | METH_KEYWORDS, destroy_doc}, METH_VARARGS | METH_KEYWORDS, destroy_doc},
{"list_all", interp_list_all, {"list_all", _PyCFunction_CAST(interp_list_all),
METH_NOARGS, list_all_doc}, METH_VARARGS | METH_KEYWORDS, list_all_doc},
{"get_current", interp_get_current, {"get_current", interp_get_current,
METH_NOARGS, get_current_doc}, METH_NOARGS, get_current_doc},
{"get_main", interp_get_main, {"get_main", interp_get_main,
@ -1324,7 +1450,7 @@ static PyMethodDef module_functions[] = {
METH_VARARGS | METH_KEYWORDS, run_func_doc}, METH_VARARGS | METH_KEYWORDS, run_func_doc},
{"set___main___attrs", _PyCFunction_CAST(interp_set___main___attrs), {"set___main___attrs", _PyCFunction_CAST(interp_set___main___attrs),
METH_VARARGS, set___main___attrs_doc}, METH_VARARGS | METH_KEYWORDS, set___main___attrs_doc},
{"incref", _PyCFunction_CAST(interp_incref), {"incref", _PyCFunction_CAST(interp_incref),
METH_VARARGS | METH_KEYWORDS, NULL}, METH_VARARGS | METH_KEYWORDS, NULL},
@ -1364,6 +1490,7 @@ module_exec(PyObject *mod)
ADD_WHENCE(LEGACY_CAPI) ADD_WHENCE(LEGACY_CAPI)
ADD_WHENCE(CAPI) ADD_WHENCE(CAPI)
ADD_WHENCE(XI) ADD_WHENCE(XI)
ADD_WHENCE(STDLIB)
#undef ADD_WHENCE #undef ADD_WHENCE
// exceptions // exceptions

View file

@ -1822,7 +1822,7 @@ _PyXI_FiniTypes(PyInterpreterState *interp)
/*************/ /*************/
PyInterpreterState * PyInterpreterState *
_PyXI_NewInterpreter(PyInterpreterConfig *config, _PyXI_NewInterpreter(PyInterpreterConfig *config, long *maybe_whence,
PyThreadState **p_tstate, PyThreadState **p_save_tstate) PyThreadState **p_tstate, PyThreadState **p_save_tstate)
{ {
PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *save_tstate = PyThreadState_Swap(NULL);
@ -1845,7 +1845,11 @@ _PyXI_NewInterpreter(PyInterpreterConfig *config,
assert(tstate != NULL); assert(tstate != NULL);
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
_PyInterpreterState_SetWhence(interp, _PyInterpreterState_WHENCE_XI); long whence = _PyInterpreterState_WHENCE_XI;
if (maybe_whence != NULL) {
whence = *maybe_whence;
}
_PyInterpreterState_SetWhence(interp, whence);
if (p_tstate != NULL) { if (p_tstate != NULL) {
// We leave the new thread state as the current one. // We leave the new thread state as the current one.
@ -1868,6 +1872,22 @@ void
_PyXI_EndInterpreter(PyInterpreterState *interp, _PyXI_EndInterpreter(PyInterpreterState *interp,
PyThreadState *tstate, PyThreadState **p_save_tstate) PyThreadState *tstate, PyThreadState **p_save_tstate)
{ {
#ifndef NDEBUG
long whence = _PyInterpreterState_GetWhence(interp);
#endif
assert(whence != _PyInterpreterState_WHENCE_RUNTIME);
if (!_PyInterpreterState_IsReady(interp)) {
assert(whence == _PyInterpreterState_WHENCE_UNKNOWN);
// PyInterpreterState_Clear() requires the GIL,
// which a not-ready does not have, so we don't clear it.
// That means there may be leaks here until clearing the
// interpreter is fixed.
PyInterpreterState_Delete(interp);
return;
}
assert(whence != _PyInterpreterState_WHENCE_UNKNOWN);
PyThreadState *save_tstate = NULL; PyThreadState *save_tstate = NULL;
PyThreadState *cur_tstate = PyThreadState_GET(); PyThreadState *cur_tstate = PyThreadState_GET();
if (tstate == NULL) { if (tstate == NULL) {
@ -1889,18 +1909,7 @@ _PyXI_EndInterpreter(PyInterpreterState *interp,
} }
} }
long whence = _PyInterpreterState_GetWhence(interp);
assert(whence != _PyInterpreterState_WHENCE_RUNTIME);
if (whence == _PyInterpreterState_WHENCE_UNKNOWN) {
assert(!interp->_ready);
PyThreadState *tstate = PyThreadState_New(interp);
save_tstate = PyThreadState_Swap(tstate);
_PyInterpreterState_Clear(tstate);
PyInterpreterState_Delete(interp);
}
else {
Py_EndInterpreter(tstate); Py_EndInterpreter(tstate);
}
if (p_save_tstate != NULL) { if (p_save_tstate != NULL) {
save_tstate = *p_save_tstate; save_tstate = *p_save_tstate;

View file

@ -1111,6 +1111,13 @@ _PyInterpreterState_ReinitRunningMain(PyThreadState *tstate)
// accessors // accessors
//---------- //----------
int
_PyInterpreterState_IsReady(PyInterpreterState *interp)
{
return interp->_ready;
}
static inline int static inline int
check_interpreter_whence(long whence) check_interpreter_whence(long whence)
{ {