moonraker: add handler for SIGTERM

This performs a graceful shutdown when SIGTERM is received.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Arksine 2021-02-23 17:25:29 -05:00
parent 54b1bd8b04
commit 958a0f3270
1 changed files with 43 additions and 8 deletions

View File

@ -13,6 +13,7 @@ import time
import socket import socket
import logging import logging
import json import json
import signal
import confighelper import confighelper
import utils import utils
import asyncio import asyncio
@ -49,6 +50,7 @@ class Server:
self.add_log_rollover_item('config', cfg_item) self.add_log_rollover_item('config', cfg_item)
self.host = config.get('host', "0.0.0.0") self.host = config.get('host', "0.0.0.0")
self.port = config.getint('port', 7125) self.port = config.getint('port', 7125)
self.exit_reason = ""
# Event initialization # Event initialization
self.events = {} self.events = {}
@ -63,6 +65,7 @@ class Server:
self.init_handle = None self.init_handle = None
self.init_attempts = 0 self.init_attempts = 0
self.klippy_state = "disconnected" self.klippy_state = "disconnected"
self.klippy_disconnect_evt = None
self.subscriptions = {} self.subscriptions = {}
self.failed_plugins = [] self.failed_plugins = []
@ -113,8 +116,14 @@ class Server:
f"Hostname: {hostname}") f"Hostname: {hostname}")
self.moonraker_app.listen(self.host, self.port) self.moonraker_app.listen(self.host, self.port)
self.server_running = True self.server_running = True
self.ioloop.spawn_callback(self._init_signals)
self.ioloop.spawn_callback(self._connect_klippy) self.ioloop.spawn_callback(self._connect_klippy)
def _init_signals(self):
aioloop = asyncio.get_event_loop()
aioloop.add_signal_handler(
signal.SIGTERM, self._handle_term_signal)
def add_log_rollover_item(self, name, item, log=True): def add_log_rollover_item(self, name, item, log=True):
if self.file_logger is not None: if self.file_logger is not None:
self.file_logger.set_rollover_info(name, item) self.file_logger.set_rollover_info(name, item)
@ -261,6 +270,8 @@ class Server:
self.ioloop.remove_timeout(self.init_handle) self.ioloop.remove_timeout(self.init_handle)
if self.server_running: if self.server_running:
self.ioloop.call_later(.25, self._connect_klippy) self.ioloop.call_later(.25, self._connect_klippy)
if self.klippy_disconnect_evt is not None:
self.klippy_disconnect_evt.set()
async def _initialize(self): async def _initialize(self):
if not self.server_running: if not self.server_running:
@ -463,17 +474,39 @@ class Server:
def remove_subscription(self, conn): def remove_subscription(self, conn):
self.subscriptions.pop(conn, None) self.subscriptions.pop(conn, None)
async def _stop_server(self): def _handle_term_signal(self):
logging.info(f"Exiting with signal SIGTERM")
self.ioloop.spawn_callback(self._stop_server, "terminate")
async def _stop_server(self, exit_reason="restart"):
self.server_running = False self.server_running = False
for name, plugin in self.plugins.items(): for name, plugin in self.plugins.items():
if hasattr(plugin, "close"): if hasattr(plugin, "close"):
ret = plugin.close() try:
if asyncio.iscoroutine(ret): ret = plugin.close()
await ret if asyncio.iscoroutine(ret):
self.klippy_connection.close() await ret
while self.klippy_state != "disconnected": except Exception:
await gen.sleep(.1) logging.exception(f"Error closing plugin: {name}")
await self.moonraker_app.close() try:
if self.klippy_connection.is_connected():
self.klippy_disconnect_evt = Event()
self.klippy_connection.close()
timeout = time.time() + 2.
await self.klippy_disconnect_evt.wait(timeout)
self.klippy_disconnect_evt = None
except Exception:
logging.exception("Klippy Disconnect Error")
# Sleep for 100ms to allow connected websockets
# to write out remaining data
await gen.sleep(.1)
try:
await self.moonraker_app.close()
except Exception:
logging.exception("Error Closing App")
self.exit_reason = exit_reason
aioloop = asyncio.get_event_loop()
aioloop.remove_signal_handler(signal.SIGTERM)
self.ioloop.stop() self.ioloop.stop()
async def _handle_server_restart(self, web_request): async def _handle_server_restart(self, web_request):
@ -639,6 +672,8 @@ def main():
logging.exception("Server Running Error") logging.exception("Server Running Error")
estatus = 1 estatus = 1
break break
if server.exit_reason == "terminate":
break
# Since we are running outside of the the server # Since we are running outside of the the server
# it is ok to use a blocking sleep here # it is ok to use a blocking sleep here
time.sleep(.5) time.sleep(.5)