Exemplo n.º 1
0
class ComponentRunner(Greenlet):
    """
    A ``ComponentRunner`` is a specialized ``gevent.Greenlet`` sub-class
    that manages  a ``Component`` instance during the execution of a
    ``Network``.

    While the ``Component`` class provides the public API for defining the
    behavior of a component subclass, the ``ComponentRunner`` provides the
    private API used by the Network and port classes.
    """
    logger = logger

    def __init__(self, component, parent):
        """
        Parameters
        ----------
        component : ``rill.engine.component.Component``
        parent : ``rill.engine.network.Network``
        """
        Greenlet.__init__(self)

        self.component = component

        self._lock = RLock()
        self._can_go = Condition(self._lock)

        # the "automatic" input port
        self._null_input = None
        # the "automatic" output port
        self._null_output = None

        # the component's immediate network parent
        self.parent_network = parent

        self.has_run = False

        # used when evaluating component statuses for deadlocks
        self.curr_conn = None  # set externally
        self.curr_outport = None  # set externally

        # FIXME: allow this value to be set.  should we read it from the Network, or do we need per-component control?
        # FIXME: this feature is broken right now due to multiple output ports
        self.ignore_packet_count_error = True
        self._status = StatusValues.NOT_STARTED

    def __str__(self):
        return self.component.get_full_name()

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__,
                           self.component.get_full_name())

    def __getstate__(self):
        data = self.__dict__.copy()
        for k in ('_lock', '_can_go'):
            data.pop(k)
        return data

    # FIXME: rename to root_network
    @property
    def network(self):
        """
        The root network.

        Returns
        -------
        ``rill.engine.network.Network``
        """
        return self.get_parents()[0]

    @cache
    def get_parents(self):
        """
        Returns
        -------
        List[``rill.engine.network.Network``]
        """
        parent = self.parent_network
        parents = []
        while True:
            if parent is None:
                break
            parents.append(parent)
            parent = parent.parent_network
        parents.reverse()
        return parents

    def error(self, msg, errtype=FlowError):
        self.component.error(msg, errtype)

    # FIXME: figure out the logging stuff
    def trace_funcs(self, msg, section='funcs'):
        self.logger.debug(msg)
        # self.parent_network.trace_funcs(self, msg)

    def trace_locks(self, msg, **kwargs):
        self.logger.debug(msg, section='locks', **kwargs)
        # self.parent_network.trace_locks(self, msg)

    # Ports --

    def open_ports(self):
        """
        Open all ports.
        """
        for port in self.component.ports:
            port.open()

    def close_ports(self):
        """
        Close all ports.
        """
        for port in self.component.ports:
            port.close()

    # Statuses --

    @property
    def status(self):
        """
        Get the component's current status.

        Returns
        -------
        status : str
            one of ``rill.engine.status.StatusValues``
        """
        return self._status

    @status.setter
    def status(self, new_status):
        if new_status != self._status:
            self.logger.debug("Changing status {} -> {}".format(
                self._status, new_status),
                              component=self)
            self._status = new_status

    def is_terminated(self):
        """
        Return whether the component has terminated.

        Returns
        -------
        bool
        """
        return self.status == StatusValues.TERMINATED

    def has_error(self):
        """
        Return whether the component has an error.

        Returns
        -------
        bool
        """
        return self.status == StatusValues.ERROR

    def terminate(self, new_status=StatusValues.TERMINATED):
        """
        Terminate the component.

        Parameters
        ----------
        new_status : int
            one of ``rill.engine.status.StatusValues`` (usually "TERMINATED" or
            "ERROR")
        """
        for child in self.component.get_children():
            # may be None if the subgraph has not started yet
            if child._runner is not None:
                child._runner.terminate(new_status)
        self.logger.debug("Terminated", component=self)
        self.status = new_status
        # self.parent_network.indicate_terminated(self)
        # FIXME: Thread.interrupt()

    # def long_wait_start(self, intvl):  # interval in seconds!
    #     self.timeout = TimeoutHandler(intvl, self)
    #     self._addto_timeouts(self.timeout)
    #
    # def _addto_timeouts(self, t):
    #     """
    #     t : TimeoutHandler
    #     """
    #     # synchronized (network)
    #     self.network.timeouts[self] = t
    #     self.status = StatusValues.LONG_WAIT
    #
    # def long_wait_end(self):
    #     self.timeout.dispose(self)

    def activate(self):
        """
        Called from other parts of the system to activate this Component.

        This will start its thread or will notify it to continue.
        """
        if self.is_terminated():
            return
        if not self.active():
            self.start()
        else:
            self.trace_locks("act - lock")
            try:
                with self._lock:
                    if self.status in (StatusValues.DORMANT,
                                       StatusValues.SUSP_FIPE):
                        self._can_go.notify()
                        self.trace_locks("act - signal")
            except GreenletExit as e:
                return
            finally:
                self.trace_locks("act - unlock")

    @property
    def self_starting(self):
        """
        True if the component has no connected input ports or has been explictly
        specified as self-starting.

        This is only considered the first time the component is activiated.

        Returns
        -------
        bool
        """
        if self.has_run:
            return False
        if self.component._self_starting:
            return True
        for port in self.component.inports:
            if port.is_connected() and not port.is_initialized():
                return False
        return True

    @property
    def must_run(self):
        """
        Returns
        -------
        bool
        """
        return not self.has_run and self.component._must_run

    def is_all_drained(self):
        """
        Wait for packets to arrive or for all ports to be drained.

        Returns
        -------
        bool
            all input ports are drained
        """
        try:
            self.trace_locks("input states - acquired")
            with self._lock:
                while True:
                    conns = [
                        inp._connection for inp in self.component.inports
                        if inp.is_connected() and not inp.is_null()
                    ]
                    all_drained = all(c.is_drained() for c in conns)
                    has_data = any(not c.is_empty() for c in conns)

                    if has_data or all_drained:
                        return all_drained

                    self.status = StatusValues.DORMANT
                    self.trace_funcs("Dormant")

                    # wait for something to change
                    self.trace_locks("input state - wait")
                    self._can_go.wait()
                    self.trace_locks("input state - wait ended")

                    self.status = StatusValues.ACTIVE
                    self.trace_funcs("Active")
        finally:
            self.trace_locks("input states - unlocked")  # while

    # override of Greenlet._run
    def _run(self):
        try:
            if self.is_terminated() or self.has_error():
                if self._lock._is_owned():
                    self._lock.release()
                    self.trace_locks("run - unlock")
                return

            self.status = StatusValues.ACTIVE
            self.trace_funcs("Started")
            if self.component.ports[IN_NULL].is_connected():
                self._null_input = self.component.ports[IN_NULL]
                # block here until null input receives a packet
                self._null_input.receive_once()
            if self.component.ports[OUT_NULL].is_connected():
                self._null_output = self.component.ports[OUT_NULL]

            self_started = self.self_starting

            while (self_started or not self.is_all_drained()
                   or self._null_input is not None
                   or (self.is_all_drained() and self.must_run)
                   or self.component.stack_size() > 0):
                self._null_input = None
                self.has_run = True

                # FIXME: added has_error to allow this loop to exit if another
                # thread calls parent.signal_error() to set our status to ERROR
                if self.is_terminated() or self.has_error():
                    break

                for inp in self.component.inports:
                    if inp.is_initialized() and not inp.is_null():
                        inp.open()

                self.trace_funcs(colored("Activated", attrs=['bold']))

                self.component.execute()

                self.trace_funcs(colored("Deactivated", attrs=['bold']))

                if self.component._packet_count != 0 and not self.ignore_packet_count_error:
                    self.trace_funcs("deactivated holding {} packets".format(
                        self.component._packet_count))
                    self.error("{} packets not disposed of during component "
                               "deactivation".format(
                                   self.component._packet_count))

                # FIXME: what is the significance of closing and reopening the InitializationConnections?
                # - is_all_drained only checks Connections.
                # - tests succeed if we simply hard-wire InitializationConnection to always open
                # - it ensures that it yields a new result when component is re-activated
                for inp in self.component.inports:
                    if inp.is_initialized() and not inp.is_null():
                        inp.close()
                        # if (not icp.is_closed()):
                        #  raise FlowError("Component deactivated with IIP port not closed: " + self.get_name())
                        #

                if self_started:
                    break

                if self.is_all_drained() and self.component.stack_size() == 0:
                    break  # while

            if self._null_output is not None:
                # p = create("")
                # self._null_output.send(p)
                self._null_output.close()

            self.close_ports()

            if self.component.stack_size() != 0:
                self.error("Compodenent terminated with stack not empty")
            self.parent_network.indicate_terminated(self)

        except ComponentError as e:
            # FIXME:
            if e.get_value() > 0:
                self.trace_funcs("Component exception: " + e.get_value())
                if e.get_value() > 999:
                    self.logger.error("terminated with exception code " +
                                      e.get_value())

                    if self.parent_network is not None:
                        # record the error and terminate siblings
                        self.parent_network.signal_error(e)
                    self.close_ports()
            raise GreenletExit()

        except Exception as err:
            # don't tell the parent if we are already in the ERROR or TERMINATE state
            # because then the parent told us to terminate
            if self.is_terminated() or self.has_error():
                # if we are in the TERMINATED or ERROR state we terminated
                # intentionally
                return

            import traceback
            traceback.print_exc()

            self.status = StatusValues.ERROR

            if self.parent_network is not None:
                # record the error and terminate siblings
                self.parent_network.signal_error(err)
            self.close_ports()

    def active(self):
        return bool(self)