new poll mechanism

- remove secop.poller and basic poller
- regular polls for 'important' parameters done by method doPoll
- all other parameters are polled slower (slowInterval) and
  with lower priority (only one at a time when main poll is due)
- nopoll decorator for read_* to disable poll
- enablePoll attribute (default True) for disabling polling a module
- fast polls may be implemented by means of a statemachine
- configurable slow poll interval
+ allow a Parameter to override a Property (parameter
  Readable.pollinterval overrides Module.pollinterval)

Change-Id: Ib1b3453041a233678b7c4b4add22ac399670e447
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27832
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
This commit is contained in:
2022-02-23 16:42:28 +01:00
parent aa82bc580d
commit b423235c5d
23 changed files with 343 additions and 683 deletions

View File

@@ -33,8 +33,9 @@ from secop.lib.enum import Enum
from secop.modules import Attached, Communicator, \
Done, Drivable, Module, Readable, Writable
from secop.params import Command, Parameter
from secop.poller import AUTO, DYNAMIC, REGULAR, SLOW
from secop.properties import Property
from secop.proxy import Proxy, SecNode, proxy_class
from secop.io import HasIO, StringIO, BytesIO, HasIodev # TODO: remove HasIodev (legacy stuff)
from secop.persistent import PersistentMixin, PersistentParam
from secop.rwhandler import ReadHandler, WriteHandler, CommonReadHandler, \
CommonWriteHandler, nopoll

View File

