| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | """Subinterpreters High Level Module.""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06:00
										 |  |  | import time | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | import _xxsubinterpreters as _interpreters | 
					
						
							| 
									
										
										
										
											2023-02-03 18:14:43 -07:00
										 |  |  | import _xxinterpchannels as _channels | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | # aliases: | 
					
						
							| 
									
										
										
										
											2023-09-24 15:07:23 +01:00
										 |  |  | from _xxsubinterpreters import is_shareable | 
					
						
							| 
									
										
										
										
											2023-02-03 18:14:43 -07:00
										 |  |  | from _xxinterpchannels import ( | 
					
						
							| 
									
										
										
										
											2023-10-02 14:47:41 -06:00
										 |  |  |     ChannelError, ChannelNotFoundError, ChannelClosedError, | 
					
						
							|  |  |  |     ChannelEmptyError, ChannelNotEmptyError, | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | __all__ = [ | 
					
						
							|  |  |  |     'Interpreter', 'get_current', 'get_main', 'create', 'list_all', | 
					
						
							| 
									
										
										
										
											2023-11-22 17:55:00 -07:00
										 |  |  |     'RunFailedError', | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  |     'SendChannel', 'RecvChannel', | 
					
						
							|  |  |  |     'create_channel', 'list_all_channels', 'is_shareable', | 
					
						
							|  |  |  |     'ChannelError', 'ChannelNotFoundError', | 
					
						
							|  |  |  |     'ChannelEmptyError', | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-22 17:55:00 -07:00
										 |  |  | class RunFailedError(RuntimeError): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, excinfo): | 
					
						
							|  |  |  |         msg = excinfo.formatted | 
					
						
							|  |  |  |         if not msg: | 
					
						
							|  |  |  |             if excinfo.type and snapshot.msg: | 
					
						
							|  |  |  |                 msg = f'{snapshot.type.__name__}: {snapshot.msg}' | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 msg = snapshot.type.__name__ or snapshot.msg | 
					
						
							|  |  |  |         super().__init__(msg) | 
					
						
							|  |  |  |         self.snapshot = excinfo | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | def create(*, isolated=True): | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06: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 18:24:40 -06: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 18:24:40 -06: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 18:24:40 -06: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 18:24:40 -06:00
										 |  |  |     """A single Python interpreter.""" | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, id, *, isolated=None): | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06: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 18:24:40 -06: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 18:24:40 -06: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 18:24:40 -06: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 18:24:40 -06:00
										 |  |  |         """Finalize and destroy the interpreter.
 | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06: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 17:52:22 -06:00
										 |  |  |     # XXX Rename "run" to "exec"? | 
					
						
							| 
									
										
										
										
											2023-11-01 17:36:40 -06:00
										 |  |  |     def run(self, src_str, /, channels=None): | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06:00
										 |  |  |         """Run the given source code in the interpreter.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 17:52:22 -06: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-11-22 17:55:00 -07:00
										 |  |  |         excinfo = _interpreters.exec(self._id, src_str, channels) | 
					
						
							|  |  |  |         if excinfo is not None: | 
					
						
							|  |  |  |             raise RunFailedError(excinfo) | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def create_channel(): | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06:00
										 |  |  |     """Return (recv, send) for a new cross-interpreter channel.
 | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06:00
										 |  |  |     The channel may be used to pass data safely between interpreters. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2023-02-03 18:14:43 -07:00
										 |  |  |     cid = _channels.create() | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06: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 18:24:40 -06: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 18:14:43 -07:00
										 |  |  |             for cid in _channels.list_all()] | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06:00
										 |  |  | class _ChannelEnd: | 
					
						
							|  |  |  |     """The base class for RecvChannel and SendChannel.""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-02 14:47:41 -06: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 18:24:40 -06: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 08:51:21 -06:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def _info(self): | 
					
						
							|  |  |  |         return _channels.get_info(self._id) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def is_closed(self): | 
					
						
							|  |  |  |         return self._info.closed | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | _NOT_SET = object() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06:00
										 |  |  | class RecvChannel(_ChannelEnd): | 
					
						
							|  |  |  |     """The receiving end of a cross-interpreter channel.""" | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-02 14:47:41 -06:00
										 |  |  |     _end = 'recv' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-17 17:05:49 -06:00
										 |  |  |     def recv(self, timeout=None, *, | 
					
						
							|  |  |  |              _sentinel=object(), | 
					
						
							|  |  |  |              _delay=10 / 1000,  # 10 milliseconds | 
					
						
							|  |  |  |              ): | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06:00
										 |  |  |         """Return the next object from the channel.
 | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06: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 17:05:49 -06: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 18:14:43 -07:00
										 |  |  |         obj = _channels.recv(self._id, _sentinel) | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06:00
										 |  |  |         while obj is _sentinel: | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  |             time.sleep(_delay) | 
					
						
							| 
									
										
										
										
											2023-10-17 17:05:49 -06:00
										 |  |  |             if timeout is not None and time.time() >= end: | 
					
						
							|  |  |  |                 raise TimeoutError | 
					
						
							| 
									
										
										
										
											2023-02-03 18:14:43 -07: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 18:24:40 -06:00
										 |  |  |         """Return the next object from the channel.
 | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06: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 18:14:43 -07:00
										 |  |  |             return _channels.recv(self._id) | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2023-02-03 18:14:43 -07:00
										 |  |  |             return _channels.recv(self._id, default) | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-02 14:47:41 -06:00
										 |  |  |     def close(self): | 
					
						
							|  |  |  |         _channels.close(self._id, recv=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06:00
										 |  |  | class SendChannel(_ChannelEnd): | 
					
						
							|  |  |  |     """The sending end of a cross-interpreter channel.""" | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-02 14:47:41 -06:00
										 |  |  |     _end = 'send' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-19 08:51:21 -06:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def is_closed(self): | 
					
						
							|  |  |  |         info = self._info | 
					
						
							|  |  |  |         return info.closed or info.closing | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-17 17:05:49 -06:00
										 |  |  |     def send(self, obj, timeout=None): | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06: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 17:05:49 -06: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 18:24:40 -06:00
										 |  |  |         """Send the object to the channel's receiving end.
 | 
					
						
							| 
									
										
										
										
											2020-06-10 00:53:23 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-16 18:24:40 -06: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 18:24:40 -06: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 03:35:14 -06:00
										 |  |  |         return _channels.send(self._id, obj, blocking=False) | 
					
						
							| 
									
										
										
										
											2023-10-02 14:47:41 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-17 17:05:49 -06:00
										 |  |  |     def send_buffer(self, obj, timeout=None): | 
					
						
							| 
									
										
										
										
											2023-10-09 07:39:51 -06:00
										 |  |  |         """Send the object's buffer to the channel's receiving end.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         This blocks until the object is received. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2023-10-17 17:05:49 -06:00
										 |  |  |         _channels.send_buffer(self._id, obj, timeout=timeout, blocking=True) | 
					
						
							| 
									
										
										
										
											2023-10-09 07:39:51 -06: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 03:35:14 -06:00
										 |  |  |         return _channels.send_buffer(self._id, obj, blocking=False) | 
					
						
							| 
									
										
										
										
											2023-10-09 07:39:51 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-02 14:47:41 -06:00
										 |  |  |     def close(self): | 
					
						
							|  |  |  |         _channels.close(self._id, send=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-03 18:36:50 -06:00
										 |  |  | # XXX This is causing leaks (gh-110318): | 
					
						
							| 
									
										
										
										
											2023-10-19 08:52:02 -06:00
										 |  |  | _channels._register_end_types(SendChannel, RecvChannel) |