Exemplo n.º 1
0
def load_shared_memory_manager():
    # type: () -> None
    """ Connects to the main shared memory manager initiated in piper_worker.py.

    :return: None
    """
    global SHARED_MEMORY_MANAGER
    SHARED_MEMORY_MANAGER = SharedMemoryManager(address=(IP, PORT),
                                                authkey=AUTH_KEY)
    SHARED_MEMORY_MANAGER.connect()
Exemplo n.º 2
0
class NapariClient(Thread):
    """Shared memory client for napari.

    Napari launches this process. We get config information from our
    NAPARI_MON_CLIENT environment variable. We connect our
    SharedMemoryManager to the server_port specified in that config file.

    See napari's MonitorApi in  for
    documention on the shared resources that napari exposes.

    Parameters
    ----------
    config : dict
        The parsed configuration from the NAPARI_MON_CLIENT env variable.
    on_shutdown : Callable[[], None]
        We call then when shutting down.
    """
    def __init__(self, config: dict, on_shutdown: Callable[[], None]):
        super().__init__()
        self.config = config
        self._on_shutdown = on_shutdown
        self._running = False

        LOGGER.info("Starting process %s", os.getpid())
        _log_env()  # Log our startup environment.

        server_port = config['server_port']
        LOGGER.info("connecting to napari on port %d.", server_port)

        # We have to register these before creating the SharedMemoryManager.
        # Note that we don't have to give the types, just the names.
        # Although to use them we probably want to know the types!
        for name in NapariRemoteAPI.RESOURCES:
            SharedMemoryManager.register(name)

        # Connect to napari's shared memory on the server_port that napari
        # passed us in our NAPARI_MON_CLIENT configuration.
        self._manager = SharedMemoryManager(
            address=('localhost', config['server_port']),
            authkey=str.encode('napari'),
        )
        self._manager.connect()

        # Get the shared resources as a convenient named tuple.
        self._remote = NapariRemoteAPI.from_manager(self._manager)

        # Start our thread which will poll napari.
        self.start()

    def run(self) -> None:
        """Thread that communicates with napari.

        Poll until we see the napari_shutdown event was set, or we get a
        connection error. Right now napari does not wait after signaling a
        shutdown, so more than 9/10 times the first indication we receive
        the napari is shutting down is getting a connection error.

        Which is fine for now. But a graceful handshake-exit might be
        something to look into. Obviously napari should have a short
        timeout so if the client is hung, it still exits quickly.
        """

        self._running = True
        tid = threading.get_ident()
        LOGGER.info("Started NapariClient.run with thread_id = %d.", tid)

        while True:
            try:
                if not self._poll():
                    break  # Shutdown event, exit the thread.
            except ConnectionResetError:
                LOGGER.info("ConnectionResetError polling napari.")
                break  # Napari exited, exit the thread.

            # Sleep until ready to poll again.
            time.sleep(POLL_INTERVAL_SECONDS)

        LOGGER.info("Thread %d is exiting.", tid)

        # Notify webmon that we shutdown.
        self._running = False
        self._on_shutdown()

    def _poll(self) -> bool:
        """Communicate with napari.

        Return
        ------
        bool
            Return True if we should keep polling.
        """
        if self._remote.napari_shutdown.is_set():
            LOGGER.info("Napari signaled shutdown.")
            return False  # Stop polling.

        return True  # Keep polling.

    def get_napari_data(self, key):
        """Get data from napari."""
        return self._remote.napari_data.get(key)

    def send_message(self, message: dict) -> None:
        """Send new message to napari.

        Parameters
        ----------
        message : dict
            The message/command to send to napari.
        """
        LOGGER.info("Sending message %s", message)

        try:
            # Put message into the queue.
            self._remote.client_messages.put(message)
        except ConnectionRefusedError:
            LOGGER.error("ConnectionRefusedError sending message to napari.")

    def get_one_napari_message(self) -> Optional[dict]:
        """Get one message from napari, non-blocking.

        Return
        ------
        Optional[dict]
            The message or None if no message was available.
        """
        if not self._running:
            return None  # Can't get messages from napari.

        napari_messages = self._remote.napari_messages

        try:
            message = napari_messages.get_nowait()
            assert isinstance(message, dict)  # For now.
            return message

        except ConnectionResetError:
            LOGGER.error("ConnectionResetError getting messages from napari")
            # Napari is probably gone, but we let NapariClient._poll()
            # notice this and exit. We just say there as no message.
            return None

        except Empty:
            return None  # No message in the queue.

    @classmethod
    def create(cls, on_shutdown: Callable[[], None]):
        """Create and return the NapariClient instance.

        Parameters
        ----------
        on_shutdown : Callable[[], None]
            NapariClient will call this when it shuts down.

        Return
        ------
        Optional[NapariClient]
            The newly created client or None on error.
        """
        config = _get_client_config()
        if config is None:
            return None
        LOGGER.info("Creating NapariClient pid=%s", os.getpid())
        return cls(config, on_shutdown)
