Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve shutdown robustness when using --reload or multiprocessing #620

Merged
merged 12 commits into from
Apr 10, 2020
11 changes: 11 additions & 0 deletions uvicorn/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""
import multiprocessing
import os
import signal
import sys

multiprocessing.allow_connection_pickling()
Expand Down Expand Up @@ -59,3 +60,13 @@ def subprocess_started(config, target, sockets, stdin_fileno):

# Now we can call into `Server.run(sockets=sockets)`
target(sockets=sockets)


def shutdown_subprocess(pid):
"""
Helper to attempt cleanly shutting down a subprocess. May fail with an exception.

* pid - Process identifier.
"""
os.kill(pid, signal.SIGINT)
os.waitpid(pid, 0)
10 changes: 9 additions & 1 deletion uvicorn/supervisors/basereload.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import click

from uvicorn.subprocess import get_subprocess
from uvicorn.subprocess import get_subprocess, shutdown_subprocess

HANDLED_SIGNALS = (
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
Expand All @@ -22,11 +22,18 @@ def __init__(self, config, target, sockets):
self.sockets = sockets
self.should_exit = threading.Event()
self.pid = os.getpid()
self.process = None

def signal_handler(self, sig, frame):
"""
A signal handler that is registered with the parent process.
"""
if self.process is not None:
try:
shutdown_subprocess(self.process.pid)
except Exception as exc:
logger.error(f"Could not stop child process {self.process.pid}: {exc}")

self.should_exit.set()

def run(self):
Expand All @@ -36,6 +43,7 @@ def run(self):
self.restart()
self.shutdown()


euri10 marked this conversation as resolved.
Show resolved Hide resolved
def startup(self):
message = "Started reloader process [{}]".format(str(self.pid))
color_message = "Started reloader process [{}]".format(
Expand Down
9 changes: 8 additions & 1 deletion uvicorn/supervisors/multiprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import click

from uvicorn.subprocess import get_subprocess
from uvicorn.subprocess import get_subprocess, shutdown_subprocess

HANDLED_SIGNALS = (
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
Expand All @@ -28,6 +28,13 @@ def signal_handler(self, sig, frame):
"""
A signal handler that is registered with the parent process.
"""

for process in self.processes:
try:
shutdown_subprocess(process.pid)
except Exception as exc:
logger.error(f"Could not stop child process {process.pid}: {exc}")

self.should_exit.set()

def run(self):
Expand Down
3 changes: 2 additions & 1 deletion uvicorn/supervisors/watchdogreload.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import logging
from os import path

from uvicorn.supervisors.basereload import BaseReload
from watchdog.events import PatternMatchingEventHandler
from watchdog.observers import Observer

from uvicorn.supervisors.basereload import BaseReload
euri10 marked this conversation as resolved.
Show resolved Hide resolved

logger = logging.getLogger("uvicorn.error")


Expand Down