Repository: encode/uvicorn

Issue:
Signal handlers
In the worker class


Discussion:

Resolving patch:
diff --git a/uvicorn/worker.py b/uvicorn/worker.py
--- a/uvicorn/worker.py
+++ b/uvicorn/worker.py
@@ -1,5 +1,7 @@
 import asyncio
 import functools
+import signal
+import sys
 
 import uvloop
 
@@ -19,6 +21,11 @@ class UvicornWorker(Worker):
     * `httptools` as the HTTP request parser.
     """
 
+    def __init__(self, *args, **kwargs):  # pragma: no cover
+        super().__init__(*args, **kwargs)
+        self.servers = []
+        self.exit_code = 0
+
     def init_process(self):
         # Close any existing event loop before setting a
         # new policy.
@@ -34,8 +41,45 @@ def init_process(self):
     def run(self):
         loop = asyncio.get_event_loop()
         loop.create_task(self.create_servers(loop))
-        loop.create_task(tick(loop, self.notify))
+        loop.create_task(self.tick(loop))
         loop.run_forever()
+        sys.exit(self.exit_code)
+
+    def init_signals(self):
+        # Set up signals through the event loop API.
+        loop = asyncio.get_event_loop()
+
+        loop.add_signal_handler(signal.SIGQUIT, self.handle_quit,
+                                signal.SIGQUIT, None)
+
+        loop.add_signal_handler(signal.SIGTERM, self.handle_exit,
+                                signal.SIGTERM, None)
+
+        loop.add_signal_handler(signal.SIGINT, self.handle_quit,
+                                signal.SIGINT, None)
+
+        loop.add_signal_handler(signal.SIGWINCH, self.handle_winch,
+                                signal.SIGWINCH, None)
+
+        loop.add_signal_handler(signal.SIGUSR1, self.handle_usr1,
+                                signal.SIGUSR1, None)
+
+        loop.add_signal_handler(signal.SIGABRT, self.handle_abort,
+                                signal.SIGABRT, None)
+
+        # Don't let SIGTERM and SIGUSR1 disturb active requests
+        # by interrupting system calls
+        signal.siginterrupt(signal.SIGTERM, False)
+        signal.siginterrupt(signal.SIGUSR1, False)
+
+    def handle_quit(self, sig, frame):
+        self.alive = False
+        self.cfg.worker_int(self)
+
+    def handle_abort(self, sig, frame):
+        self.alive = False
+        self.exit_code = 1
+        self.cfg.worker_abort(self)
 
     async def create_servers(self, loop):
         cfg = self.cfg
@@ -46,14 +90,19 @@ async def create_servers(self, loop):
                 http.HttpProtocol,
                 consumer=consumer, loop=loop, sock=sock, cfg=cfg
             )
-            await loop.create_server(protocol, sock=sock)
+            server = await loop.create_server(protocol, sock=sock)
+            self.servers.append(server)
 
+    async def tick(self, loop):
+        cycle = 0
+        while self.alive:
+            http.set_time_and_date()
+            cycle = (cycle + 1) % 10
+            if cycle == 0:
+                self.notify()
+            await asyncio.sleep(1)
 
-async def tick(loop, notify):
-    cycle = 0
-    while True:
-        http.set_time_and_date()
-        cycle = (cycle + 1) % 10
-        if cycle == 0:
-            notify()
-        await asyncio.sleep(1)
+        for server in self.servers:
+            server.close()
+            await server.wait_closed()
+        loop.stop()


Test patch: