Exemple #1
0
def test_logger_thread():
    """ Test the logger thread.

    """
    logger = logging.getLogger('test')
    queue = Queue()
    handler = QueueHandler(queue)
    logger.addHandler(handler)
    logger.info('test')
    logger.removeHandler(handler)
    queue.put(None)

    model = PanelModel()
    handler = GuiHandler(model)
    logger.addHandler(handler)

    thread = QueueLoggerThread(queue)
    thread.start()
    thread.join(2)

    if thread.is_alive():
        raise

    qapp = QtApplication.instance()._qapp
    qapp.flush()
    qapp.processEvents()

    assert_equal(model.text, 'test\n')
def test_logger_thread():
    """ Test the logger thread.

    """
    logger = logging.getLogger('test')
    queue = Queue()
    handler = QueueHandler(queue)
    logger.addHandler(handler)
    logger.info('test')
    logger.removeHandler(handler)
    queue.put(None)

    model = PanelModel()
    handler = GuiHandler(model)
    logger.addHandler(handler)

    thread = QueueLoggerThread(queue)
    thread.start()
    thread.join(2)

    if thread.is_alive():
        raise

    qapp = QtApplication.instance()._qapp
    qapp.flush()
    qapp.processEvents()

    assert_equal(model.text, 'test\n')
