avoid race conditions in read_*/write_* methods

using one RLock per Module
+ init generalConfig for all tests

Change-Id: I88db6cacdb4aaac2ecd56644ccd6a3e5fd2d1cf2
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28005
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
zolliker 2022-03-03 15:42:29 +01:00
parent 9858973ba1
commit 16a9550080
3 changed files with 95 additions and 80 deletions

View File

@ -25,6 +25,7 @@
import time import time
from queue import Queue, Empty from queue import Queue, Empty
import threading
from collections import OrderedDict from collections import OrderedDict
from functools import wraps from functools import wraps
@ -136,6 +137,7 @@ class HasAccessibles(HasProperties):
@wraps(rfunc) # handles __wrapped__ and __doc__ @wraps(rfunc) # handles __wrapped__ and __doc__
def new_rfunc(self, pname=pname, rfunc=rfunc): def new_rfunc(self, pname=pname, rfunc=rfunc):
with self.accessLock:
try: try:
value = rfunc(self) value = rfunc(self)
self.log.debug("read_%s returned %r", pname, value) self.log.debug("read_%s returned %r", pname, value)
@ -175,6 +177,7 @@ class HasAccessibles(HasProperties):
@wraps(wfunc) # handles __wrapped__ and __doc__ @wraps(wfunc) # handles __wrapped__ and __doc__
def new_wfunc(self, value, pname=pname, wfunc=wfunc): def new_wfunc(self, value, pname=pname, wfunc=wfunc):
with self.accessLock:
pobj = self.accessibles[pname] pobj = self.accessibles[pname]
self.log.debug('validate %r for %r', value, pname) self.log.debug('validate %r for %r', value, pname)
# we do not need to handle errors here, we do not # we do not need to handle errors here, we do not
@ -293,6 +296,7 @@ class Module(HasAccessibles):
self.startModuleDone = False self.startModuleDone = False
self.remoteLogHandler = None self.remoteLogHandler = None
self.changePollinterval = Queue() # used for waiting between polls and transmit info to the thread self.changePollinterval = Queue() # used for waiting between polls and transmit info to the thread
self.accessLock = threading.RLock()
errors = [] errors = []
# handle module properties # handle module properties
@ -468,6 +472,7 @@ class Module(HasAccessibles):
def announceUpdate(self, pname, value=None, err=None, timestamp=None): def announceUpdate(self, pname, value=None, err=None, timestamp=None):
"""announce a changed value or readerror""" """announce a changed value or readerror"""
with self.accessLock:
# TODO: remove readerror 'property' and replace value with exception # TODO: remove readerror 'property' and replace value with exception
pobj = self.parameters[pname] pobj = self.parameters[pname]
timestamp = timestamp or time.time() timestamp = timestamp or time.time()
@ -486,7 +491,7 @@ class Module(HasAccessibles):
if err: if err:
err = secop_error(err) err = secop_error(err)
if str(err) == str(pobj.readerror): if str(err) == str(pobj.readerror):
return # do call updates for repeated errors return # no updates for repeated errors
elif not changed and timestamp < (pobj.timestamp or 0) + self.omit_unchanged_within: elif not changed and timestamp < (pobj.timestamp or 0) + self.omit_unchanged_within:
# no change within short time -> omit # no change within short time -> omit
return return

View File

@ -127,6 +127,7 @@ class ReadHandler(Handler):
def wrap(self, key): def wrap(self, key):
def method(module, pname=key, func=self.func): def method(module, pname=key, func=self.func):
with module.accessLock:
value = func(module, pname) value = func(module, pname)
if value is Done: if value is Done:
return getattr(module, pname) return getattr(module, pname)
@ -148,6 +149,7 @@ class CommonReadHandler(ReadHandler):
def wrap(self, key): def wrap(self, key):
def method(module, pname=key, func=self.func): def method(module, pname=key, func=self.func):
with module.accessLock:
ret = func(module) ret = func(module)
if ret not in (None, Done): if ret not in (None, Done):
raise ProgrammingError('a method wrapped with CommonReadHandler must not return any value') raise ProgrammingError('a method wrapped with CommonReadHandler must not return any value')
@ -165,6 +167,7 @@ class WriteHandler(Handler):
def wrap(self, key): def wrap(self, key):
@wraps(self.func) @wraps(self.func)
def method(module, value, pname=key, func=self.func): def method(module, value, pname=key, func=self.func):
with module.accessLock:
value = func(module, pname, value) value = func(module, pname, value)
if value is not Done: if value is not Done:
setattr(module, pname, value) setattr(module, pname, value)
@ -201,6 +204,7 @@ class CommonWriteHandler(WriteHandler):
def wrap(self, key): def wrap(self, key):
@wraps(self.func) @wraps(self.func)
def method(module, value, pname=key, func=self.func): def method(module, value, pname=key, func=self.func):
with module.accessLock:
values = WriteParameters(module) values = WriteParameters(module)
values[pname] = value values[pname] = value
ret = func(module, values) ret = func(module, values)

View File

@ -1,6 +1,12 @@
# content of conftest.py # content of conftest.py
import pytest import pytest
from secop.lib import generalConfig
@pytest.fixture(scope="session", autouse=True)
def general_config():
generalConfig.testinit()
@pytest.fixture(scope="module") @pytest.fixture(scope="module")