From 33388e6ae6eeb0903666db48b632b4378303141e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=2E=20K=C3=A4rkk=C3=A4inen?= Date: Wed, 18 Mar 2020 07:24:24 -0700 Subject: [PATCH] Fix Ctrl+C on Windows. --- sanic/compat.py | 25 +++++++++++++++++++++++++ sanic/server.py | 16 ++++++---------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/sanic/compat.py b/sanic/compat.py index ebf25bb0..403eeb71 100644 --- a/sanic/compat.py +++ b/sanic/compat.py @@ -1,3 +1,6 @@ +import asyncio +import signal + from sys import argv from multidict import CIMultiDict # type: ignore @@ -23,3 +26,25 @@ else: async def open_async(file, mode="r", **kwargs): return aio_open(file, mode, **kwargs) + + +def ctrlc_workaround_for_windows(app): + async def stay_active(app): + """Frequently poll asyncio to allow *receiving* any signals in Python""" + while not die: + # If someone else stopped the app, just exit + if asyncio.get_running_loop()._stopping: + return + await asyncio.sleep(0.1) + # Can't be called from signal handler, so call it from here + app.stop() + + def ctrlc_handler(sig, frame): + nonlocal die + if die: + raise KeyboardInterrupt("Non-graceful Ctrl+C") + die = True + + die = False + signal.signal(signal.SIGINT, ctrlc_handler) + app.add_task(stay_active) diff --git a/sanic/server.py b/sanic/server.py index 4251d674..5b607b04 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -15,7 +15,7 @@ from time import time from httptools import HttpRequestParser # type: ignore from httptools.parser.errors import HttpParserError # type: ignore -from sanic.compat import Header +from sanic.compat import Header, ctrlc_workaround_for_windows from sanic.exceptions import ( HeaderExpectationFailed, InvalidUsage, @@ -929,15 +929,11 @@ def serve( # Register signals for graceful termination if register_sys_signals: - _singals = (SIGTERM,) if run_multiple else (SIGINT, SIGTERM) - for _signal in _singals: - try: - loop.add_signal_handler(_signal, loop.stop) - except NotImplementedError: - logger.warning( - "Sanic tried to use loop.add_signal_handler " - "but it is not implemented on this platform." - ) + if os.name == "nt": + ctrlc_workaround_for_windows(app) + else: + for _signal in [SIGTERM] if run_multiple else [SIGINT, SIGTERM]: + loop.add_signal_handler(_signal, app.stop) pid = os.getpid() try: logger.info("Starting worker [%s]", pid)