Exemple #3
0
class ProcessEngine(BaseEngine):
    """ An engine executing the measurement it is sent in a different process.

    """

    # --- Public API ----------------------------------------------------------

    #: Reference to the workbench got at __init__
    workbench = Typed(Workbench)

    def prepare_to_run(self, name, root, monitored_entries, build_deps):

        runtime_deps = root.run_time

        # Get ConfigObj describing measure.
        root.update_preferences_from_members()
        config = root.task_preferences

        # Make infos tuple to send to the subprocess.
        self._temp = (name, config, build_deps, runtime_deps,
                      monitored_entries)

        # Clear all the flags.
        self._meas_pause.clear()
        self._meas_paused.clear()
        self._meas_stop.clear()
        self._stop.clear()
        self._force_stop.clear()
        self._stop_requested = False

        # If the process does not exist or is dead create a new one.
        if not self._process or not self._process.is_alive():
            self._pipe, process_pipe = Pipe()
            self._process = TaskProcess(process_pipe,
                                        self._log_queue,
                                        self._monitor_queue,
                                        self._meas_pause,
                                        self._meas_paused,
                                        self._meas_stop,
                                        self._stop)
            self._process.daemon = True

            self._log_thread = QueueLoggerThread(self._log_queue)
            self._log_thread.daemon = True

            self._monitor_thread = ThreadMeasureMonitor(self,
                                                        self._monitor_queue)
            self._monitor_thread.daemon = True

            self._pause_thread = None

        self.measure_status = ('PREPARED', 'Engine ready to process.')

    def run(self):
        if not self._process.is_alive():
            # Starting monitoring threads.
            self._log_thread.start()
            self._monitor_thread.start()

            # Start process.
            self._process.start()
            self.active = True

            # Start main communication thread.
            self._com_thread = Thread(group=None,
                                      target=self._process_listener)
            self._com_thread.start()

        self._starting_allowed.set()

        self.measure_status = ('RUNNING', 'Measure running.')

    def pause(self):
        self.measure_status = ('PAUSING', 'Waiting for measure to pause.')
        self._meas_pause.set()

        self._pause_thread = Thread(target=self._wait_for_pause)
        self._pause_thread.start()

    def resume(self):
        self._meas_pause.clear()
        self.measure_status = ('RUNNING', 'Measure have been resumed.')

    def stop(self):
        self._stop_requested = True
        self._meas_stop.set()

    def exit(self):
        self._stop_requested = True
        self._meas_stop.set()
        self._stop.set()
        # Everything else handled by the _com_thread and the process.

    def force_stop(self):
        self._stop_requested = True
        # Just in case the user calls this directly. Will signal all threads to
        # stop (save _com_thread).
        self._stop.set()
        self._log_queue.put(None)
        self._monitor_queue.put((None, None))

        # Set _force_stop to stop _com_thread.
        self._force_stop.set()

        # Terminate the process and make sure all threads stopped properly.
        self._process.terminate()
        self._log_thread.join()
        self._monitor_thread.join()
        self._com_thread.join()
        self.active = False
        if self._processing.is_set():
            self.done = ('INTERRUPTED', 'The user forced the system to stop')
            self._processing.clear()

        # Discard the queues as they may have been corrupted when the process
        # was terminated.
        self._log_queue = Queue()
        self._monitor_queue = Queue()

    def force_exit(self):
        self.force_stop()

    # --- Private API ---------------------------------------------------------

    #: Flag indicating that the user requested the measure to stop.
    _stop_requested = Bool()

    #: Interprocess event used to pause the subprocess current measure.
    _meas_pause = Typed(Event, ())

    #: Interprocess event signaling the subprocess current measure is paused.
    _meas_paused = Typed(Event, ())

    #: Interprocess event used to stop the subprocess current measure.
    _meas_stop = Typed(Event, ())

    #: Interprocess event used to stop the subprocess.
    _stop = Typed(Event, ())

    #: Flag signaling that a forced exit has been requested
    _force_stop = Value(tEvent())

    #: Flag indicating the process is waiting for a measure.
    _processing = Value(tEvent())

    #: Flag indicating the communication thread it can send the next measure.
    _starting_allowed = Value(tEvent())

    #: Temporary tuple to store the data to be sent to the process when a
    #: new measure is ready.
    _temp = Tuple()

    #: Current subprocess.
    _process = Typed(TaskProcess)

    #: Connection used to send and receive messages about execution (type
    #: ambiguous when the OS is not known)
    _pipe = Value()

    #: Thread in charge of transferring measure to the process.
    _com_thread = Typed(Thread)

    #: Inter-process queue used by the subprocess to transmit its log records.
    _log_queue = Typed(Queue, ())

    #: Thread in charge of collecting the log message coming from the
    #: subprocess.
    _log_thread = Typed(Thread)

    #: Inter-process queue used by the subprocess to send the values of the
    #: observed database entries.
    _monitor_queue = Typed(Queue, ())

    #: Thread in charge of collecting the values of the observed database
    #: entries.
    _monitor_thread = Typed(Thread)

    #: Thread in charge to notify the engine that the measure did pause after
    #: being asked to do so.
    _pause_thread = Typed(Thread)

    def _process_listener(self):
        """ Handle the communications with the worker process.

        Executed by the _com_thread.

        """
        logger = logging.getLogger(__name__)
        logger.debug('Starting listener')

        while not self._pipe.poll(2):
            if not self._process.is_alive():
                logger.critical('Subprocess was found dead unexpectedly')
                self._stop.set()
                self._log_queue.put(None)
                self._monitor_queue.put((None, None))
                self._cleanup(process=False)
                self.done = ('FAILED', 'Subprocess failed to start')
                return

        mess = self._pipe.recv()
        if mess != 'READY':
            logger.critical('Subprocess was found dead unexpectedly')
            self.done = ('FAILED', 'Subprocess failed to start')
            self._cleanup()
            return

        # Infinite loop waiting for measure.
        while not self._stop.is_set():

            # Wait for measure and check for stopping.
            while not self._starting_allowed.wait(1):
                if self._stop.is_set():
                    self._cleanup()
                    return

            self._processing.set()

            # Send the measure.
            self._pipe.send(self._temp)
            logger.debug('Measure {} sent'.format(self._temp[0]))

            # Empty _temp and reset flag.
            self._temp = tuple()
            self._starting_allowed.clear()

            # Wait for the process to finish the measure and check it has not
            # been killed.
            while not self._pipe.poll(1):
                if self._force_stop.is_set():
                    self._cleanup()
                    return

            # Here get message from process and react
            meas_status, int_status, mess = self._pipe.recv()
            logger.debug('Subprocess done performing measure')

            if int_status == 'STOPPING':
                self._cleanup()

            if meas_status == 'INTERRUPTED' and not self._stop_requested:
                meas_status = 'FAILED'
                mess = mess.replace('was stopped', 'failed')

            # This event should be handled in the main thread so that this one
            # can stay responsive otherwise the engine will be unable to
            # shutdown.
            deferred_call(setattr, self, 'done', (meas_status, mess))

            self._processing.clear()

        self._cleanup()

    def _cleanup(self, process=True):
        """ Helper method taking care of making sure that everybody stops.

        Parameters
        ----------
        process : bool
            Wether to join the worker process. Used when the process has been
            termintaed abruptly.

        """
        logger = logging.getLogger(__name__)
        logger.debug('Cleaning up')
        self._pipe.close()
        if process:
            self._process.join()
            logger.debug('Subprocess joined')
        self._log_thread.join()
        logger.debug('Log thread joined')
        self._monitor_thread.join()
        logger.debug('Monitor thread joined')
        if self._pause_thread:
            self._pause_thread.join()
            logger.debug('Pause thread joined')
        self.active = False

    def _wait_for_pause(self):
        """ Wait for the task paused event to be set.

        """
        stop_sig = self._stop
        paused_sig = self._meas_paused

        while not stop_sig.is_set():
            if paused_sig.wait(0.1):
                status = ('PAUSED', 'Measure execution is paused')
                deferred_call(setattr, self, 'measure_status', status)
                break