server: Add signal handling
previously, no cleanup would be performed, SIGINTs being catched above the server.run() function + signal handler for SIGINT, SIGTERM * put serve_forever in its own thread, to be able to call shutdown() on the server Change-Id: Ia5c021f3d6fd60ff2b9012c10e30715f93104977 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31340 Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de> Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
This commit is contained in:
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify it under
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
@ -23,8 +22,8 @@
|
|||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
|
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import sys
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
# Add import path for inplace usage
|
# Add import path for inplace usage
|
||||||
@ -61,8 +60,9 @@ def parseArgv(argv):
|
|||||||
action='store',
|
action='store',
|
||||||
help="comma separated list of cfg files,\n"
|
help="comma separated list of cfg files,\n"
|
||||||
"defaults to <name_of_the_instance>.\n"
|
"defaults to <name_of_the_instance>.\n"
|
||||||
"cfgfiles given without '.cfg' extension are searched in the configuration directory, "
|
"cfgfiles given without '.cfg' extension are searched"
|
||||||
"else they are treated as path names",
|
" in the configuration directory,"
|
||||||
|
" else they are treated as path names",
|
||||||
default=None)
|
default=None)
|
||||||
parser.add_argument('-g',
|
parser.add_argument('-g',
|
||||||
'--gencfg',
|
'--gencfg',
|
||||||
@ -96,15 +96,13 @@ def main(argv=None):
|
|||||||
generalConfig.init(args.gencfg)
|
generalConfig.init(args.gencfg)
|
||||||
logger.init(loglevel)
|
logger.init(loglevel)
|
||||||
|
|
||||||
srv = Server(args.name, logger.log, cfgfiles=args.cfgfiles, interface=args.port, testonly=args.test)
|
srv = Server(args.name, logger.log, cfgfiles=args.cfgfiles,
|
||||||
|
interface=args.port, testonly=args.test)
|
||||||
|
|
||||||
if args.daemonize:
|
if args.daemonize:
|
||||||
srv.start()
|
srv.start()
|
||||||
else:
|
else:
|
||||||
try:
|
|
||||||
srv.run()
|
srv.run()
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify it under
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
@ -24,16 +23,17 @@
|
|||||||
"""Define helpers"""
|
"""Define helpers"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from frappy.errors import ConfigError, SECoPError
|
|
||||||
from frappy.lib import formatException, get_class, generalConfig
|
|
||||||
from frappy.lib.multievent import MultiEvent
|
|
||||||
from frappy.params import PREDEFINED_ACCESSIBLES
|
|
||||||
from frappy.modules import Attached
|
|
||||||
from frappy.config import load_config
|
from frappy.config import load_config
|
||||||
|
from frappy.errors import ConfigError, SECoPError
|
||||||
|
from frappy.lib import formatException, generalConfig, get_class, mkthread
|
||||||
|
from frappy.lib.multievent import MultiEvent
|
||||||
|
from frappy.modules import Attached
|
||||||
|
from frappy.params import PREDEFINED_ACCESSIBLES
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from daemon import DaemonContext
|
from daemon import DaemonContext
|
||||||
@ -106,6 +106,12 @@ class Server:
|
|||||||
|
|
||||||
self._cfgfiles = cfgfiles
|
self._cfgfiles = cfgfiles
|
||||||
self._pidfile = os.path.join(generalConfig.piddir, name + '.pid')
|
self._pidfile = os.path.join(generalConfig.piddir, name + '.pid')
|
||||||
|
signal.signal(signal.SIGINT, self.signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, self.signal_handler)
|
||||||
|
|
||||||
|
def signal_handler(self, _num, _frame):
|
||||||
|
if hasattr(self, 'interface') and self.interface:
|
||||||
|
self.shutdown()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
if not DaemonContext:
|
if not DaemonContext:
|
||||||
@ -127,7 +133,7 @@ class Server:
|
|||||||
return f"{cls.__name__} class don't know how to handle option(s): {', '.join(options)}"
|
return f"{cls.__name__} class don't know how to handle option(s): {', '.join(options)}"
|
||||||
|
|
||||||
def restart_hook(self):
|
def restart_hook(self):
|
||||||
pass
|
"""Actions to be done on restart. May be overridden by a subclass."""
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
global systemd # pylint: disable=global-statement
|
global systemd # pylint: disable=global-statement
|
||||||
@ -157,14 +163,26 @@ class Server:
|
|||||||
self.log.info('startup done, handling transport messages')
|
self.log.info('startup done, handling transport messages')
|
||||||
if systemd:
|
if systemd:
|
||||||
systemd.daemon.notify("READY=1\nSTATUS=accepting requests")
|
systemd.daemon.notify("READY=1\nSTATUS=accepting requests")
|
||||||
self.interface.serve_forever()
|
t = mkthread(self.interface.serve_forever)
|
||||||
self.interface.server_close()
|
# we wait here on the thread finishing, which means we got a
|
||||||
|
# signal to shut down or an exception was raised
|
||||||
|
# TODO: get the exception (and re-raise?)
|
||||||
|
t.join()
|
||||||
|
self.interface = None # fine due to the semantics of 'with'
|
||||||
|
# server_close() called by 'with'
|
||||||
|
|
||||||
|
self.log.info(f'stopped listenning, cleaning up'
|
||||||
|
f' {len(self.modules)} modules')
|
||||||
|
# if systemd:
|
||||||
|
# if self._restart:
|
||||||
|
# systemd.daemon.notify('RELOADING=1')
|
||||||
|
# else:
|
||||||
|
# systemd.daemon.notify('STOPPING=1')
|
||||||
for name in self._getSortedModules():
|
for name in self._getSortedModules():
|
||||||
self.modules[name].shutdownModule()
|
self.modules[name].shutdownModule()
|
||||||
if self._restart:
|
if self._restart:
|
||||||
self.restart_hook()
|
self.restart_hook()
|
||||||
self.log.info('restart')
|
self.log.info('restarting')
|
||||||
else:
|
|
||||||
self.log.info('shut down')
|
self.log.info('shut down')
|
||||||
|
|
||||||
def restart(self):
|
def restart(self):
|
||||||
|
Reference in New Issue
Block a user