Skip to content

Commit

Permalink
server: refactor startup
Browse files Browse the repository at this point in the history
Use asyncio.run() to launch the server application as recommended
by the Python docs.

Signed-off-by:  Eric Callahan <[email protected]>
  • Loading branch information
Arksine committed Aug 8, 2024
1 parent 3a42dac commit 0a85906
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 64 deletions.
5 changes: 4 additions & 1 deletion moonraker/confighelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,10 @@ def read_file(self, main_conf: pathlib.Path) -> None:
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'):
Expand Down
12 changes: 3 additions & 9 deletions moonraker/eventloop.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
_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

Expand All @@ -48,7 +48,7 @@ def asyncio_loop(self) -> AbstractEventLoop:
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
Expand Down Expand Up @@ -126,7 +126,7 @@ async def create_socket_connection(
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)
Expand All @@ -152,12 +152,6 @@ async def create_socket_connection(
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()

Expand Down
3 changes: 2 additions & 1 deletion moonraker/loghelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ def rollover_log(self) -> Awaitable[None]:
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
Expand Down
102 changes: 49 additions & 53 deletions moonraker/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def __init__(self,
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
Expand Down Expand Up @@ -198,10 +199,8 @@ async def server_init(self, start_server: bool = True) -> None:
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:
Expand All @@ -218,6 +217,9 @@ async def start_server(self, connect_to_klippy: bool = True) -> None:
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:
Expand Down Expand Up @@ -488,7 +490,7 @@ async def _stop_server(self, exit_reason: str = "restart") -> None:

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)
Expand Down Expand Up @@ -540,6 +542,45 @@ async def _handle_config_request(self, web_request: WebRequest) -> Dict[str, Any
'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"]
Expand Down Expand Up @@ -635,6 +676,7 @@ def get_env_bool(key: str) -> bool:
"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,
Expand All @@ -661,60 +703,14 @@ def get_env_bool(key: str) -> bool:
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
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":
estatus = asyncio.run(launch_server(log_manager, app_args))
if estatus is not None:
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)

0 comments on commit 0a85906

Please sign in to comment.