@@ -35,7 +35,6 @@ from secop.errors import CommunicationFailedError, CommunicationSilentError, \
ConfigError, ProgrammingError
from secop.modules import Attached, Command, \
Communicator, Done, Module, Parameter, Property
from secop.poller import REGULAR
from secop.lib import generalConfig
@@ -109,7 +108,7 @@ class IOBase(Communicator):
uri = Property('hostname:portnumber', datatype=StringType())
timeout = Parameter('timeout', datatype=FloatRange(0), default=2)
wait_before = Parameter('wait time before sending', datatype=FloatRange(), default=0)
is_connected = Parameter('connection state', datatype=BoolType(), readonly=False, default=False, poll=REGULAR)
is_connected = Parameter('connection state', datatype=BoolType(), readonly=False, default=False)
pollinterval = Parameter('reconnect interval', datatype=FloatRange(0), readonly=False, default=10)
_reconnectCallbacks = None
@@ -133,6 +132,9 @@ class IOBase(Communicator):
self._conn = None
self.is_connected = False
def doPoll(self):
self.read_is_connected()
def read_is_connected(self):
"""try to reconnect, when not connected

View File

@@ -153,7 +153,7 @@ class SequencerMixin:
self._seq_error = str(e)
finally:
self._seq_thread = None
self.pollParams(0)
self.doPoll()
def _seq_thread_inner(self, seq, store_init):
store = Namespace()

View File

@@ -23,8 +23,8 @@
"""Define base classes for real Modules implemented in the server"""
import sys
import time
import threading
from collections import OrderedDict
from functools import wraps
@@ -35,7 +35,6 @@ from secop.errors import BadValueError, ConfigError, \
from secop.lib import formatException, mkthread, UniqueObject, generalConfig
from secop.lib.enum import Enum
from secop.params import Accessible, Command, Parameter
from secop.poller import BasicPoller, Poller
from secop.properties import HasProperties, Property
from secop.logging import RemoteLogHandler, HasComlog
@@ -90,15 +89,18 @@ class HasAccessibles(HasProperties):
else:
aobj.merge(merged_properties[aname])
accessibles[aname] = aobj
# rebuild order: (1) inherited items, (2) items from paramOrder, (3) new accessibles
# move (2) to the end
for aname in list(cls.__dict__.get('paramOrder', ())):
paramOrder = cls.__dict__.get('paramOrder', ())
for aname in paramOrder:
if aname in accessibles:
accessibles.move_to_end(aname)
# ignore unknown names
# move (3) to the end
for aname in new_names:
accessibles.move_to_end(aname)
if aname not in paramOrder:
accessibles.move_to_end(aname)
# note: for python < 3.6 the order of inherited items is not ensured between
# declarations within the same class
cls.accessibles = accessibles
@@ -147,7 +149,7 @@ class HasAccessibles(HasProperties):
setattr(self, pname, value) # important! trigger the setter
return value
new_rfunc.poll = getattr(rfunc, 'poll', True) and pobj.poll
new_rfunc.poll = getattr(rfunc, 'poll', True)
else:
def new_rfunc(self, pname=pname):
@@ -268,6 +270,9 @@ class Module(HasAccessibles):
extname='implementation')
interface_classes = Property('offical highest interface-class of the module', ArrayOf(StringType()),
extname='interface_classes')
pollinterval = Property('poll interval for parameters handled by doPoll', FloatRange(0.1, 120), default=5)
slowinterval = Property('poll interval for other parameters', FloatRange(0.1, 120), default=15)
enablePoll = True
# properties, parameters and commands are auto-merged upon subclassing
parameters = {}
@@ -275,7 +280,6 @@ class Module(HasAccessibles):
# reference to the dispatcher (used for sending async updates)
DISPATCHER = None
pollerClass = Poller #: default poller used
def __init__(self, name, logger, cfgdict, srv):
# remember the dispatcher object (for the async callbacks)
@@ -289,6 +293,7 @@ class Module(HasAccessibles):
self.initModuleDone = False
self.startModuleDone = False
self.remoteLogHandler = None
self.nextPollEvent = threading.Event()
errors = []
# handle module properties
@@ -333,13 +338,6 @@ class Module(HasAccessibles):
for aname, aobj in self.accessibles.items():
# make a copy of the Parameter/Command object
aobj = aobj.copy()
if isinstance(aobj, Parameter):
# fix default properties poll and needscfg
if aobj.poll is None:
aobj.poll = bool(aobj.handler)
if aobj.needscfg is None:
aobj.needscfg = not aobj.poll
if not self.export: # do not export parameters of a module not exported
aobj.export = False
if aobj.export:
@@ -578,16 +576,25 @@ class Module(HasAccessibles):
registers it in the server for waiting
<timeout> defaults to 30 seconds
"""
if self.writeDict:
mkthread(self.writeInitParams, start_events.get_trigger())
if self.enablePoll or self.writeDict:
# enablePoll == False: start poll thread for writing values from writeDict only
mkthread(self.__pollThread, start_events.get_trigger())
self.startModuleDone = True
def pollOneParam(self, pname):
"""poll parameter <pname> with proper error handling"""
def doPoll(self):
"""polls important parameters like value and status
all other parameters are polled automatically
"""
def triggerPollEvent(self, *args): # args needed for valueCallback
"""interrupts waiting between polls"""
self.nextPollEvent.set() # trigger poll loop
def callPollFunc(self, rfunc):
"""call read method with proper error handling"""
try:
rfunc = getattr(self, 'read_' + pname)
if rfunc.poll: # TODO: handle this in poller
rfunc()
rfunc()
except SilentError:
pass
except SECoPError as e:
@@ -595,6 +602,63 @@ class Module(HasAccessibles):
except Exception:
self.log.error(formatException())
def __pollThread(self, started_callback):
self.writeInitParams()
if not self.enablePoll:
return
polled_parameters = []
# collect and call all read functions a first time
for pname, pobj in self.parameters.items():
rfunc = getattr(self, 'read_' + pname)
if rfunc.poll:
polled_parameters.append((rfunc, pobj))
self.callPollFunc(rfunc)
started_callback()
last_slow = last_main = 0
last_error = None
error_count = 0
to_poll = ()
while True:
now = time.time()
wait_main = last_main + self.pollinterval - now
wait_slow = last_slow + self.slowinterval - now
wait_time = min(wait_main, wait_slow)
if wait_time > 0:
self.nextPollEvent.wait(wait_time)
self.nextPollEvent.clear()
# remark: if there would be a need to trigger polling all parameters,
# we might replace nextPollEvent by a Queue and act depending on the
# queued item
continue
# call doPoll, if due
if wait_main <= 0:
last_main = (now // self.pollinterval) * self.pollinterval
try:
self.doPoll()
if last_error and error_count > 1:
self.log.info('recovered after %d calls to doPoll (%r)', error_count, last_error)
last_error = None
except Exception as e:
if type(e) != last_error:
error_count = 0
self.log.error('error in doPoll: %r', e)
error_count += 1
last_error = e
now = time.time()
# find ONE due slow poll and call it
loop = True
while loop: # loops max. 2 times, when to_poll is at end
for rfunc, pobj in to_poll:
if now > pobj.timestamp + self.slowinterval * 0.5:
self.callPollFunc(rfunc)
loop = False
break
else:
if now < last_slow + self.slowinterval:
break
last_slow = (now // self.slowinterval) * self.slowinterval
to_poll = iter(polled_parameters)
def writeInitParams(self, started_callback=None):
"""write values for parameters with configured values
@@ -640,55 +704,20 @@ class Readable(Module):
UNKNOWN=401,
) #: status codes
value = Parameter('current value of the module', FloatRange(), poll=True)
value = Parameter('current value of the module', FloatRange())
status = Parameter('current status of the module', TupleOf(EnumType(Status), StringType()),
default=(Status.IDLE, ''), poll=True)
pollinterval = Parameter('sleeptime between polls', FloatRange(0.1, 120),
default=5, readonly=False)
default=(Status.IDLE, ''))
pollinterval = Parameter('default poll interval', FloatRange(0.1, 120),
default=5, readonly=False, export=True)
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, start_events.get_trigger(timeout=30))
else:
super().startModule(start_events)
def earlyInit(self):
super().earlyInit()
# in case pollinterval is reduced a lot, we do not want to wait
self.valueCallbacks['pollinterval'].append(self.triggerPollEvent)
def __pollThread(self, started_callback):
while True:
try:
self.__pollThread_inner(started_callback)
except Exception as e:
self.log.exception(e)
self.status = (self.Status.ERROR, 'polling thread could not start')
started_callback()
print(formatException(0, sys.exc_info(), verbose=True))
time.sleep(10)
def __pollThread_inner(self, started_callback):
"""super simple and super stupid per-module polling thread"""
self.writeInitParams()
i = 0
fastpoll = self.pollParams(i)
started_callback()
while True:
i += 1
try:
time.sleep(self.pollinterval * (0.1 if fastpoll else 1))
except TypeError:
time.sleep(min(self.pollinterval)
if fastpoll else max(self.pollinterval))
fastpoll = self.pollParams(i)
def pollParams(self, nr=0):
# Just poll all parameters regularly where polling is enabled
for pname, pobj in self.parameters.items():
if not pobj.poll:
continue
if nr % abs(int(pobj.poll)) == 0:
# pollParams every 'pobj.pollParams' iteration
self.pollOneParam(pname)
return False
def doPoll(self):
self.read_value()
self.read_status()
class Writable(Readable):
@@ -739,24 +768,6 @@ class Drivable(Writable):
"""
return 300 <= (status or self.status)[0] < 390
# improved polling: may poll faster if module is BUSY
def pollParams(self, nr=0):
# poll status first
self.read_status()
fastpoll = self.isBusy()
for pname, pobj in self.parameters.items():
if not pobj.poll:
continue
if pname == 'status':
# status was already polled above
continue
if ((int(pobj.poll) < 0) and fastpoll) or (
nr % abs(int(pobj.poll))) == 0:
# poll always if pobj.poll is negative and fastpoll (i.e. Module is busy)
# otherwise poll every 'pobj.poll' iteration
self.pollOneParam(pname)
return fastpoll
@Command(None, result=None)
def stop(self):
"""cease driving, go to IDLE state"""

View File

@@ -26,10 +26,11 @@
import inspect
from secop.datatypes import BoolType, CommandType, DataType, \
DataTypeType, EnumType, IntRange, NoneOr, OrType, \
DataTypeType, EnumType, NoneOr, OrType, \
StringType, StructOf, TextType, TupleOf, ValueType
from secop.errors import BadValueError, ProgrammingError
from secop.properties import HasProperties, Property
from secop.lib import generalConfig
class Accessible(HasProperties):
@@ -132,24 +133,9 @@ class Parameter(Accessible):
* True: exported, name automatic.
* a string: exported with custom name''', OrType(BoolType(), StringType()),
export=False, default=True)
poll = Property(
'''[internal] polling indicator
may be:
* None (omitted): will be converted to True/False if handler is/is not None
* False or 0 (never poll this parameter)
* True or 1 (AUTO), converted to SLOW (readonly=False)
DYNAMIC (*status* and *value*) or REGULAR (else)
* 2 (SLOW), polled with lower priority and a multiple of pollinterval
* 3 (REGULAR), polled with pollperiod
* 4 (DYNAMIC), if BUSY, with a fraction of pollinterval,
else polled with pollperiod
''', NoneOr(IntRange()),
export=False, default=None)
needscfg = Property(
'[internal] needs value in config', NoneOr(BoolType()),
export=False, default=None)
export=False, default=False)
optional = Property(
'[internal] is this parameter optional?', BoolType(),
export=False, settable=False, default=False)
@@ -169,6 +155,8 @@ class Parameter(Accessible):
def __init__(self, description=None, datatype=None, inherit=True, **kwds):
super().__init__()
if 'poll' in kwds and generalConfig.tolerate_poll_property:
kwds.pop('poll')
if datatype is None:
# collect datatype properties. these are not applied, as we have no datatype
self.ownProperties = {k: kwds.pop(k) for k in list(kwds) if k not in self.propertyDict}

View File

@@ -1,278 +0,0 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""general, advanced frappy poller
Usage examples:
any Module which want to be polled with a specific Poller must define
the pollerClass class variable:
class MyModule(Readable):
...
pollerClass = poller.Poller
...
modules having a parameter 'io' with the same value will share the same poller
"""
import time
from heapq import heapify, heapreplace
from threading import Event
from secop.errors import ProgrammingError
from secop.lib import mkthread
# poll types:
AUTO = 1 #: equivalent to True, converted to REGULAR, SLOW or DYNAMIC
SLOW = 2 #: polling with low priority and increased poll interval (used by default when readonly=False)
REGULAR = 3 #: polling with standard interval (used by default for read only parameters except status and value)
DYNAMIC = 4 #: polling with shorter poll interval when BUSY (used by default for status and value)
class PollerBase:
startup_timeout = 30 # default timeout for startup
name = 'unknown' # to be overridden in implementors __init__ method
@classmethod
def add_to_table(cls, table, module):
"""sort module into poller table
table is a dict, with (<pollerClass>, <name>) as the key, and the
poller as value.
<name> is module.io.name or module.name, if io is not present
"""
# for modules with the same io, a common poller is used,
# modules without io all get their own poller
name = getattr(module, 'io', module).name
poller = table.get((cls, name), None)
if poller is None:
poller = cls(name)
table[(cls, name)] = poller
poller.add_to_poller(module)
def start(self, started_callback):
"""start poller thread
started_callback to be called after all poll items were read at least once
"""
mkthread(self.run, started_callback)
return self.startup_timeout
def run(self, started_callback):
"""poller thread function
started_callback to be called after all poll items were read at least once
"""
raise NotImplementedError
def stop(self):
"""stop polling"""
raise NotImplementedError
def __bool__(self):
"""is there any poll item?"""
raise NotImplementedError
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.name)
class Poller(PollerBase):
"""a standard poller
parameters may have the following polltypes:
- REGULAR: by default used for readonly parameters with poll=True
- SLOW: by default used for readonly=False parameters with poll=True.
slow polls happen with lower priority, but at least one parameter
is polled with regular priority within self.module.pollinterval.
Scheduled to poll every slowfactor * module.pollinterval
- DYNAMIC: by default used for 'value' and 'status'
When busy, scheduled to poll every fastfactor * module.pollinterval
"""
DEFAULT_FACTORS = {SLOW: 4, DYNAMIC: 0.25, REGULAR: 1}
def __init__(self, name):
"""create a poller"""
self.queues = {polltype: [] for polltype in self.DEFAULT_FACTORS}
self._event = Event()
self._stopped = False
self.maxwait = 3600
self.name = name
self.modules = [] # used for writeInitParams only
def add_to_poller(self, module):
self.modules.append(module)
factors = self.DEFAULT_FACTORS.copy()
try:
factors[DYNAMIC] = module.fast_pollfactor
except AttributeError:
pass
try:
factors[SLOW] = module.slow_pollfactor
except AttributeError:
pass
self.maxwait = min(self.maxwait, getattr(module, 'max_polltestperiod', 10))
try:
self.startup_timeout = max(self.startup_timeout, module.startup_timeout)
except AttributeError:
pass
handlers = set()
# at the beginning, queues are simple lists
# later, they will be converted to heaps
for pname, pobj in module.parameters.items():
polltype = pobj.poll
if not polltype:
continue
if not hasattr(module, 'pollinterval'):
raise ProgrammingError("module %s must have a pollinterval"
% module.name)
if pname == 'is_connected':
if hasattr(module, 'registerReconnectCallback'):
module.registerReconnectCallback(self.name, self.trigger_all)
else:
module.log.warning("%r has 'is_connected' but no 'registerReconnectCallback'" % module)
if polltype == AUTO: # covers also pobj.poll == True
if pname in ('value', 'status'):
polltype = DYNAMIC
elif pobj.readonly:
polltype = REGULAR
else:
polltype = SLOW
if polltype not in factors:
raise ProgrammingError("unknown poll type %r for parameter '%s'"
% (polltype, pname))
if pobj.handler:
if pobj.handler in handlers:
continue # only one poller per handler
handlers.add(pobj.handler)
# placeholders 0 are used for due, lastdue and idx
self.queues[polltype].append(
(0, 0, (0, module, pobj, pname, factors[polltype])))
def poll_next(self, polltype):
"""try to poll next item
advance in queue until
- an item is found which is really due to poll. return 0 in this case
- or until the next item is not yet due. return next due time in this case
"""
queue = self.queues[polltype]
if not queue:
return float('inf') # queue is empty
now = time.time()
done = False
while not done:
due, lastdue, pollitem = queue[0]
if now < due:
return due
_, module, pobj, pname, factor = pollitem
if polltype == DYNAMIC and not module.isBusy():
interval = module.pollinterval # effective interval
mininterval = interval * factor # interval for calculating next due
else:
interval = module.pollinterval * factor
mininterval = interval
if due == 0:
due = now # do not look at timestamp after trigger_all
else:
due = max(lastdue + interval, pobj.timestamp + interval * 0.5)
if now >= due:
module.pollOneParam(pname)
done = True
lastdue = due
due = max(lastdue + mininterval, now + min(self.maxwait, mininterval * 0.5))
# replace due, lastdue with new values and sort in
heapreplace(queue, (due, lastdue, pollitem))
return 0
def trigger_all(self):
for _, queue in sorted(self.queues.items()):
for idx, (_, lastdue, pollitem) in enumerate(queue):
queue[idx] = (0, lastdue, pollitem)
self._event.set()
return True
def run(self, started_callback):
"""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.startModule.
poll strategy:
Slow polls are performed with lower priority than regular and dynamic polls.
If more polls are scheduled than time permits, at least every second poll is a
dynamic poll. After every n regular polls, one slow poll is done, if due
(where n is the number of regular parameters).
"""
if not self:
# nothing to do (else time.sleep(float('inf')) might be called below
started_callback()
return
# if writeInitParams is not yet done, we do it here
for module in self.modules:
module.writeInitParams()
# do all polls once and, at the same time, insert due info
for _, queue in sorted(self.queues.items()): # do SLOW polls first
for idx, (_, _, (_, module, pobj, pname, factor)) in enumerate(queue):
lastdue = time.time()
module.pollOneParam(pname)
due = lastdue + min(self.maxwait, module.pollinterval * factor)
# in python 3 comparing tuples need some care, as not all objects
# are comparable. Inserting a unique idx solves the problem.
queue[idx] = (due, lastdue, (idx, module, pobj, pname, factor))
heapify(queue)
started_callback() # signal end of startup
nregular = len(self.queues[REGULAR])
while not self._stopped:
due = float('inf')
for _ in range(nregular):
due = min(self.poll_next(DYNAMIC), self.poll_next(REGULAR))
if due:
break # no dynamic or regular polls due
due = min(due, self.poll_next(DYNAMIC), self.poll_next(SLOW))
delay = due - time.time()
if delay > 0:
self._event.wait(delay)
self._event.clear()
def stop(self):
self._event.set()
self._stopped = True
def __bool__(self):
"""is there any poll item?"""
return any(self.queues.values())
class BasicPoller(PollerBase):
"""basic poller
this is just a dummy, the poller thread is started in Readable.startModule
"""
# pylint: disable=abstract-method
@classmethod
def add_to_table(cls, table, module):
pass

View File

@@ -138,17 +138,18 @@ class HasProperties(HasDescriptors):
# treat overriding properties with bare values
for pn, po in properties.items():
value = getattr(cls, pn, po)
if not isinstance(value, Property): # attribute is a bare value
if not isinstance(value, (Property, HasProperties)): # attribute may be a bare value
# HasProperties is a base class of Parameter -> allow a Parameter to override a Property ()
po = po.copy()
try:
# try to apply bare value to Property
po.value = po.datatype(value)
except BadValueError:
if pn in properties:
if callable(value):
raise ProgrammingError('method %s.%s collides with property of %s' %
(cls.__name__, pn, base.__name__)) from None
raise ProgrammingError('can not set property %s.%s to %r' %
(cls.__name__, pn, value)) from None
if callable(value):
raise ProgrammingError('method %s.%s collides with property of %s' %
(cls.__name__, pn, base.__name__)) from None
raise ProgrammingError('can not set property %s.%s to %r' %
(cls.__name__, pn, value)) from None
cls.propertyDict[pn] = po
def checkProperties(self):

View File

@@ -35,9 +35,9 @@ from secop.io import HasIO
class ProxyModule(HasIO, Module):
module = Property('remote module name', datatype=StringType(), default='')
pollerClass = None
_consistency_check_done = False
_secnode = None
enablePoll = False
def ioClass(self, name, logger, opts, srv):
opts['description'] = 'secnode %s on %s' % (opts.get('module', name), opts['uri'])
@@ -123,7 +123,8 @@ class ProxyModule(HasIO, Module):
self.announceUpdate('status', newstatus)
def checkProperties(self):
pass # skip
pass # skip
class ProxyReadable(ProxyModule, Readable):
pass
@@ -184,7 +185,7 @@ def proxy_class(remote_class, name=None):
for aname, aobj in rcls.accessibles.items():
if isinstance(aobj, Parameter):
pobj = aobj.merge(dict(poll=False, handler=None, needscfg=False))
pobj = aobj.merge(dict(handler=None, needscfg=False))
attrs[aname] = pobj
def rfunc(self, pname=aname):

View File

@@ -264,7 +264,6 @@ class Server:
failure_traceback = traceback.format_exc()
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():
@@ -276,12 +275,6 @@ class Server:
missing_super.add('%s was not called, probably missing super call'
% modobj.earlyInit.__qualname__)
# handle polling
for modname, modobj in self.modules.items():
if modobj.pollerClass is not None:
# a module might be explicitly excluded from polling by setting pollerClass to None
modobj.pollerClass.add_to_table(poll_table, modobj)
# call init on each module after registering all
for modname, modobj in self.modules.items():
try:
@@ -317,17 +310,13 @@ class Server:
sys.stderr.write(failure_traceback)
sys.exit(1)
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)
poller.start(start_events.get_trigger())
self.log.info('waiting for modules and pollers being started')
self.log.info('waiting for modules being started')
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')
self.log.info('all modules started')
history_path = os.environ.get('FRAPPY_HISTORY')
if history_path:
from secop_psi.historywriter import FrappyHistoryWriter # pylint: disable=import-outside-toplevel

View File

@@ -27,13 +27,10 @@ from time import sleep
from secop.datatypes import FloatRange
from secop.lib import mkthread
from secop.modules import BasicPoller, Drivable, \
Module, Parameter, Readable, Writable, Command
from secop.modules import Drivable, Module, Parameter, Readable, Writable, Command
class SimBase:
pollerClass = BasicPoller
def __new__(cls, devname, logger, cfgdict, dispatcher):
extra_params = cfgdict.pop('extra_params', '') or cfgdict.pop('.extra_params', '')
attrs = {}
@@ -120,7 +117,7 @@ class SimDrivable(SimReadable, Drivable):
self._value = self.target
speed *= self.interval
try:
self.pollParams(0)
self.doPoll()
except Exception:
pass
@@ -133,7 +130,7 @@ class SimDrivable(SimReadable, Drivable):
self._value = self.target
sleep(self.interval)
try:
self.pollParams(0)
self.doPoll()
except Exception:
pass
self.status = self.Status.IDLE, ''