예제 #1
0
    async def __connection_handler(self, websocket_client_connection: websockets.WebSocketClientProtocol,
                                   path: str) -> None:
        if self.__stop_server.is_set():
            return
        # check auth and stuff TODO
        origin: Optional[str]
        success: Optional[bool]
        (origin, success) = await self.__authenticate_connection(websocket_client_connection)
        if success is False:
            # failed auth, stop connection
            await self.__close_websocket_client_connection(origin, websocket_client_connection)
            return
        origin_logger = get_origin_logger(logger, origin=origin)
        origin_logger.info("New connection from {}", websocket_client_connection.remote_address)
        async with self.__users_connecting_mutex:
            if origin in self.__users_connecting:
                origin_logger.info("Client is already connecting")
                return
            else:
                self.__users_connecting.add(origin)

        continue_register = True
        async with self.__current_users_mutex:
            origin_logger.debug("Checking if an entry is already present")
            entry = self.__current_users.get(origin, None)
            if entry is None:
                origin_logger.info("Need to start a new worker thread")

                entry = WebsocketConnectedClientEntry(origin=origin,
                                                      websocket_client_connection=websocket_client_connection,
                                                      worker_instance=None,
                                                      worker_thread=None,
                                                      loop_running=self.__loop)
                if not await self.__add_worker_and_thread_to_entry(entry, origin):
                    continue_register = False
            else:
                origin_logger.info("There is a worker thread entry present, handling accordingly")
                if entry.websocket_client_connection.open:
                    origin_logger.error("Old connection open while a new one is attempted to be established, "
                                        "aborting handling of connection")
                    continue_register = False

                entry.websocket_client_connection = websocket_client_connection
                # TODO: also change the worker's Communicator? idk yet
                if entry.worker_thread.is_alive() and not entry.worker_instance.is_stopping():
                    origin_logger.info("Worker thread still alive, continue as usual")
                    # TODO: does this need more handling? probably update communicator or whatever?
                elif not entry.worker_thread.is_alive():
                    origin_logger.info("Old thread is dead, trying to start a new one")
                    if not await self.__add_worker_and_thread_to_entry(entry, origin):
                        continue_register = False
                else:
                    origin_logger.info("Old thread is about to stop. Wait a little and reconnect")
                    # random sleep to not have clients try again in sync
                    continue_register = False
            if continue_register:
                self.__current_users[origin] = entry

        if not continue_register:
            await asyncio.sleep(rand.uniform(3, 15))
            async with self.__users_connecting_mutex:
                origin_logger.debug("Removing from users_connecting")
                self.__users_connecting.remove(origin)
            return

        try:
            if not entry.worker_thread.is_alive():
                entry.worker_thread.start()
            # TODO: we need to somehow check threads and synchronize connection status with worker status?
            async with self.__users_connecting_mutex:
                self.__users_connecting.remove(origin)
            receiver_task = asyncio.ensure_future(
                self.__client_message_receiver(origin, entry))
            await receiver_task
        except Exception as e:
            origin_logger.opt(exception=True).error("Other unhandled exception during registration: {}", e)
        # also check if thread is already running to not start it again. If it is not alive, we need to create it..
        finally:
            origin_logger.info("Awaiting unregister")
            # TODO: cleanup thread is not really desired, I'd prefer to only restart a worker if the route changes :(
            self.__worker_shutdown_queue.put(entry.worker_thread)
        origin_logger.info("Done with connection ({})", websocket_client_connection.remote_address)
