sanic/sanic/worker.py

211 lines
7.1 KiB
Python
Raw Normal View History

2017-03-12 08:19:34 +00:00
import os
2017-03-15 10:01:52 +00:00
import sys
import signal
2017-03-12 14:42:34 +00:00
import asyncio
import logging
import traceback
2017-03-12 14:42:34 +00:00
try:
import ssl
except ImportError:
ssl = None
2017-03-12 08:19:34 +00:00
try:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
pass
2017-03-12 08:19:34 +00:00
import gunicorn.workers.base as base
2017-03-16 03:55:10 +00:00
from sanic.server import trigger_events, serve, HttpProtocol, Signal
2017-03-12 14:42:34 +00:00
from sanic.websocket import WebSocketProtocol
2017-03-12 08:19:34 +00:00
class GunicornWorker(base.Worker):
2017-06-27 08:08:35 +01:00
http_protocol = HttpProtocol
websocket_protocol = WebSocketProtocol
2017-03-12 08:19:34 +00:00
def __init__(self, *args, **kw): # pragma: no cover
super().__init__(*args, **kw)
2017-03-12 14:42:34 +00:00
cfg = self.cfg
if cfg.is_ssl:
self.ssl_context = self._create_ssl_context(cfg)
else:
self.ssl_context = None
self.servers = {}
2017-03-12 14:42:34 +00:00
self.connections = set()
2017-03-15 10:01:52 +00:00
self.exit_code = 0
2017-03-16 03:55:10 +00:00
self.signal = Signal()
2017-03-12 08:19:34 +00:00
def init_process(self):
# create new event_loop after fork
asyncio.get_event_loop().close()
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
super().init_process()
def run(self):
2017-03-16 09:47:01 +00:00
is_debug = self.log.loglevel == logging.DEBUG
2017-06-27 08:08:35 +01:00
protocol = (
self.websocket_protocol if self.app.callable.websocket_enabled
else self.http_protocol)
2017-03-16 09:47:01 +00:00
self._server_settings = self.app.callable._helper(
loop=self.loop,
debug=is_debug,
protocol=protocol,
ssl=self.ssl_context,
run_async=True)
self._server_settings['signal'] = self.signal
2017-03-16 09:47:01 +00:00
self._server_settings.pop('sock')
trigger_events(self._server_settings.get('before_start', []),
self.loop)
self._server_settings['before_start'] = ()
2017-03-12 08:19:34 +00:00
2017-03-16 09:47:01 +00:00
self._runner = asyncio.ensure_future(self._run(), loop=self.loop)
2017-03-12 08:19:34 +00:00
try:
self.loop.run_until_complete(self._runner)
2017-03-14 16:07:42 +00:00
self.app.callable.is_running = True
2017-03-13 09:41:47 +00:00
trigger_events(self._server_settings.get('after_start', []),
self.loop)
self.loop.run_until_complete(self._check_alive())
2017-03-20 06:27:02 +00:00
trigger_events(self._server_settings.get('before_stop', []),
self.loop)
self.loop.run_until_complete(self.close())
2017-10-26 05:58:31 +01:00
except BaseException:
traceback.print_exc()
2017-03-12 08:19:34 +00:00
finally:
try:
trigger_events(self._server_settings.get('after_stop', []),
self.loop)
2017-10-26 05:58:31 +01:00
except BaseException:
traceback.print_exc()
finally:
self.loop.close()
2017-03-12 08:19:34 +00:00
2017-03-15 10:01:52 +00:00
sys.exit(self.exit_code)
2017-03-12 08:19:34 +00:00
async def close(self):
2017-03-12 14:42:34 +00:00
if self.servers:
# stop accepting connections
self.log.info("Stopping server: %s, connections: %s",
self.pid, len(self.connections))
for server in self.servers:
server.close()
await server.wait_closed()
self.servers.clear()
# prepare connections for closing
2017-03-16 03:55:10 +00:00
self.signal.stopped = True
2017-03-12 14:42:34 +00:00
for conn in self.connections:
conn.close_if_idle()
# gracefully shutdown timeout
start_shutdown = 0
graceful_shutdown_timeout = self.cfg.graceful_timeout
2017-06-25 18:22:40 +01:00
while self.connections and \
(start_shutdown < graceful_shutdown_timeout):
2017-03-12 14:42:34 +00:00
await asyncio.sleep(0.1)
start_shutdown = start_shutdown + 0.1
# Force close non-idle connection after waiting for
# graceful_shutdown_timeout
coros = []
for conn in self.connections:
if hasattr(conn, "websocket") and conn.websocket:
2017-11-26 05:14:18 +00:00
coros.append(
conn.websocket.close_connection()
2017-11-26 05:14:18 +00:00
)
else:
conn.close()
_shutdown = asyncio.gather(*coros, loop=self.loop)
await _shutdown
2017-03-12 08:19:34 +00:00
async def _run(self):
for sock in self.sockets:
state = dict(requests_count=0)
self._server_settings["host"] = None
self._server_settings["port"] = None
server = await serve(
2017-03-12 14:42:34 +00:00
sock=sock,
connections=self.connections,
state=state,
2017-03-12 14:42:34 +00:00
**self._server_settings
)
self.servers[server] = state
2017-03-12 08:19:34 +00:00
async def _check_alive(self):
2017-03-12 08:19:34 +00:00
# If our parent changed then we shut down.
pid = os.getpid()
try:
while self.alive:
self.notify()
req_count = sum(
self.servers[srv]["requests_count"] for srv in self.servers
)
if self.max_requests and req_count > self.max_requests:
self.alive = False
2017-10-06 14:53:30 +01:00
self.log.info("Max requests exceeded, shutting down: %s",
self)
elif pid == os.getpid() and self.ppid != os.getppid():
2017-03-12 08:19:34 +00:00
self.alive = False
self.log.info("Parent changed, shutting down: %s", self)
else:
await asyncio.sleep(1.0, loop=self.loop)
except (Exception, BaseException, GeneratorExit, KeyboardInterrupt):
pass
2017-03-12 14:42:34 +00:00
@staticmethod
def _create_ssl_context(cfg):
""" Creates SSLContext instance for usage in asyncio.create_server.
See ssl.SSLSocket.__init__ for more details.
"""
ctx = ssl.SSLContext(cfg.ssl_version)
ctx.load_cert_chain(cfg.certfile, cfg.keyfile)
ctx.verify_mode = cfg.cert_reqs
if cfg.ca_certs:
ctx.load_verify_locations(cfg.ca_certs)
if cfg.ciphers:
ctx.set_ciphers(cfg.ciphers)
return ctx
2017-03-15 10:01:52 +00:00
def init_signals(self):
# Set up signals through the event loop API.
self.loop.add_signal_handler(signal.SIGQUIT, self.handle_quit,
signal.SIGQUIT, None)
self.loop.add_signal_handler(signal.SIGTERM, self.handle_exit,
signal.SIGTERM, None)
self.loop.add_signal_handler(signal.SIGINT, self.handle_quit,
signal.SIGINT, None)
self.loop.add_signal_handler(signal.SIGWINCH, self.handle_winch,
signal.SIGWINCH, None)
self.loop.add_signal_handler(signal.SIGUSR1, self.handle_usr1,
signal.SIGUSR1, None)
self.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.app.callable.is_running = False
2017-03-15 10:01:52 +00:00
self.cfg.worker_int(self)
def handle_abort(self, sig, frame):
self.alive = False
self.exit_code = 1
self.cfg.worker_abort(self)
sys.exit(1)