diff --git a/sanic/sanic.py b/sanic/sanic.py index e5873236..6d855fc2 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -16,7 +16,7 @@ from .router import Router from .server import serve, HttpProtocol from .static import register as static_register from .exceptions import ServerError -from socket import socket +from socket import socket, SOL_SOCKET, SO_REUSEADDR from os import set_inheritable @@ -244,7 +244,8 @@ class Sanic: def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None, after_start=None, before_stop=None, after_stop=None, sock=None, - workers=1, loop=None, protocol=HttpProtocol, backlog=100): + workers=1, loop=None, protocol=HttpProtocol, backlog=100, + stop_event=None): """ Runs the HTTP Server and listens until keyboard interrupt or term signal. On termination, drains connections before closing. @@ -320,7 +321,7 @@ class Sanic: else: log.info('Spinning up {} workers...'.format(workers)) - self.serve_multiple(server_settings, workers) + self.serve_multiple(server_settings, workers, stop_event) except Exception as e: log.exception( @@ -335,7 +336,7 @@ class Sanic: get_event_loop().stop() @staticmethod - def serve_multiple(server_settings, workers, stop_event=None): + def serve_multiple(self, server_settings, workers, stop_event=None): """ Starts multiple server processes simultaneously. Stops on interrupt and terminate signals, and drains connections when complete. @@ -353,6 +354,7 @@ class Sanic: signal(SIGTERM, lambda s, f: stop_event.set()) sock = socket() + #sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind((server_settings['host'], server_settings['port'])) set_inheritable(sock.fileno(), True) server_settings['sock'] = sock @@ -362,10 +364,12 @@ class Sanic: processes = [] for _ in range(workers): process = Process(target=serve, kwargs=server_settings) + process.daemon = True process.start() processes.append(process) for process in processes: process.terminate() + for process in processes: process.join() diff --git a/tests/test_multiprocessing.py b/tests/test_multiprocessing.py index cc967ef1..52a68fd1 100644 --- a/tests/test_multiprocessing.py +++ b/tests/test_multiprocessing.py @@ -1,9 +1,13 @@ from multiprocessing import Array, Event, Process from time import sleep, time from ujson import loads as json_loads +from asyncio import get_event_loop +from os import killpg, kill +from signal import SIGUSR1, signal, SIGINT, SIGTERM, SIGKILL from sanic import Sanic -from sanic.response import json +from sanic.response import json, text +from sanic.exceptions import Handler from sanic.utils import local_request, HOST, PORT @@ -50,11 +54,12 @@ def skip_test_multiprocessing(): except: raise ValueError("Expected JSON response but got '{}'".format(response)) + stop_event.set() assert results.get('test') == True def test_drain_connections(): - app = Sanic('test_json') + app = Sanic('test_stop') @app.route('/') async def handler(request): @@ -75,3 +80,31 @@ def test_drain_connections(): end = time() assert end - start < 0.05 + +def skip_test_workers(): + app = Sanic('test_workers') + + @app.route('/') + async def handler(request): + return text('ok') + + stop_event = Event() + + d = [] + async def after_start(*args, **kwargs): + http_response = await local_request('get', '/') + d.append(http_response.text) + stop_event.set() + + p = Process(target=app.run, kwargs={'host':HOST, + 'port':PORT, + 'after_start': after_start, + 'workers':2, + 'stop_event':stop_event}) + p.start() + loop = get_event_loop() + loop.run_until_complete(after_start()) + #killpg(p.pid, SIGUSR1) + kill(p.pid, SIGUSR1) + + assert d[0] == 1