improve handling of module init methods

- complain when super call is omitted (this is a common programming
  error in Mixins)
- redesign waiting mechanism for startup

+ rename MultiEvent method 'setfunc' to 'get_trigger'

Change-Id: Ica27a75597321f2571a604a7a55448cffb1bec5e
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27369
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
2021-12-22 15:19:21 +01:00
parent f13e29aad2
commit 8f7fb1e45b
14 changed files with 94 additions and 78 deletions

View File

@@ -110,7 +110,7 @@ class MultiEvent(threading.Event):
def waiting_for(self):
return set(event.name for event in self.events)
def setfunc(self, timeout=None, name=None):
def get_trigger(self, timeout=None, name=None):
"""create a new single event and return its set method
as a convenience method

View File

@@ -257,6 +257,9 @@ class Module(HasAccessibles):
self.name = name
self.valueCallbacks = {}
self.errorCallbacks = {}
self.earlyInitDone = False
self.initModuleDone = False
self.startModuleDone = False
errors = []
# handle module properties
@@ -523,11 +526,25 @@ class Module(HasAccessibles):
return False
def earlyInit(self):
# may be overriden in derived classes to init stuff
self.log.debug('empty %s.earlyInit()' % self.__class__.__name__)
"""initialise module with stuff to be done before all modules are created"""
self.earlyInitDone = True
def initModule(self):
self.log.debug('empty %s.initModule()' % self.__class__.__name__)
"""initialise module with stuff to be done after all modules are created"""
self.initModuleDone = True
def startModule(self, start_events):
"""runs after init of all modules
when a thread is started, a trigger function may signal that it
has finished its initial work
start_events.get_trigger(<timeout>) creates such a trigger and
registers it in the server for waiting
<timeout> defaults to 30 seconds
"""
if self.writeDict:
mkthread(self.writeInitParams, start_events.get_trigger())
self.startModuleDone = True
def pollOneParam(self, pname):
"""poll parameter <pname> with proper error handling"""
@@ -562,15 +579,6 @@ class Module(HasAccessibles):
if started_callback:
started_callback()
def startModule(self, started_callback):
"""runs after init of all modules
started_callback to be called when the thread spawned by startModule
has finished its initial work
might return a timeout value, if different from default
"""
mkthread(self.writeInitParams, started_callback)
class Readable(Module):
"""basic readable module"""
@@ -590,13 +598,13 @@ class Readable(Module):
pollinterval = Parameter('sleeptime between polls', FloatRange(0.1, 120),
default=5, readonly=False)
def startModule(self, started_callback):
def startModule(self, start_events):
"""start basic polling thread"""
if self.pollerClass and issubclass(self.pollerClass, BasicPoller):
# use basic poller for legacy code
mkthread(self.__pollThread, started_callback)
mkthread(self.__pollThread, start_events.get_trigger(timeout=30))
else:
super().startModule(started_callback)
super().startModule(start_events)
def __pollThread(self, started_callback):
while True:

View File

@@ -218,7 +218,7 @@ class Poller(PollerBase):
"""start poll loop
To be called as a thread. After all parameters are polled once first,
started_callback is called. To be called in Module.start_module.
started_callback is called. To be called in Module.startModule.
poll strategy:
Slow polls are performed with lower priority than regular and dynamic polls.

View File

@@ -144,10 +144,12 @@ class SecNode(Module):
uri = Property('uri of a SEC node', datatype=StringType())
def earlyInit(self):
super().earlyInit()
self.secnode = SecopClient(self.uri, self.log)
def startModule(self, started_callback):
self.secnode.spawn_connect(started_callback)
def startModule(self, start_events):
super().startModule(start_events)
self.secnode.spawn_connect(start_events.get_trigger())
@Command(StringType(), result=StringType())
def request(self, msg):

View File

@@ -27,13 +27,12 @@ import ast
import configparser
import os
import sys
import threading
import time
import traceback
from collections import OrderedDict
from secop.errors import ConfigError, SECoPError
from secop.lib import formatException, get_class, generalConfig
from secop.lib.multievent import MultiEvent
from secop.modules import Attached
from secop.params import PREDEFINED_ACCESSIBLES
@@ -267,6 +266,7 @@ class Server:
errors.append('error creating %s' % modname)
poll_table = dict()
missing_super = set()
# all objs created, now start them up and interconnect
for modname, modobj in self.modules.items():
self.log.info('registering module %r' % modname)
@@ -276,6 +276,9 @@ class Server:
modobj.pollerClass.add_to_table(poll_table, modobj)
# also call earlyInit on the modules
modobj.earlyInit()
if not modobj.earlyInitDone:
missing_super.add('%s was not called, probably missing super call'
% modobj.earlyInit.__qualname__)
# handle attached modules
for modname, modobj in self.modules.items():
@@ -291,11 +294,26 @@ class Server:
for modname, modobj in self.modules.items():
try:
modobj.initModule()
if not modobj.initModuleDone:
missing_super.add('%s was not called, probably missing super call'
% modobj.initModule.__qualname__)
except Exception as e:
if failure_traceback is None:
failure_traceback = traceback.format_exc()
errors.append('error initializing %s: %r' % (modname, e))
if self._testonly:
return
start_events = MultiEvent(default_timeout=30)
for modname, modobj in self.modules.items():
# startModule must return either a timeout value or None (default 30 sec)
start_events.name = 'module %s' % modname
modobj.startModule(start_events)
if not modobj.startModuleDone:
missing_super.add('%s was not called, probably missing super call'
% modobj.startModule.__qualname__)
errors.extend(missing_super)
if errors:
for errtxt in errors:
for line in errtxt.split('\n'):
@@ -307,23 +325,16 @@ class Server:
sys.stderr.write(failure_traceback)
sys.exit(1)
if self._testonly:
return
start_events = []
for modname, modobj in self.modules.items():
event = threading.Event()
# startModule must return either a timeout value or None (default 30 sec)
timeout = modobj.startModule(started_callback=event.set) or 30
start_events.append((time.time() + timeout, 'module %s' % modname, event))
for poller in poll_table.values():
event = threading.Event()
for (_, pollname) , poller in poll_table.items():
start_events.name = 'poller %s' % pollname
# poller.start must return either a timeout value or None (default 30 sec)
timeout = poller.start(started_callback=event.set) or 30
start_events.append((time.time() + timeout, repr(poller), event))
poller.start(start_events.get_trigger())
self.log.info('waiting for modules and pollers being started')
for deadline, name, event in sorted(start_events):
if not event.wait(timeout=max(0, deadline - time.time())):
self.log.info('WARNING: timeout when starting %s' % name)
start_events.name = None
if not start_events.wait():
# some timeout happened
for name in start_events.waiting_for():
self.log.warning('timeout when starting %s' % name)
self.log.info('all modules and pollers started')
history_path = os.environ.get('FRAPPY_HISTORY')
if history_path:

View File

@@ -60,6 +60,7 @@ class SimBase:
return object.__new__(type('SimBase_%s' % devname, (cls,), attrs))
def initModule(self):
super().initModule()
self._sim_thread = mkthread(self._sim)
def _sim(self):