import asyncio import logging import signal from pathlib import Path import click from .core.logging_setup import configure_logging # How long a stop waits for requests in flight (and SSE streams) to finish. GRACEFUL_TIMEOUT_SECONDS = 10.0 def hypercorn_config(host: str, port: int): """Hypercorn as the production server (#5159): one worker, because Steward keeps run, stream and scheduler state in-process; its access and error lines go through Steward's logging, so the log.level setting governs them.""" from hypercorn.config import Config config = Config() config.bind = [f"{host}:{port}"] config.workers = 1 config.graceful_timeout = GRACEFUL_TIMEOUT_SECONDS config.accesslog = logging.getLogger("hypercorn.access") config.errorlog = logging.getLogger("hypercorn.error") config.access_log_format = "%(h)s %(r)s %(s)s %(b)s %(D)s" return config async def _serve(app, config) -> None: from hypercorn.asyncio import serve stop = asyncio.Event() loop = asyncio.get_running_loop() for sig in (signal.SIGTERM, signal.SIGINT): loop.add_signal_handler(sig, stop.set) await serve(app, config, shutdown_trigger=stop.wait) @click.command() @click.option("--config", default="config.yaml", help="Path to config.yaml") @click.option("--host", default="0.0.0.0") @click.option("--port", default=5000, type=int) @click.option("--debug", is_flag=True, default=False, help="Quart's development server with the reloader, not Hypercorn.") def main(config: str, host: str, port: int, debug: bool) -> None: """Start the Steward server.""" # Before anything logs; create_app applies the log.level setting once # settings load. configure_logging() from .app import create_app app = create_app(config_path=Path(config)) if debug: app.run(host=host, port=port, debug=True) return asyncio.run(_serve(app, hypercorn_config(host, port)))