Skip to content

Commit 86a77f4

Browse files
gh-76785: Fixes for test.support.interpreters (gh-112982)
This involves a number of changes for PEP 734.
1 parent f26bfe4 commit 86a77f4

30 files changed

+2506
-1507
lines changed

.github/CODEOWNERS

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ Python/traceback.c @iritkatriel
7878
**/*importlib/resources/* @jaraco @warsaw @FFY00
7979
**/importlib/metadata/* @jaraco @warsaw
8080

81+
# Subinterpreters
82+
Lib/test/support/interpreters/** @ericsnowcurrently
83+
Modules/_xx*interp*module.c @ericsnowcurrently
84+
Lib/test/test_interpreters/** @ericsnowcurrently
85+
8186
# Dates and times
8287
**/*datetime* @pganssle @abalkin
8388
**/*str*time* @pganssle @abalkin
@@ -148,7 +153,15 @@ Doc/c-api/stable.rst @encukou
148153
**/*itertools* @rhettinger
149154
**/*collections* @rhettinger
150155
**/*random* @rhettinger
151-
**/*queue* @rhettinger
156+
Doc/**/*queue* @rhettinger
157+
PCbuild/**/*queue* @rhettinger
158+
Modules/_queuemodule.c @rhettinger
159+
Lib/*queue*.py @rhettinger
160+
Lib/asyncio/*queue*.py @rhettinger
161+
Lib/multiprocessing/*queue*.py @rhettinger
162+
Lib/test/*queue*.py @rhettinger
163+
Lib/test_asyncio/*queue*.py @rhettinger
164+
Lib/test_multiprocessing/*queue*.py @rhettinger
152165
**/*bisect* @rhettinger
153166
**/*heapq* @rhettinger
154167
**/*functools* @rhettinger

