update to gerrit version

Change-Id: Ifdaa28dd961a529cd9197c4c3639744f108b0a6a
This commit is contained in:
2023-07-05 17:26:33 +02:00
parent 4fe28363d3
commit 975593dd6b
12 changed files with 627 additions and 23 deletions

View File

@@ -401,10 +401,15 @@ class UniqueObject:
def merge_status(*args):
"""merge status
the status with biggest code wins
texts matching maximal code are joined with ', '
for combining stati of different mixins
- the status with biggest code wins
- texts matching maximal code are joined with ', '
- if texts already contain ', ', it is considered as composed by
individual texts and duplication is avoided. when commas are used
for other purposes, the behaviour might be surprising
"""
maxcode = max(a[0] for a in args)
merged = [a[1] for a in args if a[0] == maxcode and a[1]]
# use dict instead of set for preserving order
merged = {m: True for mm in merged for m in mm.split(', ')}
return maxcode, ', '.join(merged)

View File

@@ -640,6 +640,13 @@ class Module(HasAccessibles):
all parameters are polled once
"""
def shutdownModule(self):
"""called when the sever shuts down
any cleanup-work should be performed here, like closing threads and
saving data.
"""
def doPoll(self):
"""polls important parameters like value and status

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# This program is free software; you can redistribute it and/or modify it under
@@ -24,6 +23,7 @@
"""Define helpers"""
import os
import signal
import sys
from collections import OrderedDict
@@ -33,7 +33,6 @@ from frappy.dynamic import Pinata
from frappy.lib import formatException, generalConfig, get_class, mkthread
from frappy.lib.multievent import MultiEvent
from frappy.params import PREDEFINED_ACCESSIBLES
from frappy.modules import Attached
try:
from daemon import DaemonContext
@@ -106,6 +105,12 @@ class Server:
self._cfgfiles = cfgfiles
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):
if not DaemonContext:
@@ -127,17 +132,18 @@ class Server:
return f"{cls.__name__} class don't know how to handle option(s): {', '.join(options)}"
def restart_hook(self):
pass
"""Actions to be done on restart. May be overridden by a subclass."""
def run(self):
global systemd # pylint: disable=global-statement
while self._restart:
self._restart = False
try:
# TODO: make systemd notifications configurable
if systemd: # pylint: disable=used-before-assignment
if systemd:
systemd.daemon.notify("STATUS=initializing")
except Exception:
systemd = None # pylint: disable=redefined-outer-name
systemd = None
try:
self._processCfg()
if self._testonly:
@@ -156,13 +162,27 @@ class Server:
self.log.info('startup done, handling transport messages')
if systemd:
systemd.daemon.notify("READY=1\nSTATUS=accepting requests")
self.interface.serve_forever()
self.interface.server_close()
t = mkthread(self.interface.serve_forever)
# 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():
self.modules[name].shutdownModule()
if self._restart:
self.restart_hook()
self.log.info('restart')
else:
self.log.info('shut down')
self.log.info('restarting')
self.log.info('shut down')
def restart(self):
if not self._restart:
@@ -268,3 +288,41 @@ class Server:
# history_path = os.environ.get('ALTERNATIVE_HISTORY')
# if history_path:
# from frappy_<xx>.historywriter import ... etc.
def _getSortedModules(self):
"""Sort modules topologically by inverse dependency.
Example: if there is an IO device A and module B depends on it, then
the result will be [B, A].
Right now, if the dependency graph is not a DAG, we give up and return
the unvisited nodes to be dismantled at the end.
Taken from Introduction to Algorithms [CLRS].
"""
def go(name):
if name in done: # visiting a node
return True
if name in visited:
visited.add(name)
return False # cycle in dependencies -> fail
visited.add(name)
if name in unmarked:
unmarked.remove(name)
for module in self.modules[name].attachedModules.values():
res = go(module.name)
if not res:
return False
visited.remove(name)
done.add(name)
l.append(name)
return True
unmarked = set(self.modules.keys()) # unvisited nodes
visited = set() # visited in DFS, but not completed
done = set()
l = [] # list of sorted modules
while unmarked:
if not go(unmarked.pop()):
self.log.error('cyclical dependency between modules!')
return l[::-1] + list(visited) + list(unmarked)
return l[::-1]