예제 #2
0
    async def __connection_handler(self, websocket_client_connection: websockets.WebSocketClientProtocol,
                                   path: str) -> None:
        if self.__stop_server.is_set():
            return
        # check auth and stuff TODO
        origin: Optional[str]
        success: Optional[bool]
        (origin, success) = await self.__authenticate_connection(websocket_client_connection)
        if success is False:
            # failed auth, stop connection
            await self.__close_websocket_client_connection(origin, websocket_client_connection)
            return
        origin_logger = get_origin_logger(logger, origin=origin)

        remote_address = websocket_client_connection.remote_address

        origin_logger.info("New connection from {}", remote_address)
        if self.__enable_configmode:
            origin_logger.warning('Connected in ConfigMode.  No mapping will occur in the current mode')
        async with self.__users_connecting_mutex:
            if origin in self.__users_connecting:
                origin_logger.info("Done with connection ({}) -- Client is already connecting", remote_address)
                return
            else:
                self.__users_connecting.add(origin)

        continue_register = True
        async with self.__current_users_mutex:
            origin_logger.debug("Checking if an entry is already present")
            entry = self.__current_users.get(origin, None)
            device = None
            use_configmode = self.__enable_configmode
            if not self.__enable_configmode:
                for _, dev in self.__data_manager.search('device', params={'origin': origin}).items():
                    if dev['origin'] == origin:
                        device = dev
                        break
                if not self.__data_manager.is_device_active(device.identifier):
                    origin_logger.warning('Origin is currently paused. Unpause through MADmin to begin working')
                    use_configmode = True
            if entry is None or use_configmode:
                origin_logger.info("Need to start a new worker thread")

                entry = WebsocketConnectedClientEntry(origin=origin,
                                                      websocket_client_connection=websocket_client_connection,
                                                      worker_instance=None,
                                                      worker_thread=None,
                                                      loop_running=self.__loop)
                if not await self.__add_worker_and_thread_to_entry(entry, origin, use_configmode=use_configmode):
                    continue_register = False
            else:
                origin_logger.info("There is a worker thread entry present, handling accordingly")
                if entry.websocket_client_connection.open:
                    origin_logger.error("Old connection open while a new one is attempted to be established, "
                                        "aborting handling of connection")
                    continue_register = False
                elif entry.worker_thread.is_alive() and not entry.worker_instance.is_stopping():
                    # Ideally we just set the new connection in the entry and the worker starts using it.
                    # The problem is that the above check is racey. The worker could be stopping right
                    # now (because it shut itself down, etc). It's best to just disallow a new connection
                    # until the old connection is detected dead and the worker completely stops.
                    origin_logger.info("Worker thread still alive, rejecting new conn ({}) because thread "
                                       "should be stopped when old connection is found dead", remote_address)
                    continue_register = False
                elif not entry.worker_thread.is_alive():
                    origin_logger.info("Old thread is dead, trying to start a new one ({})", remote_address)
                    if not await self.__add_worker_and_thread_to_entry(entry, origin, use_configmode=use_configmode):
                        continue_register = False
                else:
                    origin_logger.info("Old thread is about to stop. Wait a little and reconnect ({})", remote_address)
                    # random sleep to not have clients try again in sync
                    continue_register = False
                if continue_register:
                    entry.websocket_client_connection = websocket_client_connection
            if continue_register:
                self.__current_users[origin] = entry

        if not continue_register:
            await asyncio.sleep(rand.uniform(3, 15))
            async with self.__users_connecting_mutex:
                origin_logger.debug("Removing from users_connecting")
                self.__users_connecting.remove(origin)
            origin_logger.info("Done with connection ({}) (not allowing register)", remote_address)
            return

        worker_instance = entry.worker_instance
        worker_thread = entry.worker_thread
        try:
            if not worker_thread.is_alive():
                origin_logger.debug('starting worker thread ({})', remote_address)
                worker_thread.start()
            # TODO: we need to somehow check threads and synchronize connection status with worker status?
            async with self.__users_connecting_mutex:
                self.__users_connecting.remove(origin)
            receiver_task = asyncio.ensure_future(
                self.__client_message_receiver(origin, entry))
            origin_logger.debug('awaiting __client_message_receiver for connection {}', remote_address)
            await receiver_task
        except Exception as e:
            origin_logger.opt(exception=True).error("Other unhandled exception during registration: {}", e)
        # also check if thread is already running to not start it again. If it is not alive, we need to create it..
        finally:
            # TODO: cleanup thread is not really desired, I'd prefer to only restart a worker if the route changes :(
            cur_connection = entry.websocket_client_connection
            if cur_connection == websocket_client_connection:
                origin_logger.debug('stopping worker (connection {} done)', remote_address)
                worker_instance.stop_worker()
                self.__worker_shutdown_queue.put(worker_thread)
            else:
                origin_logger.warning('Not stopping worker (connection {} done): we raced (connection {} active)',
                                      remote_address, cur_connection.remote_address)
        origin_logger.info("Done with connection ({})", remote_address)