mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	 8b209fd4f8
			
		
	
	
		8b209fd4f8
		
			
		
	
	
	
	
		
			
			See 6b98b274b6 for an explanation of the problem and solution.  Here I've applied the solution to channels.
		
	
			
		
			
				
	
	
		
			257 lines
		
	
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Cross-interpreter Channels High Level Module."""
 | |
| 
 | |
| import time
 | |
| import _interpchannels as _channels
 | |
| from . import _crossinterp
 | |
| 
 | |
| # aliases:
 | |
| from _interpchannels import (
 | |
|     ChannelError, ChannelNotFoundError, ChannelClosedError,
 | |
|     ChannelEmptyError, ChannelNotEmptyError,
 | |
| )
 | |
| from ._crossinterp import (
 | |
|     UNBOUND_ERROR, UNBOUND_REMOVE,
 | |
| )
 | |
| 
 | |
| 
 | |
| __all__ = [
 | |
|     'UNBOUND', 'UNBOUND_ERROR', 'UNBOUND_REMOVE',
 | |
|     'create', 'list_all',
 | |
|     'SendChannel', 'RecvChannel',
 | |
|     'ChannelError', 'ChannelNotFoundError', 'ChannelEmptyError',
 | |
|     'ItemInterpreterDestroyed',
 | |
| ]
 | |
| 
 | |
| 
 | |
| class ItemInterpreterDestroyed(ChannelError,
 | |
|                                _crossinterp.ItemInterpreterDestroyed):
 | |
|     """Raised from get() and get_nowait()."""
 | |
| 
 | |
| 
 | |
| UNBOUND = _crossinterp.UnboundItem.singleton('queue', __name__)
 | |
| 
 | |
| 
 | |
| def _serialize_unbound(unbound):
 | |
|     if unbound is UNBOUND:
 | |
|         unbound = _crossinterp.UNBOUND
 | |
|     return _crossinterp.serialize_unbound(unbound)
 | |
| 
 | |
| 
 | |
| def _resolve_unbound(flag):
 | |
|     resolved = _crossinterp.resolve_unbound(flag, ItemInterpreterDestroyed)
 | |
|     if resolved is _crossinterp.UNBOUND:
 | |
|         resolved = UNBOUND
 | |
|     return resolved
 | |
| 
 | |
| 
 | |
| def create(*, unbounditems=UNBOUND):
 | |
|     """Return (recv, send) for a new cross-interpreter channel.
 | |
| 
 | |
|     The channel may be used to pass data safely between interpreters.
 | |
| 
 | |
|     "unbounditems" sets the default for the send end of the channel.
 | |
|     See SendChannel.send() for supported values.  The default value
 | |
|     is UNBOUND, which replaces the unbound item when received.
 | |
|     """
 | |
|     unbound = _serialize_unbound(unbounditems)
 | |
|     unboundop, = unbound
 | |
|     cid = _channels.create(unboundop)
 | |
|     recv, send = RecvChannel(cid), SendChannel(cid, _unbound=unbound)
 | |
|     return recv, send
 | |
| 
 | |
| 
 | |
| def list_all():
 | |
|     """Return a list of (recv, send) for all open channels."""
 | |
|     return [(RecvChannel(cid), SendChannel(cid, _unbound=unbound))
 | |
|             for cid, unbound in _channels.list_all()]
 | |
| 
 | |
| 
 | |
| class _ChannelEnd:
 | |
|     """The base class for RecvChannel and SendChannel."""
 | |
| 
 | |
|     _end = None
 | |
| 
 | |
|     def __new__(cls, cid):
 | |
|         self = super().__new__(cls)
 | |
|         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
 | |
|         return self
 | |
| 
 | |
|     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
 | |
| 
 | |
|     # for pickling:
 | |
|     def __getnewargs__(self):
 | |
|         return (int(self._id),)
 | |
| 
 | |
|     # for pickling:
 | |
|     def __getstate__(self):
 | |
|         return None
 | |
| 
 | |
|     @property
 | |
|     def id(self):
 | |
|         return self._id
 | |
| 
 | |
|     @property
 | |
|     def _info(self):
 | |
|         return _channels.get_info(self._id)
 | |
| 
 | |
|     @property
 | |
|     def is_closed(self):
 | |
|         return self._info.closed
 | |
| 
 | |
| 
 | |
| _NOT_SET = object()
 | |
| 
 | |
| 
 | |
| class RecvChannel(_ChannelEnd):
 | |
|     """The receiving end of a cross-interpreter channel."""
 | |
| 
 | |
|     _end = 'recv'
 | |
| 
 | |
|     def recv(self, timeout=None, *,
 | |
|              _sentinel=object(),
 | |
|              _delay=10 / 1000,  # 10 milliseconds
 | |
|              ):
 | |
|         """Return the next object from the channel.
 | |
| 
 | |
|         This blocks until an object has been sent, if none have been
 | |
