7 import util.go_utils as gobject_utils
8 import util.coroutines as coroutines
9 import util.misc as misc_utils
12 _moduleLogger = logging.getLogger("gvoice.state_machine")
15 def to_milliseconds(**kwd):
16 if "milliseconds" in kwd:
17 return kwd["milliseconds"]
18 elif "seconds" in kwd:
19 return kwd["seconds"] * 1000
20 elif "minutes" in kwd:
21 return kwd["minutes"] * 1000 * 60
23 return kwd["hours"] * 1000 * 60 * 60
24 raise KeyError("Unknown arg: %r" % kwd)
27 def to_seconds(**kwd):
28 if "milliseconds" in kwd:
29 return kwd["milliseconds"] / 1000
30 elif "seconds" in kwd:
32 elif "minutes" in kwd:
33 return kwd["minutes"] * 60
35 return kwd["hours"] * 60 * 60
36 raise KeyError("Unknown arg: %r" % kwd)
39 class NopStateStrategy(object):
44 def initialize_state(self):
47 def reinitialize_state(self):
50 def increment_state(self):
55 return UpdateStateMachine.INFINITE_PERIOD
58 return "NopStateStrategy()"
61 class ConstantStateStrategy(object):
63 def __init__(self, timeout):
64 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
65 self._timeout = timeout
67 def initialize_state(self):
70 def reinitialize_state(self):
73 def increment_state(self):
81 return "ConstantStateStrategy(timeout=%r)" % self._timeout
84 class NTimesStateStrategy(object):
86 def __init__(self, timeouts, postTimeout):
87 assert 0 < len(timeouts)
88 for timeout in timeouts:
89 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
90 assert 0 < postTimeout or postTimeout == UpdateStateMachine.INFINITE_PERIOD
91 self._timeouts = timeouts
92 self._postTimeout = postTimeout
94 self._attemptCount = 0
96 def initialize_state(self):
97 self._attemptCount = len(self._timeouts)
99 def reinitialize_state(self):
100 self._attemptCount = 0
102 def increment_state(self):
103 self._attemptCount += 1
108 return self._timeouts[self._attemptCount]
110 return self._postTimeout
113 return "NTimesStateStrategy(timeouts=%r, postTimeout=%r)" % (
119 class GeometricStateStrategy(object):
121 def __init__(self, init, min, max):
122 assert 0 < init and init < max or init == UpdateStateMachine.INFINITE_PERIOD
123 assert 0 < min or min == UpdateStateMachine.INFINITE_PERIOD
124 assert min < max or max == UpdateStateMachine.INFINITE_PERIOD
130 def initialize_state(self):
131 self._current = self._max
133 def reinitialize_state(self):
134 self._current = self._min
136 def increment_state(self):
137 if self._current == UpdateStateMachine.INFINITE_PERIOD:
139 if self._init == UpdateStateMachine.INFINITE_PERIOD:
140 self._current = UpdateStateMachine.INFINITE_PERIOD
141 elif self._max == UpdateStateMachine.INFINITE_PERIOD:
144 self._current = min(2 * self._current, self._max - self._init)
148 if UpdateStateMachine.INFINITE_PERIOD in (self._init, self._current):
149 timeout = UpdateStateMachine.INFINITE_PERIOD
151 timeout = self._init + self._current
155 return "GeometricStateStrategy(init=%r, min=%r, max=%r)" % (
156 self._init, self._min, self._max
160 class StateMachine(object):
162 STATE_ACTIVE = 0, "active"
163 STATE_IDLE = 1, "idle"
167 raise NotImplementedError("Abstract")
170 raise NotImplementedError("Abstract")
173 raise NotImplementedError("Abstract")
175 def set_state(self, state):
176 raise NotImplementedError("Abstract")
180 raise NotImplementedError("Abstract")
183 class MasterStateMachine(StateMachine):
187 self._state = self.STATE_ACTIVE
189 def append_machine(self, machine):
190 self._machines.append(machine)
193 # Confirm we are all on the same page
194 for machine in self._machines:
195 machine.set_state(self._state)
196 for machine in self._machines:
200 for machine in self._machines:
204 for machine in self._machines:
207 def set_state(self, state):
209 for machine in self._machines:
210 machine.set_state(state)
217 class UpdateStateMachine(StateMachine):
218 # Making sure the it is initialized is finicky, be careful
221 DEFAULT_MAX_TIMEOUT = to_seconds(hours=24)
225 def __init__(self, updateItems, name="", maxTime = DEFAULT_MAX_TIMEOUT):
227 self._updateItems = updateItems
228 self._maxTime = maxTime
230 self._state = self.STATE_ACTIVE
231 self._onTimeout = gobject_utils.Timeout(self._on_timeout)
233 self._strategies = {}
234 self._callback = coroutines.func_sink(
235 coroutines.expand_positional(
236 self._request_reset_timers
241 return """UpdateStateMachine(
244 )""" % (self._name, self._strategies)
246 def set_state_strategy(self, state, strategy):
247 self._strategies[state] = strategy
250 for strategy in self._strategies.itervalues():
251 strategy.initialize_state()
252 if self._strategy.timeout != self.INFINITE_PERIOD:
253 self._onTimeout.start(seconds=0)
254 _moduleLogger.info("%s Starting State Machine" % (self._name, ))
257 _moduleLogger.info("%s Stopping State Machine" % (self._name, ))
258 self._onTimeout.cancel()
261 self._onTimeout.cancel()
262 self._callback = None
264 def set_state(self, newState):
265 if self._state == newState:
267 oldState = self._state
268 _moduleLogger.info("%s Transitioning from %s to %s" % (self._name, oldState, newState))
270 self._state = newState
271 self._reset_timers(initialize=True)
277 def reset_timers(self):
281 def request_reset_timers(self):
282 return self._callback
286 return self._strategies[self._state]
292 @misc_utils.log_exception(_moduleLogger)
293 def _request_reset_timers(self, *args):
296 def _reset_timers(self, initialize=False):
297 if self._timeoutId is None:
298 return # not started yet
299 _moduleLogger.info("%s Resetting State Machine" % (self._name, ))
300 self._onTimeout.cancel()
302 self._strategy.initialize_state()
304 self._strategy.reinitialize_state()
305 self._schedule_update()
307 def _schedule_update(self):
308 self._strategy.increment_state()
309 nextTimeout = self._strategy.timeout
310 if nextTimeout != self.INFINITE_PERIOD and nextTimeout < self._maxTime:
311 assert 0 < nextTimeout
312 self._onTimeout.start(seconds=nextTimeout)
313 _moduleLogger.info("%s Next update in %s seconds" % (self._name, nextTimeout, ))
315 _moduleLogger.info("%s No further updates (timeout is %s seconds)" % (self._name, nextTimeout, ))
317 @misc_utils.log_exception(_moduleLogger)
318 def _on_timeout(self):
319 self._schedule_update()
320 for item in self._updateItems:
322 item.update(force=True)
324 _moduleLogger.exception("Update failed for %r" % item)