Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
multiprocess.py224 linesDownload Raw Back to supervisors
1from __future__ import annotations2 3import logging4import os5import signal6import threading7from collections.abc import Callable8from multiprocessing import Pipe9from socket import socket10from typing import Any11 12import click13 14from uvicorn._subprocess import get_subprocess15from uvicorn.config import Config16 17SIGNALS = {18    getattr(signal, f"SIG{x}"): x19    for x in "INT TERM BREAK HUP QUIT TTIN TTOU USR1 USR2 WINCH".split()20    if hasattr(signal, f"SIG{x}")21}22 23logger = logging.getLogger("uvicorn.error")24 25 26class Process:27    def __init__(28        self,29        config: Config,30        target: Callable[[list[socket] | None], None],31        sockets: list[socket],32    ) -> None:33        self.real_target = target34 35        self.parent_conn, self.child_conn = Pipe()36        self.process = get_subprocess(config, self.target, sockets)37 38    def ping(self, timeout: float = 5) -> bool:39        self.parent_conn.send(b"ping")40        if self.parent_conn.poll(timeout):41            self.parent_conn.recv()42            return True43        return False44 45    def pong(self) -> None:46        self.child_conn.recv()47        self.child_conn.send(b"pong")48 49    def always_pong(self) -> None:50        while True:51            self.pong()52 53    def target(self, sockets: list[socket] | None = None) -> Any:  # pragma: no cover54        if os.name == "nt":  # pragma: py-not-win3255            # Windows doesn't support SIGTERM, so we use SIGBREAK instead.56            # And then we raise SIGTERM when SIGBREAK is received.57            # https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/signal?view=msvc-17058            signal.signal(59                signal.SIGBREAK,  # type: ignore[attr-defined]60                lambda sig, frame: signal.raise_signal(signal.SIGTERM),61            )62 63        threading.Thread(target=self.always_pong, daemon=True).start()64        return self.real_target(sockets)65 66    def is_alive(self, timeout: float = 5) -> bool:67        if not self.process.is_alive():68            return False  # pragma: full coverage69 70        return self.ping(timeout)71 72    def start(self) -> None:73        self.process.start()74 75    def terminate(self) -> None:76        if self.process.exitcode is None:  # Process is still running77            assert self.process.pid is not None78            if os.name == "nt":  # pragma: py-not-win3279                # Windows doesn't support SIGTERM.80                # So send SIGBREAK, and then in process raise SIGTERM.81                os.kill(self.process.pid, signal.CTRL_BREAK_EVENT)  # type: ignore[attr-defined]82            else:83                os.kill(self.process.pid, signal.SIGTERM)84            logger.info(f"Terminated child process [{self.process.pid}]")85 86            self.parent_conn.close()87            self.child_conn.close()88 89    def kill(self) -> None:90        # In Windows, the method will call `TerminateProcess` to kill the process.91        # In Unix, the method will send SIGKILL to the process.92        self.process.kill()93 94    def join(self) -> None:95        logger.info(f"Waiting for child process [{self.process.pid}]")96        self.process.join()97 98    @property99    def pid(self) -> int | None:100        return self.process.pid101 102 103class Multiprocess:104    def __init__(105        self,106        config: Config,107        target: Callable[[list[socket] | None], None],108        sockets: list[socket],109    ) -> None:110        self.config = config111        self.target = target112        self.sockets = sockets113 114        self.processes_num = config.workers115        self.processes: list[Process] = []116 117        self.should_exit = threading.Event()118 119        self.signal_queue: list[int] = []120        for sig in SIGNALS:121            signal.signal(sig, lambda sig, frame: self.signal_queue.append(sig))122 123    def init_processes(self) -> None:124        for _ in range(self.processes_num):125            process = Process(self.config, self.target, self.sockets)126            process.start()127            self.processes.append(process)128 129    def terminate_all(self) -> None:130        for process in self.processes:131            process.terminate()132 133    def join_all(self) -> None:134        for process in self.processes:135            process.join()136 137    def restart_all(self) -> None:138        for idx, process in enumerate(self.processes):139            process.terminate()140            process.join()141            new_process = Process(self.config, self.target, self.sockets)142            new_process.start()143            self.processes[idx] = new_process144 145    def run(self) -> None:146        message = f"Started parent process [{os.getpid()}]"147        color_message = "Started parent process [{}]".format(click.style(str(os.getpid()), fg="cyan", bold=True))148        logger.info(message, extra={"color_message": color_message})149 150        self.init_processes()151 152        while not self.should_exit.wait(0.5):153            self.handle_signals()154            self.keep_subprocess_alive()155 156        self.terminate_all()157        self.join_all()158 159        message = f"Stopping parent process [{os.getpid()}]"160        color_message = "Stopping parent process [{}]".format(click.style(str(os.getpid()), fg="cyan", bold=True))161        logger.info(message, extra={"color_message": color_message})162 163    def keep_subprocess_alive(self) -> None:164        if self.should_exit.is_set():165            return  # parent process is exiting, no need to keep subprocess alive166 167        for idx, process in enumerate(self.processes):168            if process.is_alive(timeout=self.config.timeout_worker_healthcheck):169                continue170 171            process.kill()  # process is hung, kill it172            process.join()173 174            if self.should_exit.is_set():175                return  # pragma: full coverage176 177            logger.info(f"Child process [{process.pid}] died")178            process = Process(self.config, self.target, self.sockets)179            process.start()180            self.processes[idx] = process181 182    def handle_signals(self) -> None:183        for sig in tuple(self.signal_queue):184            self.signal_queue.remove(sig)185            sig_name = SIGNALS[sig]186            sig_handler = getattr(self, f"handle_{sig_name.lower()}", None)187            if sig_handler is not None:188                sig_handler()189            else:  # pragma: no cover190                logger.debug(f"Received signal {sig_name}, but no handler is defined for it.")191 192    def handle_int(self) -> None:193        logger.info("Received SIGINT, exiting.")194        self.should_exit.set()195 196    def handle_term(self) -> None:197        logger.info("Received SIGTERM, exiting.")198        self.should_exit.set()199 200    def handle_break(self) -> None:  # pragma: py-not-win32201        logger.info("Received SIGBREAK, exiting.")202        self.should_exit.set()203 204    def handle_hup(self) -> None:  # pragma: py-win32205        logger.info("Received SIGHUP, restarting processes.")206        self.restart_all()207 208    def handle_ttin(self) -> None:  # pragma: py-win32209        logger.info("Received SIGTTIN, increasing the number of processes.")210        self.processes_num += 1211        process = Process(self.config, self.target, self.sockets)212        process.start()213        self.processes.append(process)214 215    def handle_ttou(self) -> None:  # pragma: py-win32216        logger.info("Received SIGTTOU, decreasing number of processes.")217        if self.processes_num <= 1:218            logger.info("Already reached one process, cannot decrease the number of processes anymore.")219            return220        self.processes_num -= 1221        process = self.processes.pop()222        process.terminate()223        process.join()224 
codekingpro/portable-devtools · Team Ai