From d079bd15e71b43003f6c3937bad6364171905447 Mon Sep 17 00:00:00 2001 From: Alexander Zaft Date: Wed, 14 Jun 2023 09:35:09 +0200 Subject: [PATCH] Add shutdownModule function Add and call shutdownModule function, in order to allow modules to clean up. use shutdownModule instead of shutdown to avoid confusion with severs/dispatchers shutdown and to make it consistent with initModule etc. Try to resolve module dependencies Change-Id: I2a091bf74ecadc2395fcdf92c599f1c49eb120f5 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31339 Tested-by: Jenkins Automated Tests Reviewed-by: Enrico Faulhaber Reviewed-by: Alexander Zaft --- frappy/modules.py | 7 +++++++ frappy/server.py | 40 ++++++++++++++++++++++++++++++++++++++++ frappy_demo/cryo.py | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/frappy/modules.py b/frappy/modules.py index 9f7a03e8..cc7bd93e 100644 --- a/frappy/modules.py +++ b/frappy/modules.py @@ -639,6 +639,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 diff --git a/frappy/server.py b/frappy/server.py index bb1c7b02..e46ed027 100644 --- a/frappy/server.py +++ b/frappy/server.py @@ -159,6 +159,8 @@ class Server: systemd.daemon.notify("READY=1\nSTATUS=accepting requests") self.interface.serve_forever() self.interface.server_close() + for name in self._getSortedModules(): + self.modules[name].shutdownModule() if self._restart: self.restart_hook() self.log.info('restart') @@ -300,3 +302,41 @@ class Server: # history_path = os.environ.get('ALTERNATIVE_HISTORY') # if history_path: # from frappy_.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] diff --git a/frappy_demo/cryo.py b/frappy_demo/cryo.py index c9964227..b357cc41 100644 --- a/frappy_demo/cryo.py +++ b/frappy_demo/cryo.py @@ -354,7 +354,7 @@ class Cryostat(CryoBase): timestamp = t self.read_value() - def shutdown(self): + def shutdownModule(self): # should be called from server when the server is stopped self._stopflag = True if self._thread and self._thread.is_alive():