2020-06-10 00:53:23 -03:00
|
|
|
"""Subinterpreters High Level Module."""
|
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
import time
|
2020-06-10 00:53:23 -03:00
|
|
|
import _xxsubinterpreters as _interpreters
|
2023-02-03 21:14:43 -04:00
|
|
|
import _xxinterpchannels as _channels
|
2020-06-10 00:53:23 -03:00
|
|
|
|
|
|
|
# aliases:
|
2023-09-24 11:07:23 -03:00
|
|
|
from _xxsubinterpreters import is_shareable
|
2023-02-03 21:14:43 -04:00
|
|
|
from _xxinterpchannels import (
|
2023-10-02 17:47:41 -03:00
|
|
|
ChannelError, ChannelNotFoundError, ChannelClosedError,
|
|
|
|
ChannelEmptyError, ChannelNotEmptyError,
|
2020-06-10 00:53:23 -03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
'Interpreter', 'get_current', 'get_main', 'create', 'list_all',
|
|
|
|
'SendChannel', 'RecvChannel',
|
|
|
|
'create_channel', 'list_all_channels', 'is_shareable',
|
|
|
|
'ChannelError', 'ChannelNotFoundError',
|
|
|
|
'ChannelEmptyError',
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def create(*, isolated=True):
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Return a new (idle) Python interpreter."""
|
2020-06-10 00:53:23 -03:00
|
|
|
id = _interpreters.create(isolated=isolated)
|
|
|
|
return Interpreter(id, isolated=isolated)
|
|
|
|
|
|
|
|
|
|
|
|
def list_all():
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Return all existing interpreters."""
|
|
|
|
return [Interpreter(id) for id in _interpreters.list_all()]
|
2020-06-10 00:53:23 -03:00
|
|
|
|
|
|
|
|
|
|
|
def get_current():
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Return the currently running interpreter."""
|
2020-06-10 00:53:23 -03:00
|
|
|
id = _interpreters.get_current()
|
|
|
|
return Interpreter(id)
|
|
|
|
|
|
|
|
|
|
|
|
def get_main():
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Return the main interpreter."""
|
2020-06-10 00:53:23 -03:00
|
|
|
id = _interpreters.get_main()
|
|
|
|
return Interpreter(id)
|
|
|
|
|
|
|
|
|
|
|
|
class Interpreter:
|
2020-06-16 21:24:40 -03:00
|
|
|
"""A single Python interpreter."""
|
2020-06-10 00:53:23 -03:00
|
|
|
|
|
|
|
def __init__(self, id, *, isolated=None):
|
2020-06-16 21:24:40 -03:00
|
|
|
if not isinstance(id, (int, _interpreters.InterpreterID)):
|
|
|
|
raise TypeError(f'id must be an int, got {id!r}')
|
2020-06-10 00:53:23 -03:00
|
|
|
self._id = id
|
|
|
|
self._isolated = isolated
|
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
def __repr__(self):
|
|
|
|
data = dict(id=int(self._id), isolated=self._isolated)
|
|
|
|
kwargs = (f'{k}={v!r}' for k, v in data.items())
|
|
|
|
return f'{type(self).__name__}({", ".join(kwargs)})'
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return hash(self._id)
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
if not isinstance(other, Interpreter):
|
|
|
|
return NotImplemented
|
|
|
|
else:
|
|
|
|
return other._id == self._id
|
|
|
|
|
2020-06-10 00:53:23 -03:00
|
|
|
@property
|
|
|
|
def id(self):
|
|
|
|
return self._id
|
|
|
|
|
|
|
|
@property
|
|
|
|
def isolated(self):
|
|
|
|
if self._isolated is None:
|
2020-06-16 21:24:40 -03:00
|
|
|
# XXX The low-level function has not been added yet.
|
|
|
|
# See bpo-....
|
2020-06-10 00:53:23 -03:00
|
|
|
self._isolated = _interpreters.is_isolated(self._id)
|
|
|
|
return self._isolated
|
|
|
|
|
|
|
|
def is_running(self):
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Return whether or not the identified interpreter is running."""
|
2020-06-10 00:53:23 -03:00
|
|
|
return _interpreters.is_running(self._id)
|
|
|
|
|
|
|
|
def close(self):
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Finalize and destroy the interpreter.
|
2020-06-10 00:53:23 -03:00
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
Attempting to destroy the current interpreter results
|
|
|
|
in a RuntimeError.
|
2020-06-10 00:53:23 -03:00
|
|
|
"""
|
|
|
|
return _interpreters.destroy(self._id)
|
|
|
|
|
2023-10-06 20:52:22 -03:00
|
|
|
# XXX Rename "run" to "exec"?
|
2023-11-01 20:36:40 -03:00
|
|
|
def run(self, src_str, /, channels=None):
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Run the given source code in the interpreter.
|
|
|
|
|
2023-10-06 20:52:22 -03:00
|
|
|
This is essentially the same as calling the builtin "exec"
|
|
|
|
with this interpreter, using the __dict__ of its __main__
|
|
|
|
module as both globals and locals.
|
|
|
|
|
|
|
|
There is no return value.
|
|
|
|
|
|
|
|
If the code raises an unhandled exception then a RunFailedError
|
|
|
|
is raised, which summarizes the unhandled exception. The actual
|
|
|
|
exception is discarded because objects cannot be shared between
|
|
|
|
interpreters.
|
|
|
|
|
|
|
|
This blocks the current Python thread until done. During
|
|
|
|
that time, the previous interpreter is allowed to run
|
|
|
|
in other threads.
|
2020-06-10 00:53:23 -03:00
|
|
|
"""
|
2023-10-06 20:52:22 -03:00
|
|
|
_interpreters.exec(self._id, src_str, channels)
|
2020-06-10 00:53:23 -03:00
|
|
|
|
|
|
|
|
|
|
|
def create_channel():
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Return (recv, send) for a new cross-interpreter channel.
|
2020-06-10 00:53:23 -03:00
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
The channel may be used to pass data safely between interpreters.
|
|
|
|
"""
|
2023-02-03 21:14:43 -04:00
|
|
|
cid = _channels.create()
|
2020-06-16 21:24:40 -03:00
|
|
|
recv, send = RecvChannel(cid), SendChannel(cid)
|
|
|
|
return recv, send
|
2020-06-10 00:53:23 -03:00
|
|
|
|
|
|
|
|
|
|
|
def list_all_channels():
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Return a list of (recv, send) for all open channels."""
|
2020-06-10 00:53:23 -03:00
|
|
|
return [(RecvChannel(cid), SendChannel(cid))
|
2023-02-03 21:14:43 -04:00
|
|
|
for cid in _channels.list_all()]
|
2020-06-10 00:53:23 -03:00
|
|
|
|
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
class _ChannelEnd:
|
|
|
|
"""The base class for RecvChannel and SendChannel."""
|
|
|
|
|
2023-10-02 17:47:41 -03:00
|
|
|
_end = None
|
|
|
|
|
|
|
|
def __init__(self, cid):
|
|
|
|
if self._end == 'send':
|
|
|
|
cid = _channels._channel_id(cid, send=True, force=True)
|
|
|
|
elif self._end == 'recv':
|
|
|
|
cid = _channels._channel_id(cid, recv=True, force=True)
|
|
|
|
else:
|
|
|
|
raise NotImplementedError(self._end)
|
|
|
|
self._id = cid
|
2020-06-16 21:24:40 -03:00
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return f'{type(self).__name__}(id={int(self._id)})'
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return hash(self._id)
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
if isinstance(self, RecvChannel):
|
|
|
|
if not isinstance(other, RecvChannel):
|
|
|
|
return NotImplemented
|
|
|
|
elif not isinstance(other, SendChannel):
|
|
|
|
return NotImplemented
|
|
|
|
return other._id == self._id
|
|
|
|
|
|
|
|
@property
|
|
|
|
def id(self):
|
|
|
|
return self._id
|
|
|
|
|
2023-10-19 11:51:21 -03:00
|
|
|
@property
|
|
|
|
def _info(self):
|
|
|
|
return _channels.get_info(self._id)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_closed(self):
|
|
|
|
return self._info.closed
|
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
|
2020-06-10 00:53:23 -03:00
|
|
|
_NOT_SET = object()
|
|
|
|
|
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
class RecvChannel(_ChannelEnd):
|
|
|
|
"""The receiving end of a cross-interpreter channel."""
|
2020-06-10 00:53:23 -03:00
|
|
|
|
2023-10-02 17:47:41 -03:00
|
|
|
_end = 'recv'
|
|
|
|
|
2023-10-17 20:05:49 -03:00
|
|
|
def recv(self, timeout=None, *,
|
|
|
|
_sentinel=object(),
|
|
|
|
_delay=10 / 1000, # 10 milliseconds
|
|
|
|
):
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Return the next object from the channel.
|
2020-06-10 00:53:23 -03:00
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
This blocks until an object has been sent, if none have been
|
|
|
|
sent already.
|
2020-06-10 00:53:23 -03:00
|
|
|
"""
|
2023-10-17 20:05:49 -03:00
|
|
|
if timeout is not None:
|
|
|
|
timeout = int(timeout)
|
|
|
|
if timeout < 0:
|
|
|
|
raise ValueError(f'timeout value must be non-negative')
|
|
|
|
end = time.time() + timeout
|
2023-02-03 21:14:43 -04:00
|
|
|
obj = _channels.recv(self._id, _sentinel)
|
2020-06-16 21:24:40 -03:00
|
|
|
while obj is _sentinel:
|
2020-06-10 00:53:23 -03:00
|
|
|
time.sleep(_delay)
|
2023-10-17 20:05:49 -03:00
|
|
|
if timeout is not None and time.time() >= end:
|
|
|
|
raise TimeoutError
|
2023-02-03 21:14:43 -04:00
|
|
|
obj = _channels.recv(self._id, _sentinel)
|
2020-06-10 00:53:23 -03:00
|
|
|
return obj
|
|
|
|
|
|
|
|
def recv_nowait(self, default=_NOT_SET):
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Return the next object from the channel.
|
2020-06-10 00:53:23 -03:00
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
If none have been sent then return the default if one
|
|
|
|
is provided or fail with ChannelEmptyError. Otherwise this
|
|
|
|
is the same as recv().
|
2020-06-10 00:53:23 -03:00
|
|
|
"""
|
|
|
|
if default is _NOT_SET:
|
2023-02-03 21:14:43 -04:00
|
|
|
return _channels.recv(self._id)
|
2020-06-10 00:53:23 -03:00
|
|
|
else:
|
2023-02-03 21:14:43 -04:00
|
|
|
return _channels.recv(self._id, default)
|
2020-06-10 00:53:23 -03:00
|
|
|
|
2023-10-02 17:47:41 -03:00
|
|
|
def close(self):
|
|
|
|
_channels.close(self._id, recv=True)
|
|
|
|
|
2020-06-10 00:53:23 -03:00
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
class SendChannel(_ChannelEnd):
|
|
|
|
"""The sending end of a cross-interpreter channel."""
|
2020-06-10 00:53:23 -03:00
|
|
|
|
2023-10-02 17:47:41 -03:00
|
|
|
_end = 'send'
|
|
|
|
|
2023-10-19 11:51:21 -03:00
|
|
|
@property
|
|
|
|
def is_closed(self):
|
|
|
|
info = self._info
|
|
|
|
return info.closed or info.closing
|
|
|
|
|
2023-10-17 20:05:49 -03:00
|
|
|
def send(self, obj, timeout=None):
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Send the object (i.e. its data) to the channel's receiving end.
|
|
|
|
|
|
|
|
This blocks until the object is received.
|
2020-06-10 00:53:23 -03:00
|
|
|
"""
|
2023-10-17 20:05:49 -03:00
|
|
|
_channels.send(self._id, obj, timeout=timeout, blocking=True)
|
2020-06-10 00:53:23 -03:00
|
|
|
|
|
|
|
def send_nowait(self, obj):
|
2020-06-16 21:24:40 -03:00
|
|
|
"""Send the object to the channel's receiving end.
|
2020-06-10 00:53:23 -03:00
|
|
|
|
2020-06-16 21:24:40 -03:00
|
|
|
If the object is immediately received then return True
|
|
|
|
(else False). Otherwise this is the same as send().
|
2020-06-10 00:53:23 -03:00
|
|
|
"""
|
2020-06-16 21:24:40 -03:00
|
|
|
# XXX Note that at the moment channel_send() only ever returns
|
|
|
|
# None. This should be fixed when channel_send_wait() is added.
|
|
|
|
# See bpo-32604 and gh-19829.
|
2023-10-10 06:35:14 -03:00
|
|
|
return _channels.send(self._id, obj, blocking=False)
|
2023-10-02 17:47:41 -03:00
|
|
|
|
2023-10-17 20:05:49 -03:00
|
|
|
def send_buffer(self, obj, timeout=None):
|
2023-10-09 10:39:51 -03:00
|
|
|
"""Send the object's buffer to the channel's receiving end.
|
|
|
|
|
|
|
|
This blocks until the object is received.
|
|
|
|
"""
|
2023-10-17 20:05:49 -03:00
|
|
|
_channels.send_buffer(self._id, obj, timeout=timeout, blocking=True)
|
2023-10-09 10:39:51 -03:00
|
|
|
|
|
|
|
def send_buffer_nowait(self, obj):
|
|
|
|
"""Send the object's buffer to the channel's receiving end.
|
|
|
|
|
|
|
|
If the object is immediately received then return True
|
|
|
|
(else False). Otherwise this is the same as send().
|
|
|
|
"""
|
2023-10-10 06:35:14 -03:00
|
|
|
return _channels.send_buffer(self._id, obj, blocking=False)
|
2023-10-09 10:39:51 -03:00
|
|
|
|
2023-10-02 17:47:41 -03:00
|
|
|
def close(self):
|
|
|
|
_channels.close(self._id, send=True)
|
|
|
|
|
|
|
|
|
2023-10-03 21:36:50 -03:00
|
|
|
# XXX This is causing leaks (gh-110318):
|
2023-10-19 11:52:02 -03:00
|
|
|
_channels._register_end_types(SendChannel, RecvChannel)
|