server: refactor startup

Use asyncio.run() to launch the server application as recommended
by the Python docs.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2024-08-08 16:29:01 -04:00
parent 3a42dac02b
commit 0a8590643f
No known key found for this signature in database
GPG Key ID: 5A1EB336DFB4C71B
4 changed files with 58 additions and 64 deletions

View File

@ -1108,7 +1108,10 @@ class FileSourceWrapper(ConfigSourceWrapper):
def get_configuration( def get_configuration(
server: Server, app_args: Dict[str, Any] server: Server, app_args: Dict[str, Any]
) -> ConfigHelper: ) -> 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 = FileSourceWrapper(server)
source.read_file(start_path) source.read_file(start_path)
if not source.config.has_section('server'): if not source.config.has_section('server'):

View File

@ -27,7 +27,7 @@ _uvl_var = os.getenv("MOONRAKER_ENABLE_UVLOOP", "y").lower()
_uvl_enabled = False _uvl_enabled = False
if _uvl_var in ["y", "yes", "true"]: if _uvl_var in ["y", "yes", "true"]:
with contextlib.suppress(ImportError): with contextlib.suppress(ImportError):
import uvloop import uvloop # type: ignore
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
_uvl_enabled = True _uvl_enabled = True
@ -48,7 +48,7 @@ class EventLoop:
return self.aioloop return self.aioloop
def reset(self) -> None: 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.add_signal_handler = self.aioloop.add_signal_handler
self.remove_signal_handler = self.aioloop.remove_signal_handler self.remove_signal_handler = self.aioloop.remove_signal_handler
self.add_reader = self.aioloop.add_reader self.add_reader = self.aioloop.add_reader
@ -126,7 +126,7 @@ class EventLoop:
host, port, family=0, type=socket.SOCK_STREAM host, port, family=0, type=socket.SOCK_STREAM
) )
for res in ainfo: for res in ainfo:
af, socktype, proto, canonname, sa = res af, socktype, proto, _cannon_name, _sa = res
sock = None sock = None
try: try:
sock = socket.socket(af, socktype, proto) sock = socket.socket(af, socktype, proto)
@ -152,12 +152,6 @@ class EventLoop:
else: else:
raise socket.error("getaddrinfo returns an empty list") raise socket.error("getaddrinfo returns an empty list")
def start(self):
self.aioloop.run_forever()
def stop(self):
self.aioloop.stop()
def close(self): def close(self):
self.aioloop.close() self.aioloop.close()

View File

@ -130,6 +130,7 @@ class LogManager:
return eventloop.run_in_thread(self.file_hdlr.doRollover) return eventloop.run_in_thread(self.file_hdlr.doRollover)
def stop_logging(self): def stop_logging(self):
if self.listener is not None:
self.listener.stop() self.listener.stop()
async def _handle_log_rollover( async def _handle_log_rollover(

View File

@ -89,6 +89,7 @@ class Server:
self.ssl_port: int = config.getint('ssl_port', 7130) self.ssl_port: int = config.getint('ssl_port', 7130)
self.exit_reason: str = "" self.exit_reason: str = ""
self.server_running: bool = False self.server_running: bool = False
self.app_running_evt = asyncio.Event()
self.pip_recovery_attempted: bool = False self.pip_recovery_attempted: bool = False
# Configure Debug Logging # Configure Debug Logging
@ -198,10 +199,8 @@ class Server:
await self.event_loop.run_in_thread(self.config.create_backup) await self.event_loop.run_in_thread(self.config.create_backup)
machine: Machine = self.lookup_component("machine") machine: Machine = self.lookup_component("machine")
if await machine.validate_installation(): restarting = await machine.validate_installation()
return if not restarting and start_server:
if start_server:
await self.start_server() await self.start_server()
async def start_server(self, connect_to_klippy: bool = True) -> None: async def start_server(self, connect_to_klippy: bool = True) -> None:
@ -218,6 +217,9 @@ class Server:
if connect_to_klippy: if connect_to_klippy:
self.klippy_connection.connect() self.klippy_connection.connect()
async def run_until_exit(self) -> None:
await self.app_running_evt.wait()
def add_log_rollover_item( def add_log_rollover_item(
self, name: str, item: str, log: bool = True self, name: str, item: str, log: bool = True
) -> None: ) -> None:
@ -488,7 +490,7 @@ class Server:
self.exit_reason = exit_reason self.exit_reason = exit_reason
self.event_loop.remove_signal_handler(signal.SIGTERM) 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: async def _handle_server_restart(self, web_request: WebRequest) -> str:
self.event_loop.register_callback(self._stop_server) self.event_loop.register_callback(self._stop_server)
@ -540,6 +542,45 @@ class Server:
'files': cfg_file_list '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 main(from_package: bool = True) -> None:
def get_env_bool(key: str) -> bool: def get_env_bool(key: str) -> bool:
return os.getenv(key, "").lower() in ["y", "yes", "true"] 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), "data_path": str(data_path),
"is_default_data_path": cmd_line_args.datapath is None, "is_default_data_path": cmd_line_args.datapath is None,
"config_file": cfg_file, "config_file": cfg_file,
"backup_config": confighelper.find_config_backup(cfg_file),
"startup_warnings": startup_warnings, "startup_warnings": startup_warnings,
"verbose": cmd_line_args.verbose, "verbose": cmd_line_args.verbose,
"debug": cmd_line_args.debug, "debug": cmd_line_args.debug,
@ -661,60 +703,14 @@ def main(from_package: bool = True) -> None:
log_manager = LogManager(app_args, startup_warnings) log_manager = LogManager(app_args, startup_warnings)
# Start asyncio event loop and server # Start asyncio event loop and server
event_loop = EventLoop()
alt_config_loaded = False
estatus = 0
while True: while True:
try: estatus = asyncio.run(launch_server(log_manager, app_args))
server = Server(app_args, log_manager, event_loop) if estatus is not None:
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 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
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 # Since we are running outside of the the server
# it is ok to use a blocking sleep here # it is ok to use a blocking sleep here
time.sleep(.5) time.sleep(.5)
logging.info("Attempting Server Restart...") logging.info("Attempting Server Restart...")
del server
event_loop.reset()
event_loop.close()
logging.info("Server Shutdown") logging.info("Server Shutdown")
log_manager.stop_logging() log_manager.stop_logging()
exit(estatus) exit(estatus)