import platform import random import signal import time from multiprocessing import Event, Process from typing import Any from .config import Config from .typing import WorkerFunc from .utils import write_pid_file def run(config: Config) -> None: if config.pid_path is not None: write_pid_file(config.pid_path) worker_func: WorkerFunc if config.worker_class == "asyncio": from .asyncio.run import asyncio_worker worker_func = asyncio_worker elif config.worker_class == "uvloop": from .asyncio.run import uvloop_worker worker_func = uvloop_worker elif config.worker_class == "trio": from .trio.run import trio_worker worker_func = trio_worker else: raise ValueError(f"No worker of class {config.worker_class} exists") if config.workers == 1: worker_func(config) else: run_multiple(config, worker_func) def run_multiple(config: Config, worker_func: WorkerFunc) -> None: if config.use_reloader: raise RuntimeError("Reloader can only be used with a single worker") sockets = config.create_sockets() processes = [] # Ignore SIGINT before creating the processes, so that they # inherit the signal handling. This means that the shutdown # function controls the shutdown. signal.signal(signal.SIGINT, signal.SIG_IGN) shutdown_event = Event() for _ in range(config.workers): process = Process( target=worker_func, kwargs={"config": config, "shutdown_event": shutdown_event, "sockets": sockets}, ) process.daemon = True process.start() processes.append(process) if platform.system() == "Windows": time.sleep(0.1 * random.random()) def shutdown(*args: Any) -> None: shutdown_event.set() for signal_name in {"SIGINT", "SIGTERM", "SIGBREAK"}: if hasattr(signal, signal_name): signal.signal(getattr(signal, signal_name), shutdown) for process in processes: process.join() for process in processes: process.terminate() for sock in sockets.secure_sockets: sock.close() for sock in sockets.insecure_sockets: sock.close()