def abort_workflow(self, message=None):
        """Abort workflow - may be called from controller in any state"""

        logging.getLogger("HWR").info("Aborting workflow: %s", message)
        logging.getLogger("user_level_log").info("Aborting workflow ...")
        if self._await_result is not None:
            # Workflow waiting for answer - send abort
            self._await_result = [(GphlMessages.BeamlineAbort(), None)]

        # Shut down hardware object
        que = self.workflow_queue
        if que is None:
            self.workflow_ended()
        else:
            # If the queue is running,
            # workflow_ended will be called from post_execute
            que.put_nowait(StopIteration)
    def workflow_ended(self):
        if self.get_state() == States.OFF:
            # No workflow to abort
            return

        logging.getLogger("HWR").debug("GPhL workflow ended")
        self.set_state(States.OFF)
        if self._await_result is not None:
            # We are awaiting an answer - give an abort
            self._await_result.append((GphlMessages.BeamlineAbort(), None))
            time.sleep(0.2)
        elif self._running_process is not None:
            self._running_process = None
            # NBNB TODO how do we close down the workflow if there is no answer pending?

        self._enactment_id = None
        self._workflow_name = None
        self.workflow_queue = None
        self._await_result = None

        # xx0 = self._running_process
        # self._running_process = None
        xx0 = self.collect_emulator_process
        if xx0 is not None:
            self.collect_emulator_process = "ABORTED"
            try:
                if xx0.poll() is None:
                    xx0.send_signal(signal.SIGINT)
                    time.sleep(3)
                    if xx0.poll() is None:
                        xx0.terminate()
                        time.sleep(9)
                        if xx0.poll() is None:
                            xx0.kill()
            except Exception:
                logging.getLogger("HWR").info(
                    "Exception while terminating external workflow process %s",
                    xx0)
                logging.getLogger("HWR").info("Error was:", exc_info=True)
    def processMessage(self, py4j_message):
        """Receive and process message from workflow server
        Return goes to server

        NB Callled freom external java) workflow"""

        xx0 = self._decode_py4j_message(py4j_message)
        message_type = xx0.message_type
        payload = xx0.payload
        correlation_id = xx0.correlation_id
        enactment_id = xx0.enactment_id

        if not enactment_id:
            logging.getLogger("HWR").error(
                "GPhL message lacks enactment ID - sending 'Abort' to external workflow"
            )
            return self._response_to_server(GphlMessages.BeamlineAbort(),
                                            correlation_id)

        elif self._enactment_id is None:
            # NB this should be made less primitive
            # once we are past direct function calls
            self._enactment_id = enactment_id

        elif self._enactment_id != enactment_id:
            logging.getLogger("HWR").error(
                "Workflow enactment ID %s != message enactment ID %s"
                " - sending 'Abort' to external workflow" %
                (self._enactment_id, enactment_id))
            return self._response_to_server(GphlMessages.BeamlineAbort(),
                                            correlation_id)

        elif not payload:
            logging.getLogger("HWR").error(
                "GPhL message lacks payload - sending 'Abort' to external workflow"
            )
            return self._response_to_server(GphlMessages.BeamlineAbort(),
                                            correlation_id)

        if message_type in ("SubprocessStarted", "SubprocessStopped"):

            if self.workflow_queue is not None:
                # Could happen if we have ended the workflow
                self.workflow_queue.put_nowait(
                    (message_type, payload, correlation_id, None))
            logging.getLogger("HWR").debug(
                "Subprocess start/stop - return None")
            return None

        elif message_type in (
                "RequestConfiguration",
                "GeometricStrategy",
                "CollectionProposal",
                "ChooseLattice",
                "RequestCentring",
                "ObtainPriorInformation",
                "PrepareForCentring",
        ):
            # Requests:
            self._await_result = []
            self.set_state(States.OPEN)
            if self.workflow_queue is None:
                # Could be None if we have ended the workflow
                return self._response_to_server(GphlMessages.BeamlineAbort(),
                                                correlation_id)
            else:
                self.workflow_queue.put_nowait(
                    (message_type, payload, correlation_id,
                     self._await_result))
                while not self._await_result:
                    time.sleep(0.1)
                result, correlation_id = self._await_result.pop(0)
                self._await_result = None
                if self.get_state() == States.OPEN:
                    self.set_state(States.RUNNING)

                logging.getLogger("HWR").debug(
                    "GPhL - response=%s jobId=%s messageId=%s" %
                    (result.__class__.__name__, enactment_id, correlation_id))
                return self._response_to_server(result, correlation_id)

        elif message_type in ("WorkflowAborted", "WorkflowCompleted",
                              "WorkflowFailed"):
            if self.workflow_queue is not None:
                # Could happen if we have ended the workflow
                self.workflow_queue.put_nowait(
                    (message_type, payload, correlation_id, None))
                self.workflow_queue.put_nowait(StopIteration)
            logging.getLogger("HWR").debug("Aborting - return None")
            return None

        else:
            logging.getLogger("HWR").error(
                "GPhL Unknown message type: %s - aborting", message_type)
            return self._response_to_server(GphlMessages.BeamlineAbort(),
                                            correlation_id)