|         sent already.
 | |
|         """
 | |
|         if timeout is not None:
 | |
|             timeout = int(timeout)
 | |
|             if timeout < 0:
 | |
|                 raise ValueError(f'timeout value must be non-negative')
 | |
|             end = time.time() + timeout
 | |
|         obj, unboundop = _channels.recv(self._id, _sentinel)
 | |
|         while obj is _sentinel:
 | |
|             time.sleep(_delay)
 | |
|             if timeout is not None and time.time() >= end:
 | |
|                 raise TimeoutError
 | |
|             obj, unboundop = _channels.recv(self._id, _sentinel)
 | |
|         if unboundop is not None:
 | |
|             assert obj is None, repr(obj)
 | |
|             return _resolve_unbound(unboundop)
 | |
|         return obj
 | |
| 
 | |
|     def recv_nowait(self, default=_NOT_SET):
 | |
|         """Return the next object from the channel.
 | |
| 
 | |
|         If none have been sent then return the default if one
 | |
|         is provided or fail with ChannelEmptyError.  Otherwise this
 | |
|         is the same as recv().
 | |
|         """
 | |
|         if default is _NOT_SET:
 | |
|             obj, unboundop = _channels.recv(self._id)
 | |
|         else:
 | |
|             obj, unboundop = _channels.recv(self._id, default)
 | |
|         if unboundop is not None:
 | |
|             assert obj is None, repr(obj)
 | |
|             return _resolve_unbound(unboundop)
 | |
|         return obj
 | |
| 
 | |
|     def close(self):
 | |
|         _channels.close(self._id, recv=True)
 | |
| 
 | |
| 
 | |
| class SendChannel(_ChannelEnd):
 | |
|     """The sending end of a cross-interpreter channel."""
 | |
| 
 | |
|     _end = 'send'
 | |
| 
 | |
|     def __new__(cls, cid, *, _unbound=None):
 | |
|         if _unbound is None:
 | |
|             try:
 | |
|                 op = _channels.get_channel_defaults(cid)
 | |
|                 _unbound = (op,)
 | |
|             except ChannelNotFoundError:
 | |
|                 _unbound = _serialize_unbound(UNBOUND)
 | |
|         self = super().__new__(cls, cid)
 | |
|         self._unbound = _unbound
 | |
|         return self
 | |
| 
 | |
|     @property
 | |
|     def is_closed(self):
 | |
|         info = self._info
 | |
|         return info.closed or info.closing
 | |
| 
 | |
|     def send(self, obj, timeout=None, *,
 | |
|              unbound=None,
 | |
|              ):
 | |
|         """Send the object (i.e. its data) to the channel's receiving end.
 | |
| 
 | |
|         This blocks until the object is received.
 | |
|         """
 | |
|         if unbound is None:
 | |
|             unboundop, = self._unbound
 | |
|         else:
 | |
|             unboundop, = _serialize_unbound(unbound)
 | |
|         _channels.send(self._id, obj, unboundop, timeout=timeout, blocking=True)
 | |
| 
 | |
|     def send_nowait(self, obj, *,
 | |
|                     unbound=None,
 | |
|                     ):
 | |
|         """Send the object to the channel's receiving end.
 | |
| 
 | |
|         If the object is immediately received then return True
 | |
|         (else False).  Otherwise this is the same as send().
 | |
|         """
 | |
|         if unbound is None:
 | |
|             unboundop, = self._unbound
 | |
|         else:
 | |
|             unboundop, = _serialize_unbound(unbound)
 | |
|         # 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.
 | |
|         return _channels.send(self._id, obj, unboundop, blocking=False)
 | |
| 
 | |
|     def send_buffer(self, obj, timeout=None, *,
 | |
|                     unbound=None,
 | |
|                     ):
 | |
|         """Send the object's buffer to the channel's receiving end.
 | |
| 
 | |
|         This blocks until the object is received.
 | |
|         """
 | |
|         if unbound is None:
 | |
|             unboundop, = self._unbound
 | |
|         else:
 | |
|             unboundop, = _serialize_unbound(unbound)
 | |
|         _channels.send_buffer(self._id, obj, unboundop,
 | |
|                               timeout=timeout, blocking=True)
 | |
| 
 | |
|     def send_buffer_nowait(self, obj, *,
 | |
|                            unbound=None,
 | |
|                            ):
 | |
|         """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().
 | |
|         """
 | |
|         if unbound is None:
 | |
|             unboundop, = self._unbound
 | |
|         else:
 | |
|             unboundop, = _serialize_unbound(unbound)
 | |
|         return _channels.send_buffer(self._id, obj, unboundop, blocking=False)
 | |
| 
 | |
|     def close(self):
 | |
|         _channels.close(self._id, send=True)
 | |
| 
 | |
| 
 | |
| # XXX This is causing leaks (gh-110318):
 | |
| _channels._register_end_types(SendChannel, RecvChannel)
 |