Skip to content

Commit fd259fd

Browse files
authoredApr 11, 2024··
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.
1 parent fd2bab9 commit fd259fd

File tree

9 files changed

+454
-200
lines changed

9 files changed

+454
-200
lines changed
 

‎Include/internal/pycore_crossinterp.h

+1
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
325325
// Export for _testinternalcapi shared extension
326326
PyAPI_FUNC(PyInterpreterState *) _PyXI_NewInterpreter(
327327
PyInterpreterConfig *config,
328+
long *maybe_whence,
328329
PyThreadState **p_tstate,
329330
PyThreadState **p_save_tstate);
330331
PyAPI_FUNC(void) _PyXI_EndInterpreter(

‎Include/internal/pycore_interp.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ struct _is {
109109
#define _PyInterpreterState_WHENCE_LEGACY_CAPI 2
110110
#define _PyInterpreterState_WHENCE_CAPI 3
111111
#define _PyInterpreterState_WHENCE_XI 4
112-
#define _PyInterpreterState_WHENCE_MAX 4
112+
#define _PyInterpreterState_WHENCE_STDLIB 5
113+
#define _PyInterpreterState_WHENCE_MAX 5
113114
long _whence;
114115

115116
/* Has been initialized to a safe state.
@@ -316,6 +317,8 @@ PyAPI_FUNC(int) _PyInterpreterState_IDInitref(PyInterpreterState *);
316317
PyAPI_FUNC(int) _PyInterpreterState_IDIncref(PyInterpreterState *);
317318
PyAPI_FUNC(void) _PyInterpreterState_IDDecref(PyInterpreterState *);
318319

320+
PyAPI_FUNC(int) _PyInterpreterState_IsReady(PyInterpreterState *interp);
321+
319322
PyAPI_FUNC(long) _PyInterpreterState_GetWhence(PyInterpreterState *interp);
320323
extern void _PyInterpreterState_SetWhence(
321324
PyInterpreterState *interp,

‎Lib/test/support/interpreters/__init__.py

+56-23
Original file line numberDiff line numberDiff line change
@@ -74,51 +74,77 @@ def __str__(self):
7474
def create():
7575
"""Return a new (idle) Python interpreter."""
7676
id = _interpreters.create(reqrefs=True)
77-
return Interpreter(id)
77+
return Interpreter(id, _ownsref=True)
7878

7979

8080
def list_all():
8181
"""Return all existing interpreters."""
82-
return [Interpreter(id)
83-
for id, in _interpreters.list_all()]
82+
return [Interpreter(id, _whence=whence)
83+
for id, whence in _interpreters.list_all(require_ready=True)]
8484

8585

8686
def get_current():
8787
"""Return the currently running interpreter."""
88-
id, = _interpreters.get_current()
89-
return Interpreter(id)
88+
id, whence = _interpreters.get_current()
89+
return Interpreter(id, _whence=whence)
9090

9191

9292
def get_main():
9393
"""Return the main interpreter."""
94-
id, = _interpreters.get_main()
95-
return Interpreter(id)
94+
id, whence = _interpreters.get_main()
95+
assert whence == _interpreters.WHENCE_RUNTIME, repr(whence)
96+
return Interpreter(id, _whence=whence)
9697

9798

9899
_known = weakref.WeakValueDictionary()
99100

100101
class Interpreter:
101-
"""A single Python interpreter."""
102+
"""A single Python interpreter.
102103
103-
def __new__(cls, id, /):
104+
Attributes:
105+
106+
"id" - the unique process-global ID number for the interpreter
107+
"whence" - indicates where the interpreter was created
108+
109+
If the interpreter wasn't created by this module
110+
then any method that modifies the interpreter will fail,
111+
i.e. .close(), .prepare_main(), .exec(), and .call()
112+
"""
113+
114+
_WHENCE_TO_STR = {
115+
_interpreters.WHENCE_UNKNOWN: 'unknown',
116+
_interpreters.WHENCE_RUNTIME: 'runtime init',
117+
_interpreters.WHENCE_LEGACY_CAPI: 'legacy C-API',
118+
_interpreters.WHENCE_CAPI: 'C-API',
119+
_interpreters.WHENCE_XI: 'cross-interpreter C-API',
120+
_interpreters.WHENCE_STDLIB: '_interpreters module',
121+
}
122+
123+
def __new__(cls, id, /, _whence=None, _ownsref=None):
104124
# There is only one instance for any given ID.
105125
if not isinstance(id, int):
106126
raise TypeError(f'id must be an int, got {id!r}')
107127
id = int(id)
128+
if _whence is None:
129+
if _ownsref:
130+
_whence = _interpreters.WHENCE_STDLIB
131+
else:
132+
_whence = _interpreters.whence(id)
133+
assert _whence in cls._WHENCE_TO_STR, repr(_whence)
134+
if _ownsref is None:
135+
_ownsref = (_whence == _interpreters.WHENCE_STDLIB)
108136
try:
109137
self = _known[id]
110138
assert hasattr(self, '_ownsref')
111139
except KeyError:
112-
# This may raise InterpreterNotFoundError:
113-
_interpreters.incref(id)
114-
try:
115-
self = super().__new__(cls)
116-
self._id = id
117-
self._ownsref = True
118-
except BaseException:
119-
_interpreters.decref(id)
120-
raise
140+
self = super().__new__(cls)
121141
_known[id] = self
142+
self._id = id
143+
self._whence = _whence
144+
self._ownsref = _ownsref
145+
if _ownsref:
146+
# This may raise InterpreterNotFoundError:
147+
_interpreters.incref(id)
122148
return self
123149

124150
def __repr__(self):
@@ -143,33 +169,40 @@ def _decref(self):
143169
return
144170
self._ownsref = False
145171
try:
146-
_interpreters.decref(self.id)
172+
_interpreters.decref(self._id)
147173
except InterpreterNotFoundError:
148174
pass
149175

150176
@property
151177
def id(self):
152178
return self._id
153179

180+
@property
181+
def whence(self):
182+
return self._WHENCE_TO_STR[self._whence]
183+
154184
def is_running(self):
155185
"""Return whether or not the identified interpreter is running."""
156186
return _interpreters.is_running(self._id)
157187

188+
# Everything past here is available only to interpreters created by
189+
# interpreters.create().
190+
158191
def close(self):
159192
"""Finalize and destroy the interpreter.
160193
161194
Attempting to destroy the current interpreter results
162195
in an InterpreterError.
163196
"""
164-
return _interpreters.destroy(self._id)
197+
return _interpreters.destroy(self._id, restrict=True)
165198

166199
def prepare_main(self, ns=None, /, **kwargs):
167200
"""Bind the given values into the interpreter's __main__.
168201
169202
The values must be shareable.
170203
"""
171204
ns = dict(ns, **kwargs) if ns is not None else kwargs
172-
_interpreters.set___main___attrs(self._id, ns)
205+
_interpreters.set___main___attrs(self._id, ns, restrict=True)
173206

174207
def exec(self, code, /):
175208
"""Run the given source code in the interpreter.
@@ -189,7 +222,7 @@ def exec(self, code, /):
189222
that time, the previous interpreter is allowed to run
190223
in other threads.
191224
"""
192-
excinfo = _interpreters.exec(self._id, code)
225+
excinfo = _interpreters.exec(self._id, code, restrict=True)
193226
if excinfo is not None:
194227
raise ExecutionFailed(excinfo)
195228

@@ -209,7 +242,7 @@ def call(self, callable, /):
209242
# XXX Support args and kwargs.
210243
# XXX Support arbitrary callables.
211244
# XXX Support returning the return value (e.g. via pickle).
212-
excinfo = _interpreters.call(self._id, callable)
245+
excinfo = _interpreters.call(self._id, callable, restrict=True)
213246
if excinfo is not None:
214247
raise ExecutionFailed(excinfo)
215248

0 commit comments

Comments
 (0)
Please sign in to comment.