class VClientSocketAgent(VClientSocket): """A :class:`VClientSocket` with a byte producer/consumer interface. :param max_read: max bytes fetched per socket read :type max_read: int :param max_write: max bytes written per socket send :type max_write: int :param wbuf_len: buffer size of data held for writing (or None) :type wbuf_len: int *max_read* is also the maximum size of the buffer for data read from the socket (so maximum bytes read in one read operation is *max_read* minus the amount of data currently held in the receive buffer). If *wbuf_len* is None then *max_write* is used as the buffer size. """ def __init__(self, reactor, sock=None, hc_pol=None, close_cback=None, connected=False, max_read=0x4000, max_write=0x4000, wbuf_len=None): self._max_read = max_read self._max_write = max_write self._wbuf = VByteBuffer() if wbuf_len is None: wbuf_len = max_write self._wbuf_len = wbuf_len self._ci = None self._ci_eod = False self._ci_eod_clean = None self._ci_producer = None self._ci_consumed = 0 self._ci_lim_sent = 0 self._ci_aborted = False self._pi = None self._pi_closed = False self._pi_consumer = None self._pi_produced = 0 self._pi_prod_lim = 0 self._pi_buffer = VByteBuffer() self._pi_aborted = False # Parent __init__ must be called after local attributes are # initialized due to overloaded methods called during construction super_init = super(VClientSocketAgent, self).__init__ super_init(reactor=reactor, sock=sock, hc_pol=hc_pol, close_cback=close_cback, connected=connected) @property def byte_consume(self): """Holds a :class:`IVByteConsumer` interface to the socket.""" if not self._ci: ci = _VSocketConsumer(self) self._ci = weakref.ref(ci) return ci else: ci = self._ci() if ci: return ci else: ci = _VSocketConsumer(self) self._ci = weakref.ref(ci) return ci @property def byte_produce(self): """Holds a :class:`IVByteProducer` interface to the socket.""" if not self._pi: pi = _VSocketProducer(self) self._pi = weakref.ref(pi) return pi else: pi = self._pi() if pi: return pi else: pi = _VSocketProducer(self) self._pi = weakref.ref(pi) return pi @property def byte_io(self): """Byte interface (\ :class:`versile.reactor.io.VByteIOPair`\ ).""" return VByteIOPair(self.byte_consume, self.byte_produce) def _can_connect(self, peer): """Called internally to validate whether a connection can be made. :param peer: peer to connect to :type peer: :class:`versile.common.peer.VPeer` :returns: True if connection is allowed :rtype: bool If the socket is connected to a byte consumer, sends a control request for 'can_connect(peer)'. If that control message returns a boolean, this is used as the _can_connect result. Otherwise, True is returned. """ if self._pi_consumer: try: return self._pi_consumer.control.can_connect(peer) except VIOMissingControl: return True else: return True def _sock_was_connected(self): super(VClientSocketAgent, self)._sock_was_connected() if self._p_consumer: # Notify producer-connected chain about 'connected' status control = self._p_consumer.control def notify(): try: control.connected(self._sock_peer) except VIOMissingControl: pass self.reactor.schedule(0.0, notify) @peer def _c_consume(self, buf, clim): if self._ci_eod: raise VIOClosed('Consumer already reached end-of-data') elif not self._ci_producer: raise VIOError('No connected producer') elif self._ci_consumed >= self._ci_lim_sent: raise VIOError('Consume limit exceeded') elif not buf: raise VIOError('No data to consume') max_cons = self._wbuf_len - len(self._wbuf) max_cons = min(max_cons, self._ci_lim_sent - self._ci_consumed) if clim is not None and clim > 0: max_cons = min(max_cons, clim) was_empty = not self._wbuf indata = buf.pop(max_cons) self._wbuf.append(indata) self._ci_consumed += len(indata) if was_empty: self.start_writing(internal=True) return self._ci_lim_sent def _c_end_consume(self, clean): if self._ci_eod: return self._ci_eod = True self._ci_eod_clean = clean if not self._wbuf: self.close_output(VFIOCompleted()) if self._ci_producer: self._ci_producer.abort() self._c_detach() def _c_abort(self, force=False): if not self._ci_aborted or force: self._ci_aborted = True self._ci_eod = True self._ci_consumed = self._ci_lim_sent = 0 self._wbuf.clear() if not self._sock_out_closed: self.close_output(VFIOCompleted()) if self._ci_producer: self._ci_producer.abort() self._c_detach() def _c_attach(self, producer, rthread=False): # Ensure 'attach' is performed in reactor thread if not rthread: self.reactor.execute(self._c_attach, producer, rthread=True) return if self._ci_producer is producer: return elif self._ci_producer: raise VIOError('Producer already attached') self._ci_producer = producer if self._sock_out_closed: self.reactor.schedule(0.0, self._c_abort, True) else: self._ci_consumed = self._ci_lim_sent = 0 producer.attach(self.byte_consume) self._ci_lim_sent = self._wbuf_len producer.can_produce(self._ci_lim_sent) # Notify attached chain try: producer.control.notify_consumer_attached(self.byte_consume) except VIOMissingControl: pass def _c_detach(self, rthread=False): # Ensure 'detach' is performed in reactor thread if not rthread: self.reactor.execute(self._c_detach, rthread=True) return if self._ci_producer: prod, self._ci_producer = self._ci_producer, None prod.detach() self._ci_consumed = self._ci_lim_sent = 0 def active_do_write(self): if self._wbuf: data = self._wbuf.peek(self._max_write) try: num_written = self.write_some(data) except VIOException: self._c_abort() else: if num_written > 0: self._wbuf.remove(num_written) if self._ci_producer: self._ci_lim_sent = (self._ci_consumed + self._wbuf_len - len(self._wbuf)) self._ci_producer.can_produce(self._ci_lim_sent) if not self._wbuf: self.stop_writing() if self._ci_eod: self._c_abort() else: self.stop_writing() def _output_was_closed(self, reason): # No more output will be written, abort consumer self._c_abort() @property def _c_control(self): return self._c_get_control() # Implements _c_control in order to be able to override _c_control # behavior by overloading as a regular method def _c_get_control(self): return VIOControl() @property def _c_producer(self): return self._ci_producer @property def _c_flows(self): return tuple() @property def _c_twoway(self): return True @property def _c_reverse(self): return self.byte_produce() @peer def _p_can_produce(self, limit): if not self._pi_consumer: raise VIOError('No connected consumer') if limit is None or limit < 0: if (not self._pi_prod_lim is None and not self._pi_prod_lim < 0): if self._pi_produced >= self._pi_prod_lim: self.start_reading(internal=True) self._pi_prod_lim = limit else: if (self._pi_prod_lim is not None and 0 <= self._pi_prod_lim < limit): if self._pi_produced >= self._pi_prod_lim: self.start_reading(internal=True) self._pi_prod_lim = limit def _p_abort(self, force=False): if not self._pi_aborted or force: self._pi_aborted = True self._pi_produced = self._pi_prod_lim = 0 if not self._sock_in_closed: self.close_input(VFIOCompleted()) if self._pi_consumer: self._pi_consumer.abort() self._p_detach() def _p_attach(self, consumer, rthread=False): # Ensure 'attach' is performed in reactor thread if not rthread: self.reactor.execute(self._p_attach, consumer, rthread=True) return if self._pi_consumer is consumer: return elif self._pi_consumer: raise VIOError('Consumer already attached') self._pi_produced = self._pi_prod_lim = 0 self._pi_consumer = consumer consumer.attach(self.byte_produce) # Notify attached chain try: consumer.control.notify_producer_attached(self.byte_produce) except VIOMissingControl: pass # If closed, pass notification if self._sock_in_closed: self.reactor.schedule(0.0, self._p_abort, True) def _p_detach(self, rthread=False): # Ensure 'detach' is performed in reactor thread if not rthread: self.reactor.execute(self._p_detach, rthread=True) return if self._pi_consumer: cons, self._pi_consumer = self._pi_consumer, None cons.detach() self._pi_produced = self._pi_prod_lim = 0 def active_do_read(self): if not self._pi_consumer: self.stop_reading() if self._pi_prod_lim is not None and self._pi_prod_lim >= 0: max_read = self._pi_prod_lim - self._pi_produced else: max_read = self._max_read max_read = min(max_read, self._max_read) if max_read <= 0: self.stop_reading() return try: data = self.read_some(max_read) except Exception as e: self._p_abort() else: self._pi_buffer.append(data) if self._pi_buffer: self.pi_prod_lim = self._pi_consumer.consume(self._pi_buffer) def _input_was_closed(self, reason): if self._pi_consumer: # Notify consumer about end-of-data clean = isinstance(reason, VFIOCompleted) self._pi_consumer.end_consume(clean) else: self._p_abort() @property def _p_control(self): return self._p_get_control() # Implements _p_control in order to be able to override _p_control # behavior by overloading as a regular method def _p_get_control(self): class _Control(VIOControl): def __init__(self, sock): self.__sock = sock def req_producer_state(self, consumer): # Send notification of socket connect status def notify(): if self.__sock._sock_peer: try: consumer.control.connected(self.__sock._sock_peer) except VIOMissingControl: pass self.__sock.reactor.schedule(0.0, notify) return _Control(self) @property def _p_consumer(self): return self._pi_consumer @property def _p_flows(self): return tuple() @property def _p_twoway(self): return True @property def _p_reverse(self): return self.byte_consume()
class VPipeAgent(object): """Byte producer/consumer interface to a pipe reader/writer pair. :param read_fd: read pipe file descriptor :type read_fd: int :param write_fd: write pipe file descriptor :type write_fd: int :param max_read: max bytes fetched per pipe read :type max_read: int :param max_write: max bytes written per pipe write :type max_write: int :param wbuf_len: buffer size of data held for writing (or None) :type wbuf_len: int The agent creates a :class:`VPipeReader` and :class:`VPipeWriter` for the provided pipe read/write descriptors which it uses for reactor driven pipe I/O communication. *max_read* is also the maximum size of the buffer for data read from the socket (so maximum bytes read in one read operation is *max_read* minus the amount of data currently held in the receive buffer). If *wbuf_len* is None then *max_write* is used as the buffer size. """ def __init__(self, reactor, read_fd, write_fd, max_read=0x4000, max_write=0x4000, wbuf_len=None): self.__reactor = reactor self._reader = _VAgentReader(self, reactor, read_fd) self._writer = _VAgentWriter(self, reactor, write_fd) self._reader.set_pipe_peer(self._writer) self._writer.set_pipe_peer(self._reader) self._max_read = max_read self._max_write = max_write self._wbuf = VByteBuffer() if wbuf_len is None: wbuf_len = max_write self._wbuf_len = wbuf_len self._ci = None self._ci_eod = False self._ci_eod_clean = None self._ci_producer = None self._ci_consumed = 0 self._ci_lim_sent = 0 self._ci_aborted = False self._pi = None self._pi_closed = False self._pi_consumer = None self._pi_produced = 0 self._pi_prod_lim = 0 self._pi_buffer = VByteBuffer() self._pi_aborted = False @property def byte_consume(self): """Holds a :class:`IVByteConsumer` interface to the pipe reader.""" if not self._ci: ci = _VPipeConsumer(self) self._ci = weakref.ref(ci) return ci else: ci = self._ci() if ci: return ci else: ci = _VPipeConsumer(self) self._ci = weakref.ref(ci) return ci @property def byte_produce(self): """Holds a :class:`IVByteProducer` interface to the pipe writer.""" if not self._pi: pi = _VPipeProducer(self) self._pi = weakref.ref(pi) return pi else: pi = self._pi() if pi: return pi else: pi = _VPipeProducer(self) self._pi = weakref.ref(pi) return pi @property def byte_io(self): """Byte interface (\ :class:`versile.reactor.io.VByteIOPair`\ ).""" return VByteIOPair(self.byte_consume, self.byte_produce) @property def reactor(self): """Holds the reactor of the associated reader.""" return self.__reactor @peer def _c_consume(self, buf, clim): if self._ci_eod: raise VIOClosed('Consumer already reached end-of-data') elif not self._ci_producer: raise VIOError('No connected producer') elif self._ci_consumed >= self._ci_lim_sent: raise VIOError('Consume limit exceeded') elif not buf: raise VIOError('No data to consume') max_cons = self._wbuf_len - len(self._wbuf) max_cons = min(max_cons, self._ci_lim_sent - self._ci_consumed) if clim is not None and clim > 0: max_cons = min(max_cons, clim) was_empty = not self._wbuf indata = buf.pop(max_cons) self._wbuf.append(indata) self._ci_consumed += len(indata) if was_empty: self._writer.start_writing(internal=True) return self._ci_lim_sent def _c_end_consume(self, clean): if self._ci_eod: return self._ci_eod = True self._ci_eod_clean = clean if not self._wbuf: self._writer.close_output(VFIOCompleted()) if self._ci_producer: self._ci_producer.abort() self._c_detach() def _c_abort(self): if not self._ci_aborted: self._ci_aborted = True self._ci_eod = True self._ci_consumed = self._ci_lim_sent = 0 self._wbuf.clear() self._writer.close_output(VFIOCompleted()) if self._ci_producer: self._ci_producer.abort() self._c_detach() def _c_attach(self, producer, rthread=False): # Ensure 'attach' is performed in reactor thread if not rthread: self.reactor.execute(self._c_attach, producer, rthread=True) return if self._ci_producer is producer: return elif self._ci_producer: raise VIOError('Producer already attached') self._ci_producer = producer self._ci_consumed = self._ci_lim_sent = 0 producer.attach(self.byte_consume) self._ci_lim_sent = self._wbuf_len producer.can_produce(self._ci_lim_sent) # Notify attached chain try: producer.control.notify_consumer_attached(self.byte_consume) except VIOMissingControl: pass def _c_detach(self, rthread=False): # Ensure 'detach' is performed in reactor thread if not rthread: self.reactor.execute(self._c_detach, rthread=True) return if self._ci_producer: prod, self._ci_producer = self._ci_producer, None prod.detach() self._ci_consumed = self._ci_lim_sent = 0 def _do_write(self): if self._wbuf: data = self._wbuf.peek(self._max_write) try: num_written = self._writer.write_some(data) except VIOException: self._c_abort() else: if num_written > 0: self._wbuf.remove(num_written) if self._ci_producer: self._ci_lim_sent = (self._ci_consumed + self._wbuf_len - len(self._wbuf)) self._ci_producer.can_produce(self._ci_lim_sent) if not self._wbuf: self._writer.stop_writing() if self._ci_eod: self._c_abort() else: self._writer.stop_writing() def _output_was_closed(self, reason): # No more output will be written, abort consumer self._c_abort() @property def _c_control(self): return VIOControl() @property def _c_producer(self): return self._ci_producer @property def _c_flows(self): return tuple() @property def _c_twoway(self): return True @property def _c_reverse(self): return self.byte_produce() @peer def _p_can_produce(self, limit): if not self._pi_consumer: raise VIOError('No connected consumer') if limit is None or limit < 0: if (not self._pi_prod_lim is None and not self._pi_prod_lim < 0): if self._pi_produced >= self._pi_prod_lim: self._reader.start_reading(internal=True) self._pi_prod_lim = limit else: if (self._pi_prod_lim is not None and 0 <= self._pi_prod_lim < limit): if self._pi_produced >= self._pi_prod_lim: self._reader.start_reading(internal=True) self._pi_prod_lim = limit def _p_abort(self): if not self._pi_aborted: self._pi_aborted = True self._pi_produced = self._pi_prod_lim = 0 self._reader.close_input(VFIOCompleted()) if self._pi_consumer: self._pi_consumer.abort() self._p_detach() def _p_attach(self, consumer, rthread=False): # Ensure 'attach' is performed in reactor thread if not rthread: self.reactor.execute(self._p_attach, consumer, rthread=True) return if self._pi_consumer is consumer: return elif self._pi_consumer: raise VIOError('Consumer already attached') self._pi_produced = self._pi_prod_lim = 0 self._pi_consumer = consumer consumer.attach(self.byte_produce) # Notify attached chain try: consumer.control.notify_producer_attached(self.byte_produce) except VIOMissingControl: pass def _p_detach(self, rthread=False): # Ensure 'detach' is performed in reactor thread if not rthread: self.reactor.execute(self._p_detach, rthread=True) return if self._pi_consumer: cons, self._pi_consumer = self._pi_consumer, None cons.detach() self._pi_produced = self._pi_prod_lim = 0 def _do_read(self): if not self._pi_consumer: self._reader.stop_reading() if self._pi_prod_lim is not None and self._pi_prod_lim >= 0: max_read = self._pi_prod_lim - self._pi_produced else: max_read = self._max_read max_read = min(max_read, self._max_read) if max_read <= 0: self._reader.stop_reading() return try: data = self._reader.read_some(max_read) except Exception as e: self._p_abort() else: self._pi_buffer.append(data) if self._pi_buffer: self.pi_prod_lim = self._pi_consumer.consume(self._pi_buffer) def _input_was_closed(self, reason): if self._pi_consumer: # Notify consumer about end-of-data clean = isinstance(reason, VFIOCompleted) self._pi_consumer.end_consume(clean) else: self._p_abort() @property def _p_control(self): class _Control(VIOControl): def __init__(self, obj): self._obj = obj def req_producer_state(self, consumer): # Send 'connected' notification if pipe is not closed def notify(): if (self._obj._reader._fd >= 0 and self._obj._writer._fd >= 0): try: consumer.control.connected(VPipePeer()) except VIOMissingControl: pass self._obj.reactor.schedule(0.0, notify) return _Control(self) @property def _p_consumer(self): return self._pi_consumer @property def _p_flows(self): return tuple() @property def _p_twoway(self): return True @property def _p_reverse(self): return self.byte_consume()