From 0a8590643f3e6e5287565c98b6abfa2c719aeba9 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Thu, 8 Aug 2024 16:29:01 -0400 Subject: [PATCH] server: refactor startup Use asyncio.run() to launch the server application as recommended by the Python docs. Signed-off-by: Eric Callahan --- moonraker/confighelper.py | 5 +- moonraker/eventloop.py | 12 ++--- moonraker/loghelper.py | 3 +- moonraker/server.py | 102 ++++++++++++++++++-------------------- 4 files changed, 58 insertions(+), 64 deletions(-) diff --git a/moonraker/confighelper.py b/moonraker/confighelper.py index 3820a57..a68a0c1 100644 --- a/moonraker/confighelper.py +++ b/moonraker/confighelper.py @@ -1108,7 +1108,10 @@ class FileSourceWrapper(ConfigSourceWrapper): def get_configuration( server: Server, app_args: Dict[str, Any] ) -> ConfigHelper: - start_path = pathlib.Path(app_args['config_file']).expanduser().resolve() + cfg_file = app_args["config_file"] + if app_args["is_backup_config"]: + cfg_file = app_args["backup_config"] + start_path = pathlib.Path(cfg_file).expanduser().absolute() source = FileSourceWrapper(server) source.read_file(start_path) if not source.config.has_section('server'): diff --git a/moonraker/eventloop.py b/moonraker/eventloop.py index 6ee83b9..e2c2c16 100644 --- a/moonraker/eventloop.py +++ b/moonraker/eventloop.py @@ -27,7 +27,7 @@ _uvl_var = os.getenv("MOONRAKER_ENABLE_UVLOOP", "y").lower() _uvl_enabled = False if _uvl_var in ["y", "yes", "true"]: with contextlib.suppress(ImportError): - import uvloop + import uvloop # type: ignore asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) _uvl_enabled = True @@ -48,7 +48,7 @@ class EventLoop: return self.aioloop def reset(self) -> None: - self.aioloop = self._create_new_loop() + self.aioloop = asyncio.get_running_loop() self.add_signal_handler = self.aioloop.add_signal_handler self.remove_signal_handler = self.aioloop.remove_signal_handler self.add_reader = self.aioloop.add_reader @@ -126,7 +126,7 @@ class EventLoop: host, port, family=0, type=socket.SOCK_STREAM ) for res in ainfo: - af, socktype, proto, canonname, sa = res + af, socktype, proto, _cannon_name, _sa = res sock = None try: sock = socket.socket(af, socktype, proto) @@ -152,12 +152,6 @@ class EventLoop: else: raise socket.error("getaddrinfo returns an empty list") - def start(self): - self.aioloop.run_forever() - - def stop(self): - self.aioloop.stop() - def close(self): self.aioloop.close() diff --git a/moonraker/loghelper.py b/moonraker/loghelper.py index 8245fb7..640e651 100644 --- a/moonraker/loghelper.py +++ b/moonraker/loghelper.py @@ -130,7 +130,8 @@ class LogManager: return eventloop.run_in_thread(self.file_hdlr.doRollover) def stop_logging(self): - self.listener.stop() + if self.listener is not None: + self.listener.stop() async def _handle_log_rollover( self, web_request: WebRequest diff --git a/moonraker/server.py b/moonraker/server.py index 456de39..4b11338 100755 --- a/moonraker/server.py +++ b/moonraker/server.py @@ -89,6 +89,7 @@ class Server: self.ssl_port: int = config.getint('ssl_port', 7130) self.exit_reason: str = "" self.server_running: bool = False + self.app_running_evt = asyncio.Event() self.pip_recovery_attempted: bool = False # Configure Debug Logging @@ -198,10 +199,8 @@ class Server: await self.event_loop.run_in_thread(self.config.create_backup) machine: Machine = self.lookup_component("machine") - if await machine.validate_installation(): - return - - if start_server: + restarting = await machine.validate_installation() + if not restarting and start_server: await self.start_server() async def start_server(self, connect_to_klippy: bool = True) -> None: @@ -218,6 +217,9 @@ class Server: if connect_to_klippy: self.klippy_connection.connect() + async def run_until_exit(self) -> None: + await self.app_running_evt.wait() + def add_log_rollover_item( self, name: str, item: str, log: bool = True ) -> None: @@ -488,7 +490,7 @@ class Server: self.exit_reason = exit_reason self.event_loop.remove_signal_handler(signal.SIGTERM) - self.event_loop.stop() + self.app_running_evt.set() async def _handle_server_restart(self, web_request: WebRequest) -> str: self.event_loop.register_callback(self._stop_server) @@ -540,6 +542,45 @@ class Server: 'files': cfg_file_list } +async def launch_server( + log_manager: LogManager, app_args: Dict[str, Any] +) -> Optional[int]: + eventloop = EventLoop() + startup_warnings: List[str] = app_args["startup_warnings"] + try: + server = Server(app_args, log_manager, eventloop) + server.load_components() + except confighelper.ConfigError as e: + logging.exception("Server Config Error") + backup_cfg: Optional[str] = app_args["backup_config"] + if app_args["is_backup_config"] or backup_cfg is None: + return 1 + app_args["is_backup_config"] = True + startup_warnings.append( + f"Server configuration error: {e}\n" + f"Loading most recent working configuration: '{backup_cfg}'\n" + f"Please fix the issue in moonraker.conf and restart the server." + ) + return True + except Exception: + logging.exception("Moonraker Error") + return 1 + try: + await server.server_init() + await server.run_until_exit() + except Exception: + logging.exception("Server Running Error") + return 1 + if server.exit_reason == "terminate": + return 0 + # Restore the original config and clear the warning + # before the server restarts + if app_args["is_backup_config"]: + startup_warnings.pop() + app_args["is_backup_config"] = False + del server + return None + def main(from_package: bool = True) -> None: def get_env_bool(key: str) -> bool: return os.getenv(key, "").lower() in ["y", "yes", "true"] @@ -635,6 +676,7 @@ def main(from_package: bool = True) -> None: "data_path": str(data_path), "is_default_data_path": cmd_line_args.datapath is None, "config_file": cfg_file, + "backup_config": confighelper.find_config_backup(cfg_file), "startup_warnings": startup_warnings, "verbose": cmd_line_args.verbose, "debug": cmd_line_args.debug, @@ -661,60 +703,14 @@ def main(from_package: bool = True) -> None: log_manager = LogManager(app_args, startup_warnings) # Start asyncio event loop and server - event_loop = EventLoop() - alt_config_loaded = False - estatus = 0 while True: - try: - server = Server(app_args, log_manager, event_loop) - server.load_components() - except confighelper.ConfigError as e: - backup_cfg = confighelper.find_config_backup(cfg_file) - logging.exception("Server Config Error") - if alt_config_loaded or backup_cfg is None: - estatus = 1 - break - app_args["config_file"] = backup_cfg - app_args["is_backup_config"] = True - warn_list = list(startup_warnings) - app_args["startup_warnings"] = warn_list - warn_list.append( - f"Server configuration error: {e}\n" - f"Loaded server from most recent working configuration:" - f" '{app_args['config_file']}'\n" - f"Please fix the issue in moonraker.conf and restart " - f"the server." - ) - alt_config_loaded = True - continue - except Exception: - logging.exception("Moonraker Error") - estatus = 1 + estatus = asyncio.run(launch_server(log_manager, app_args)) + if estatus is not None: break - try: - event_loop.register_callback(server.server_init) - event_loop.start() - except Exception: - logging.exception("Server Running Error") - estatus = 1 - break - if server.exit_reason == "terminate": - break - # Restore the original config and clear the warning - # before the server restarts - if alt_config_loaded: - app_args["config_file"] = cfg_file - app_args["startup_warnings"] = startup_warnings - app_args["is_backup_config"] = False - alt_config_loaded = False - event_loop.close() # Since we are running outside of the the server # it is ok to use a blocking sleep here time.sleep(.5) logging.info("Attempting Server Restart...") - del server - event_loop.reset() - event_loop.close() logging.info("Server Shutdown") log_manager.stop_logging() exit(estatus)