diff --git a/secop/modules.py b/secop/modules.py index 2032717..a56940e 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -25,6 +25,7 @@ import time from queue import Queue, Empty +import threading from collections import OrderedDict from functools import wraps @@ -136,16 +137,17 @@ class HasAccessibles(HasProperties): @wraps(rfunc) # handles __wrapped__ and __doc__ def new_rfunc(self, pname=pname, rfunc=rfunc): - try: - value = rfunc(self) - self.log.debug("read_%s returned %r", pname, value) - except Exception as e: - self.log.debug("read_%s failed with %r", pname, e) - raise - if value is Done: - return getattr(self, pname) - setattr(self, pname, value) # important! trigger the setter - return value + with self.accessLock: + try: + value = rfunc(self) + self.log.debug("read_%s returned %r", pname, value) + except Exception as e: + self.log.debug("read_%s failed with %r", pname, e) + raise + if value is Done: + return getattr(self, pname) + setattr(self, pname, value) # important! trigger the setter + return value new_rfunc.poll = getattr(rfunc, 'poll', True) else: @@ -175,18 +177,19 @@ class HasAccessibles(HasProperties): @wraps(wfunc) # handles __wrapped__ and __doc__ def new_wfunc(self, value, pname=pname, wfunc=wfunc): - pobj = self.accessibles[pname] - self.log.debug('validate %r for %r', value, pname) - # we do not need to handle errors here, we do not - # want to make a parameter invalid, when a write failed - new_value = pobj.datatype(value) - new_value = wfunc(self, new_value) - self.log.debug('write_%s(%r) returned %r', pname, value, new_value) - if new_value is Done: - # setattr(self, pname, getattr(self, pname)) - return getattr(self, pname) - setattr(self, pname, new_value) # important! trigger the setter - return new_value + with self.accessLock: + pobj = self.accessibles[pname] + self.log.debug('validate %r for %r', value, pname) + # we do not need to handle errors here, we do not + # want to make a parameter invalid, when a write failed + new_value = pobj.datatype(value) + new_value = wfunc(self, new_value) + self.log.debug('write_%s(%r) returned %r', pname, value, new_value) + if new_value is Done: + # setattr(self, pname, getattr(self, pname)) + return getattr(self, pname) + setattr(self, pname, new_value) # important! trigger the setter + return new_value else: def new_wfunc(self, value, pname=pname): @@ -293,6 +296,7 @@ class Module(HasAccessibles): self.startModuleDone = False self.remoteLogHandler = None self.changePollinterval = Queue() # used for waiting between polls and transmit info to the thread + self.accessLock = threading.RLock() errors = [] # handle module properties @@ -468,45 +472,46 @@ class Module(HasAccessibles): def announceUpdate(self, pname, value=None, err=None, timestamp=None): """announce a changed value or readerror""" - # TODO: remove readerror 'property' and replace value with exception - pobj = self.parameters[pname] - timestamp = timestamp or time.time() - changed = pobj.value != value - try: - # store the value even in case of error - pobj.value = pobj.datatype(value) - except Exception as e: - if isinstance(e, DiscouragedConversion): - if DiscouragedConversion.log_message: - self.log.error(str(e)) - self.log.error('you may disable this behaviour by running the server with --relaxed') - DiscouragedConversion.log_message = False - if not err: # do not overwrite given error - err = e - if err: - err = secop_error(err) - if str(err) == str(pobj.readerror): - return # do call updates for repeated errors - elif not changed and timestamp < (pobj.timestamp or 0) + self.omit_unchanged_within: - # no change within short time -> omit - return - pobj.timestamp = timestamp or time.time() - pobj.readerror = err - if pobj.export: - self.DISPATCHER.announce_update(self.name, pname, pobj) - if err: - callbacks = self.errorCallbacks - arg = err - else: - callbacks = self.valueCallbacks - arg = value - cblist = callbacks[pname] - for cb in cblist: + with self.accessLock: + # TODO: remove readerror 'property' and replace value with exception + pobj = self.parameters[pname] + timestamp = timestamp or time.time() + changed = pobj.value != value try: - cb(arg) - except Exception: - # print(formatExtendedTraceback()) - pass + # store the value even in case of error + pobj.value = pobj.datatype(value) + except Exception as e: + if isinstance(e, DiscouragedConversion): + if DiscouragedConversion.log_message: + self.log.error(str(e)) + self.log.error('you may disable this behaviour by running the server with --relaxed') + DiscouragedConversion.log_message = False + if not err: # do not overwrite given error + err = e + if err: + err = secop_error(err) + if str(err) == str(pobj.readerror): + return # no updates for repeated errors + elif not changed and timestamp < (pobj.timestamp or 0) + self.omit_unchanged_within: + # no change within short time -> omit + return + pobj.timestamp = timestamp or time.time() + pobj.readerror = err + if pobj.export: + self.DISPATCHER.announce_update(self.name, pname, pobj) + if err: + callbacks = self.errorCallbacks + arg = err + else: + callbacks = self.valueCallbacks + arg = value + cblist = callbacks[pname] + for cb in cblist: + try: + cb(arg) + except Exception: + # print(formatExtendedTraceback()) + pass def registerCallbacks(self, modobj, autoupdate=()): """register callbacks to another module diff --git a/secop/rwhandler.py b/secop/rwhandler.py index 1727adc..76aa803 100644 --- a/secop/rwhandler.py +++ b/secop/rwhandler.py @@ -127,11 +127,12 @@ class ReadHandler(Handler): def wrap(self, key): def method(module, pname=key, func=self.func): - value = func(module, pname) - if value is Done: - return getattr(module, pname) - setattr(module, pname, value) - return value + with module.accessLock: + value = func(module, pname) + if value is Done: + return getattr(module, pname) + setattr(module, pname, value) + return value return wraps(self.func)(method) @@ -148,10 +149,11 @@ class CommonReadHandler(ReadHandler): def wrap(self, key): def method(module, pname=key, func=self.func): - ret = func(module) - if ret not in (None, Done): - raise ProgrammingError('a method wrapped with CommonReadHandler must not return any value') - return getattr(module, pname) + with module.accessLock: + ret = func(module) + if ret not in (None, Done): + raise ProgrammingError('a method wrapped with CommonReadHandler must not return any value') + return getattr(module, pname) method = wraps(self.func)(method) method.poll = self.poll and getattr(method, 'poll', True) if key == self.first_key else False @@ -165,10 +167,11 @@ class WriteHandler(Handler): def wrap(self, key): @wraps(self.func) def method(module, value, pname=key, func=self.func): - value = func(module, pname, value) - if value is not Done: - setattr(module, pname, value) - return value + with module.accessLock: + value = func(module, pname, value) + if value is not Done: + setattr(module, pname, value) + return value return method @@ -201,13 +204,14 @@ class CommonWriteHandler(WriteHandler): def wrap(self, key): @wraps(self.func) def method(module, value, pname=key, func=self.func): - values = WriteParameters(module) - values[pname] = value - ret = func(module, values) - if ret not in (None, Done): - raise ProgrammingError('a method wrapped with CommonWriteHandler must not return any value') - # remove pname from writeDict. this was not removed in WriteParameters, as it was not missing - module.writeDict.pop(pname, None) + with module.accessLock: + values = WriteParameters(module) + values[pname] = value + ret = func(module, values) + if ret not in (None, Done): + raise ProgrammingError('a method wrapped with CommonWriteHandler must not return any value') + # remove pname from writeDict. this was not removed in WriteParameters, as it was not missing + module.writeDict.pop(pname, None) return method diff --git a/test/conftest.py b/test/conftest.py index 6289b9b..6e1d2b8 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,6 +1,12 @@ # content of conftest.py import pytest +from secop.lib import generalConfig + + +@pytest.fixture(scope="session", autouse=True) +def general_config(): + generalConfig.testinit() @pytest.fixture(scope="module")