codekingpro/portable-devtools
114k
1"""2Some light wrappers around Python's multiprocessing, to deal with cleanly3starting child processes.4"""5 6from __future__ import annotations7 8import multiprocessing9import os10import sys11from collections.abc import Callable12from multiprocessing.context import SpawnProcess13from socket import socket14 15from uvicorn.config import Config16 17multiprocessing.allow_connection_pickling()18spawn = multiprocessing.get_context("spawn")19 20 21def get_subprocess(22 config: Config,23 target: Callable[..., None],24 sockets: list[socket],25) -> SpawnProcess:26 """27 Called in the parent process, to instantiate a new child process instance.28 The child is not yet started at this point.29 30 * config - The Uvicorn configuration instance.31 * target - A callable that accepts a list of sockets. In practice this will32 be the `Server.run()` method.33 * sockets - A list of sockets to pass to the server. Sockets are bound once34 by the parent process, and then passed to the child processes.35 """36 # We pass across the stdin fileno, and reopen it in the child process.37 # This is required for some debugging environments.38 try:39 stdin_fileno = sys.stdin.fileno()40 # The `sys.stdin` can be `None`, see https://docs.python.org/3/library/sys.html#sys.__stdin__.41 except (AttributeError, OSError):42 stdin_fileno = None43 44 kwargs = {45 "config": config,46 "target": target,47 "sockets": sockets,48 "stdin_fileno": stdin_fileno,49 }50 51 return spawn.Process(target=subprocess_started, kwargs=kwargs)52 53 54def subprocess_started(55 config: Config,56 target: Callable[..., None],57 sockets: list[socket],58 stdin_fileno: int | None,59) -> None:60 """61 Called when the child process starts.62 63 * config - The Uvicorn configuration instance.64 * target - A callable that accepts a list of sockets. In practice this will65 be the `Server.run()` method.66 * sockets - A list of sockets to pass to the server. Sockets are bound once67 by the parent process, and then passed to the child processes.68 * stdin_fileno - The file number of sys.stdin, so that it can be reattached69 to the child process.70 """71 # Re-open stdin.72 if stdin_fileno is not None:73 sys.stdin = os.fdopen(stdin_fileno) # pragma: full coverage74 75 # Logging needs to be setup again for each child.76 config.configure_logging()77 78 try:79 # Now we can call into `Server.run(sockets=sockets)`80 target(sockets=sockets)81 except KeyboardInterrupt: # pragma: no cover82 # suppress the exception to avoid a traceback from subprocess.Popen83 # the parent already expects us to end, so no vital information is lost84 pass85 