improve error handling on callbacks

errors in callback functions should be reported to log,
but not stop the callback chain

Change-Id: I4fc509b7121960ebe59e1ad4f4b4746dfb4d5ba3
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30950
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
2023-04-13 16:00:10 +02:00
parent 827d27ed59
commit ea5cdbbe44
2 changed files with 47 additions and 42 deletions

View File

@ -46,10 +46,10 @@ UPDATE_MESSAGES = {EVENTREPLY, READREPLY, WRITEREPLY, ERRORPREFIX + READREQUEST,
VERSIONFMT= re.compile(r'^[^,]*?ISSE[^,]*,SECoP,') VERSIONFMT= re.compile(r'^[^,]*?ISSE[^,]*,SECoP,')
class UNREGISTER:
"""a magic value, used a returned value in a callback
to indicate it has to be unregistered class UnregisterCallback(Exception):
"""raise in a callback to indicate it has to be unregistered
used to implement one shot callbacks used to implement one shot callbacks
""" """
@ -157,8 +157,8 @@ class CacheItem(tuple):
class ProxyClient: class ProxyClient:
"""common functionality for proxy clients""" """common functionality for proxy clients"""
CALLBACK_NAMES = ('updateEvent', 'updateItem', 'descriptiveDataChange', CALLBACK_NAMES = {'updateEvent', 'updateItem', 'descriptiveDataChange',
'nodeStateChange', 'unhandledMessage') 'nodeStateChange', 'unhandledMessage'}
online = False # connected or reconnecting since a short time online = False # connected or reconnecting since a short time
state = 'disconnected' # further possible values: 'connecting', 'reconnecting', 'connected' state = 'disconnected' # further possible values: 'connecting', 'reconnecting', 'connected'
log = None log = None
@ -181,42 +181,41 @@ class ProxyClient:
""" """
for cbfunc in args: for cbfunc in args:
kwds[cbfunc.__name__] = cbfunc kwds[cbfunc.__name__] = cbfunc
for cbname in self.CALLBACK_NAMES: for cbname, cbfunc in kwds.items():
cbfunc = kwds.pop(cbname, None) if cbname not in self.CALLBACK_NAMES:
if not cbfunc: raise TypeError(f"unknown callback: {', '.join(kwds)}")
continue
cbdict = self.callbacks[cbname]
cbdict[key].append(cbfunc)
# immediately call for some callback types # immediately call for some callback types
if cbname == 'updateItem': if cbname in ('updateItem', 'updateEvent'):
if key is None: if key is None: # case generic callback
for (mname, pname), data in self.cache.items(): cbargs = [(m, p, d) for (m, p), d in self.cache.items()]
cbfunc(mname, pname, data)
else: else:
data = self.cache.get(key, None) data = self.cache.get(key, None)
if data: if data: # case single parameter
cbfunc(*key, data) # case single parameter cbargs = [key + (data,)]
else: # case key = module else: # case key = module
for (mname, pname), data in self.cache.items(): cbargs = [(m, p, d) for (m, p), d in self.cache.items() if m == key]
if mname == key:
cbfunc(mname, pname, data) if cbname == 'updateEvent':
elif cbname == 'updateEvent': # expand entry argument to (value, timestamp, readerror)
if key is None: cbargs = [a[0:2] + a[2] for a in cbargs]
for (mname, pname), data in self.cache.items():
cbfunc(mname, pname, *data)
else:
data = self.cache.get(key, None)
if data:
cbfunc(*key, *data) # case single parameter
else: # case key = module
for (mname, pname), data in self.cache.items():
if mname == key:
cbfunc(mname, pname, *data)
elif cbname == 'nodeStateChange': elif cbname == 'nodeStateChange':
cbfunc(self.online, self.state) cbargs = [(self.online, self.state)]
if kwds: else:
raise TypeError(f"unknown callback: {', '.join(kwds)}") cbargs = []
do_append = True
for args in cbargs:
try:
cbfunc(*args)
except UnregisterCallback:
do_append = False
except Exception as e:
if self.log:
self.log.error('error %r calling %s%r', e, cbfunc.__name__, args)
if do_append:
self.callbacks[cbname][key].append(cbfunc)
def unregister_callback(self, key, *args, **kwds): def unregister_callback(self, key, *args, **kwds):
"""unregister a callback """unregister a callback
@ -240,20 +239,27 @@ class ProxyClient:
key=(<module name>, <parameter name): callbacks for specified parameter key=(<module name>, <parameter name): callbacks for specified parameter
""" """
cblist = self.callbacks[cbname].get(key, []) cblist = self.callbacks[cbname].get(key, [])
self.callbacks[cbname][key] = [cb for cb in cblist if cb(*args) is not UNREGISTER] for cbfunc in list(cblist):
try:
cbfunc(*args)
except UnregisterCallback:
cblist.remove(cbfunc)
except Exception as e:
if self.log:
self.log.error('error %r calling %s%r', e, cbfunc.__name__, args)
return bool(cblist) return bool(cblist)
def updateValue(self, module, param, value, timestamp, readerror): def updateValue(self, module, param, value, timestamp, readerror):
entry = CacheItem(value, timestamp, readerror, entry = CacheItem(value, timestamp, readerror,
self.modules[module]['parameters'][param]['datatype']) self.modules[module]['parameters'][param]['datatype'])
self.cache[(module, param)] = entry self.cache[(module, param)] = entry
self.callback(None, 'updateItem', module, param, entry) self.callback(None, 'updateItem', module, param, entry)
self.callback(module, 'updateItem', module, param, entry) self.callback(module, 'updateItem', module, param, entry)
self.callback((module, param), 'updateItem', module, param, entry) self.callback((module, param), 'updateItem', module, param, entry)
# TODO: change clients to use updateItem instead of updateEvent # TODO: change clients to use updateItem instead of updateEvent
self.callback(None, 'updateEvent', module, param, value, timestamp, readerror) self.callback(None, 'updateEvent', module, param, *entry)
self.callback(module, 'updateEvent', module, param, value, timestamp, readerror) self.callback(module, 'updateEvent', module, param, *entry)
self.callback((module, param), 'updateEvent', module, param, value, timestamp, readerror) self.callback((module, param), 'updateEvent', module, param, *entry)
class SecopClient(ProxyClient): class SecopClient(ProxyClient):

View File

@ -122,8 +122,7 @@ class Router(frappy.protocol.dispatcher.Dispatcher):
self.node_by_module[module] = node self.node_by_module[module] = node
self.nodes.append(node) self.nodes.append(node)
self.restart() self.restart()
return frappy.client.UNREGISTER raise frappy.client.UnregisterCallback()
return None
node.register_callback(None, nodeStateChange) node.register_callback(None, nodeStateChange)
logger.warning('can not connect to node %r', node.nodename) logger.warning('can not connect to node %r', node.nodename)