Include/internal/pycore_crossinterp.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ extern "C" {
1111
#include "pycore_lock.h" // PyMutex
1212
#include "pycore_pyerrors.h"
1313

14+
/**************/
15+
/* exceptions */
16+
/**************/
17+
18+
PyAPI_DATA(PyObject *) PyExc_InterpreterError;
19+
PyAPI_DATA(PyObject *) PyExc_InterpreterNotFoundError;
20+
1421

1522
/***************************/
1623
/* cross-interpreter calls */
@@ -160,6 +167,9 @@ struct _xi_state {
160167
extern PyStatus _PyXI_Init(PyInterpreterState *interp);
161168
extern void _PyXI_Fini(PyInterpreterState *interp);
162169

170+
extern PyStatus _PyXI_InitTypes(PyInterpreterState *interp);
171+
extern void _PyXI_FiniTypes(PyInterpreterState *interp);
172+
163173

164174
/***************************/
165175
/* short-term data sharing */

Include/internal/pycore_interp.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,9 @@ _PyInterpreterState_SetFinalizing(PyInterpreterState *interp, PyThreadState *tst
250250
// Export for the _xxinterpchannels module.
251251
PyAPI_FUNC(PyInterpreterState *) _PyInterpreterState_LookUpID(int64_t);
252252

253-
extern int _PyInterpreterState_IDInitref(PyInterpreterState *);
254-
extern int _PyInterpreterState_IDIncref(PyInterpreterState *);
255-
extern void _PyInterpreterState_IDDecref(PyInterpreterState *);
253+
PyAPI_FUNC(int) _PyInterpreterState_IDInitref(PyInterpreterState *);
254+
PyAPI_FUNC(int) _PyInterpreterState_IDIncref(PyInterpreterState *);
255+
PyAPI_FUNC(void) _PyInterpreterState_IDDecref(PyInterpreterState *);
256256

257257
extern const PyConfig* _PyInterpreterState_GetConfig(PyInterpreterState *interp);
258258

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""Subinterpreters High Level Module."""
2+
3+
import threading
4+
import weakref
5+
import _xxsubinterpreters as _interpreters
6+
7+
# aliases:
8+
from _xxsubinterpreters import (
9+
InterpreterError, InterpreterNotFoundError,
10+
is_shareable,
11+
)
12+
13+
14+
__all__ = [
15+
'get_current', 'get_main', 'create', 'list_all', 'is_shareable',
16+
'Interpreter',
17+
'InterpreterError', 'InterpreterNotFoundError', 'ExecFailure',
18+
'create_queue', 'Queue', 'QueueEmpty', 'QueueFull',
19+
]
20+
21+
22+
_queuemod = None
23+
24+
def __getattr__(name):
25+
if name in ('Queue', 'QueueEmpty', 'QueueFull', 'create_queue'):
26+
global create_queue, Queue, QueueEmpty, QueueFull
27+
ns = globals()
28+
from .queues import (
29+
create as create_queue,
30+
Queue, QueueEmpty, QueueFull,
31+
)
32+
return ns[name]
33+
else:
34+
raise AttributeError(name)
35+
36+
37+
class ExecFailure(RuntimeError):
38+
39+
def __init__(self, excinfo):
40+
msg = excinfo.formatted
41+
if not msg:
42+
if excinfo.type and snapshot.msg:
43+
msg = f'{snapshot.type.__name__}: {snapshot.msg}'
44+
else:
45+
msg = snapshot.type.__name__ or snapshot.msg
46+
super().__init__(msg)
47+
self.snapshot = excinfo
48+
49+
50+
def create():
51+
"""Return a new (idle) Python interpreter."""
52+
id = _interpreters.create(isolated=True)
53+
return Interpreter(id)
54+
55+
56+
def list_all():
57+
"""Return all existing interpreters."""
58+
return [Interpreter(id) for id in _interpreters.list_all()]
59+
60+
61+
def get_current():
62+
"""Return the currently running interpreter."""
63+
id = _interpreters.get_current()
64+
return Interpreter(id)
65+
66+
67+
def get_main():
68+
"""Return the main interpreter."""
69+
id = _interpreters.get_main()
70+
return Interpreter(id)
71+
72+
73+
_known = weakref.WeakValueDictionary()
74+
75+
class Interpreter:
76+
"""A single Python interpreter."""
77+
78+
def __new__(cls, id, /):
79+
# There is only one instance for any given ID.
80+
if not isinstance(id, int):
81+
raise TypeError(f'id must be an int, got {id!r}')
82+
id = int(id)
83+
try:
84+
self = _known[id]
85+
assert hasattr(self, '_ownsref')
86+
except KeyError:
87+
# This may raise InterpreterNotFoundError:
88+
_interpreters._incref(id)
89+
try:
90+
self = super().__new__(cls)
91+
self._id = id
92+
self._ownsref = True
93+
except BaseException:
94+
_interpreters._deccref(id)
95+
raise
96+
_known[id] = self
97+
return self
98+
99+
def __repr__(self):
100+
return f'{type(self).__name__}({self.id})'
101+
102+
def __hash__(self):
103+
return hash(self._id)
104+
105+
def __del__(self):
106+
self._decref()
107+
108+
def _decref(self):
109+
if not self._ownsref:
110+
return
111+
self._ownsref = False
112+
try:
113+
_interpreters._decref(self.id)
114+
except InterpreterNotFoundError:
115+
pass
116+
117+
@property
118+
def id(self):
119+
return self._id
120+
121+
def is_running(self):
122+
"""Return whether or not the identified interpreter is running."""
123+
return _interpreters.is_running(self._id)
124+
125+
def close(self):
126+
"""Finalize and destroy the interpreter.
127+
128+
Attempting to destroy the current interpreter results
129+
in a RuntimeError.
130+
"""
131+
return _interpreters.destroy(self._id)
132+
133+
def exec_sync(self, code, /, channels=None):
134+
"""Run the given source code in the interpreter.
135+
136+
This is essentially the same as calling the builtin "exec"
137+
with this interpreter, using the __dict__ of its __main__
138+
module as both globals and locals.
139+
140+
There is no return value.
141+
142+
If the code raises an unhandled exception then an ExecFailure
143+
is raised, which summarizes the unhandled exception. The actual
144+
exception is discarded because objects cannot be shared between
145+
interpreters.
146+
147+
This blocks the current Python thread until done. During
148+
that time, the previous interpreter is allowed to run
149+
in other threads.
150+
"""
151+
excinfo = _interpreters.exec(self._id, code, channels)
152+
if excinfo is not None:
153+
raise ExecFailure(excinfo)
154+
155+
def run(self, code, /, channels=None):
156+
def task():
157+
self.exec_sync(code, channels=channels)
158+
t = threading.Thread(target=task)
159+
t.start()
160+
return t

Lib/test/support/interpreters.py renamed to Lib/test/support/interpreters/channels.py

Lines changed: 6 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,23 @@
1-
"""Subinterpreters High Level Module."""
1+
"""Cross-interpreter Channels High Level Module."""
22

33
import time
4-
import _xxsubinterpreters as _interpreters
54
import _xxinterpchannels as _channels
65

76
# aliases:
8-
from _xxsubinterpreters import is_shareable
97
from _xxinterpchannels import (
108
ChannelError, ChannelNotFoundError, ChannelClosedError,
119
ChannelEmptyError, ChannelNotEmptyError,
1210
)
1311

1412

1513
__all__ = [
16-
'Interpreter', 'get_current', 'get_main', 'create', 'list_all',
17-
'RunFailedError',
14+
'create', 'list_all',
1815
'SendChannel', 'RecvChannel',
19-
'create_channel', 'list_all_channels', 'is_shareable',
20-
'ChannelError', 'ChannelNotFoundError',
21-
'ChannelEmptyError',
22-
]
16+
'ChannelError', 'ChannelNotFoundError', 'ChannelEmptyError',
17+
]
2318

2419

25-
class RunFailedError(RuntimeError):
26-
27-
def __init__(self, excinfo):
28-
msg = excinfo.formatted
29-
if not msg:
30-
if excinfo.type and snapshot.msg:
31-
msg = f'{snapshot.type.__name__}: {snapshot.msg}'
32-
else:
33-
msg = snapshot.type.__name__ or snapshot.msg
34-
super().__init__(msg)
35-
self.snapshot = excinfo
36-
37-
38-
def create(*, isolated=True):
39-
"""Return a new (idle) Python interpreter."""
40-
id = _interpreters.create(isolated=isolated)
41-
return Interpreter(id, isolated=isolated)
42-
43-
44-
def list_all():
45-
"""Return all existing interpreters."""
46-
return [Interpreter(id) for id in _interpreters.list_all()]
47-
48-
49-
def get_current():
50-
"""Return the currently running interpreter."""
51-
id = _interpreters.get_current()
52-
return Interpreter(id)
53-
54-
55-
def get_main():
56-
"""Return the main interpreter."""
57-
id = _interpreters.get_main()
58-
return Interpreter(id)
59-
60-
61-
class Interpreter:
62-
"""A single Python interpreter."""
63-
64-
def __init__(self, id, *, isolated=None):
65-
if not isinstance(id, (int, _interpreters.InterpreterID)):
66-
raise TypeError(f'id must be an int, got {id!r}')
67-
self._id = id
68-
self._isolated = isolated
69-
70-
def __repr__(self):
71-
data = dict(id=int(self._id), isolated=self._isolated)
72-
kwargs = (f'{k}={v!r}' for k, v in data.items())
73-
return f'{type(self).__name__}({", ".join(kwargs)})'
74-
75-
def __hash__(self):
76-
return hash(self._id)
77-
78-
def __eq__(self, other):
79-
if not isinstance(other, Interpreter):
80-
return NotImplemented
81-
else:
82-
return other._id == self._id
83-
84-
@property
85-
def id(self):
86-
return self._id
87-
88-
@property
89-
def isolated(self):
90-
if self._isolated is None:
91-
# XXX The low-level function has not been added yet.
92-
# See bpo-....
93-
self._isolated = _interpreters.is_isolated(self._id)
94-
return self._isolated
95-
96-
def is_running(self):
97-
"""Return whether or not the identified interpreter is running."""
98-
return _interpreters.is_running(self._id)
99-
100-
def close(self):
101-
"""Finalize and destroy the interpreter.
102-
103-
Attempting to destroy the current interpreter results
104-
in a RuntimeError.
105-
"""
106-
return _interpreters.destroy(self._id)
107-
108-
# XXX Rename "run" to "exec"?
109-
def run(self, src_str, /, channels=None):
110-
"""Run the given source code in the interpreter.
111-
112-
This is essentially the same as calling the builtin "exec"
113-
with this interpreter, using the __dict__ of its __main__
114-
module as both globals and locals.
115-
116-
There is no return value.
117-
118-
If the code raises an unhandled exception then a RunFailedError
119-
is raised, which summarizes the unhandled exception. The actual
120-
exception is discarded because objects cannot be shared between
121-
interpreters.
122-
123-
This blocks the current Python thread until done. During
124-
that time, the previous interpreter is allowed to run
125-
in other threads.
126-
"""
127-
excinfo = _interpreters.exec(self._id, src_str, channels)
128-
if excinfo is not None:
129-
raise RunFailedError(excinfo)
130-
131-
132-
def create_channel():
20+
def create():
13321
"""Return (recv, send) for a new cross-interpreter channel.
13422
13523
The channel may be used to pass data safely between interpreters.
@@ -139,7 +27,7 @@ def create_channel():
13927
return recv, send
14028

14129

142-
def list_all_channels():
30+
def list_all():
14331
"""Return a list of (recv, send) for all open channels."""
14432
return [(RecvChannel(cid), SendChannel(cid))
14533
for cid in _channels.list_all()]

0 commit comments

Comments
 (0)