Example #1
0
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()
Example #2
0
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()