1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
"""Graceful shutdown helpers shared by signal handlers and the settings UI."""
from __future__ import annotations
import os
import signal
import sys
import threading
from collections.abc import Callable
from fastapi import FastAPI
def begin_shutdown(app: FastAPI) -> None:
"""Wake long-poll waiters; safe to call multiple times."""
app.state.shutting_down = True
shutdown_event = getattr(app.state, "shutdown_event", None)
if shutdown_event is not None:
shutdown_event.set()
def request_graceful_shutdown(app: FastAPI) -> None:
"""Begin graceful shutdown and signal the server process when supported."""
begin_shutdown(app)
if getattr(app.state, "can_signal_shutdown", False):
os.kill(os.getpid(), signal.SIGTERM)
def install_graceful_shutdown_wakeup(app: FastAPI) -> bool:
"""Chain OS signals so long-poll waiters wake before uvicorn shuts down.
Returns whether SIGTERM may be sent to this process later (e.g. from the
settings shutdown button). Skipped off the main thread.
"""
if threading.current_thread() is not threading.main_thread():
app.state.can_signal_shutdown = False
return False
signals: tuple[int, ...] = (signal.SIGINT, signal.SIGTERM)
if sys.platform == "win32":
signals = signals + (signal.SIGBREAK,)
installed = False
for sig in signals:
def make_chain(
previous: Callable[[int, object | None], object] | int | None,
) -> Callable[[int, object | None], None]:
def handler(signum: int, frame: object | None) -> None:
begin_shutdown(app)
if callable(previous) and previous not in (signal.SIG_DFL, signal.SIG_IGN):
previous(signum, frame)
return handler
try:
prev = signal.getsignal(sig)
signal.signal(sig, make_chain(prev))
installed = True
except ValueError:
continue
app.state.can_signal_shutdown = installed
return installed