Exemplo n.º 3
0
class MonitorClient(Thread):
    """Client for napari shared memory monitor.

    Napari launches us. We get config information from our NAPARI_MON_CLIENT
    environment variable. That contains a port number to connect to.
    We connect our SharedMemoryManager to that port.

    We get these resources from the manager:

    1) shutdown_event()

    If this is set napari is exiting. Ususally it exists so fast we get
    at ConnectionResetError exception instead of see this was set. We have
    no clean way to exit the SocketIO server yet.

    2) command_queue()

    We put command onto this queue for napari to execute.

    3) data()

    Data from napari's monitor.add() command.
    """
    def __init__(self, config: dict, client_name="?"):
        super().__init__()
        assert config
        self.config = config
        self.client_name = client_name

        self.running = True
        self.napari_data = None

        LOGGER.info("Starting MonitorClient process %s", os.getpid())
        _log_env()

        server_port = config['server_port']
        LOGGER.info("Connecting to port %d...", server_port)

        # Right now we just need to magically know these callback names,
        # maybe we can come up with a better way.
        napari_api = ['shutdown_event', 'command_queue', 'data']
        for name in napari_api:
            SharedMemoryManager.register(name)

        # Connect to napari's shared memory.
        self._manager = SharedMemoryManager(
            address=('localhost', config['server_port']),
            authkey=str.encode('napari'),
        )
        self._manager.connect()

        # Get the shared resources.
        self._shared = SharedResources(
            self._manager.shutdown_event(),
            self._manager.command_queue(),
            self._manager.data(),
        )

        # Start our thread so we can poll napari.
        self.start()

    def run(self) -> None:
        """Check shared memory for new data."""

        LOGGER.info("MonitorClient thread is running...")

        while True:
            if not self._poll():
                LOGGER.info("Exiting...")
                break

            time.sleep(POLL_INTERVAL_MS / 1000)

        # webmon checks this and stops/exits.
        self.running = False

    def _poll(self) -> bool:
        """See if there is now information in shared mem."""

        # LOGGER.info("Poll...")
        try:
            if self._shared.shutdown.is_set():
                # We sometimes do see the shutdown event was set. But usually
                # we just get ConnectionResetError, because napari is exiting.
                LOGGER.info("Shutdown event was set.")
                return False  # Stop polling
        except ConnectionResetError:
            LOGGER.info("ConnectionResetError.")
            return False  # Stop polling

        # Do we need to copy here?
        self.napari_data = {
            "tile_config": self._shared.data.get('tile_config'),
            "tile_state": self._shared.data.get('tile_state'),
        }

        if DUMP_DATA_FROM_NAPARI:
            pretty_str = json.dumps(self.napari_data, indent=4)
            LOGGER.info("New data from napari: %s", pretty_str)

        return True  # Keep polling

    def post_command(self, command) -> None:
        """Send new command to napari.
        """
        LOGGER.info(f"Posting command {command}")

        try:
            self._shared.commands.put(command)
        except ConnectionRefusedError:
            self._log("ConnectionRefusedError")

    def stop(self) -> None:
        """Call on shutdown. TODO_MON: no one calls this yet?"""
        self._manager.shutdown()