5 import util.go_utils as gobject_utils
6 import util.coroutines as coroutines
7 import util.misc as misc_utils
10 _moduleLogger = logging.getLogger("gvoice.state_machine")
13 def to_milliseconds(**kwd):
14 if "milliseconds" in kwd:
15 return kwd["milliseconds"]
16 elif "seconds" in kwd:
17 return kwd["seconds"] * 1000
18 elif "minutes" in kwd:
19 return kwd["minutes"] * 1000 * 60
21 return kwd["hours"] * 1000 * 60 * 60
22 raise KeyError("Unknown arg: %r" % kwd)
25 def to_seconds(**kwd):
26 if "milliseconds" in kwd:
27 return kwd["milliseconds"] / 1000
28 elif "seconds" in kwd:
30 elif "minutes" in kwd:
31 return kwd["minutes"] * 60
33 return kwd["hours"] * 60 * 60
34 raise KeyError("Unknown arg: %r" % kwd)
37 class NopStateStrategy(object):
42 def initialize_state(self):
45 def reinitialize_state(self):
48 def increment_state(self):
53 return UpdateStateMachine.INFINITE_PERIOD
56 return "NopStateStrategy()"
59 class ConstantStateStrategy(object):
61 def __init__(self, timeout):
62 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
63 self._timeout = timeout
65 def initialize_state(self):
68 def reinitialize_state(self):
71 def increment_state(self):
79 return "ConstantStateStrategy(timeout=%r)" % self._timeout
82 class NTimesStateStrategy(object):
84 def __init__(self, timeouts, postTimeout):
85 assert 0 < len(timeouts)
86 for timeout in timeouts:
87 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
88 assert 0 < postTimeout or postTimeout == UpdateStateMachine.INFINITE_PERIOD
89 self._timeouts = timeouts
90 self._postTimeout = postTimeout
92 self._attemptCount = 0
94 def initialize_state(self):
95 self._attemptCount = len(self._timeouts)
97 def reinitialize_state(self):
98 self._attemptCount = 0
100 def increment_state(self):
101 self._attemptCount += 1
106 return self._timeouts[self._attemptCount]
108 return self._postTimeout
111 return "NTimesStateStrategy(timeouts=%r, postTimeout=%r)" % (
117 class GeometricStateStrategy(object):
119 def __init__(self, init, min, max):
120 assert 0 < init and init < max or init == UpdateStateMachine.INFINITE_PERIOD
121 assert 0 < min or min == UpdateStateMachine.INFINITE_PERIOD
122 assert min < max or max == UpdateStateMachine.INFINITE_PERIOD
128 def initialize_state(self):
129 self._current = self._max
131 def reinitialize_state(self):
132 self._current = self._min
134 def increment_state(self):
135 if self._current == UpdateStateMachine.INFINITE_PERIOD:
137 if self._init == UpdateStateMachine.INFINITE_PERIOD:
138 self._current = UpdateStateMachine.INFINITE_PERIOD
139 elif self._max == UpdateStateMachine.INFINITE_PERIOD:
142 self._current = min(2 * self._current, self._max - self._init)
146 if UpdateStateMachine.INFINITE_PERIOD in (self._init, self._current):
147 timeout = UpdateStateMachine.INFINITE_PERIOD
149 timeout = self._init + self._current
153 return "GeometricStateStrategy(init=%r, min=%r, max=%r)" % (
154 self._init, self._min, self._max
158 class StateMachine(object):
160 STATE_ACTIVE = 0, "active"
161 STATE_IDLE = 1, "idle"
165 raise NotImplementedError("Abstract")
168 raise NotImplementedError("Abstract")
171 raise NotImplementedError("Abstract")
173 def set_state(self, state):
174 raise NotImplementedError("Abstract")
178 raise NotImplementedError("Abstract")
181 class MasterStateMachine(StateMachine):
185 self._state = self.STATE_ACTIVE
187 def append_machine(self, machine):
188 self._machines.append(machine)
191 # Confirm we are all on the same page
192 for machine in self._machines:
193 machine.set_state(self._state)
194 for machine in self._machines:
198 for machine in self._machines:
202 for machine in self._machines:
205 def set_state(self, state):
207 for machine in self._machines:
208 machine.set_state(state)
215 class UpdateStateMachine(StateMachine):
216 # Making sure the it is initialized is finicky, be careful
219 DEFAULT_MAX_TIMEOUT = to_seconds(hours=24)
223 def __init__(self, updateItems, name="", maxTime = DEFAULT_MAX_TIMEOUT):
225 self._updateItems = updateItems
226 self._maxTime = maxTime
228 self._state = self.STATE_ACTIVE
229 self._onTimeout = gobject_utils.Timeout(self._on_timeout)
231 self._strategies = {}
232 self._callback = coroutines.func_sink(
233 coroutines.expand_positional(
234 self._request_reset_timers
239 return """UpdateStateMachine(
242 )""" % (self._name, self._strategies)
244 def set_state_strategy(self, state, strategy):
245 self._strategies[state] = strategy
248 for strategy in self._strategies.itervalues():
249 strategy.initialize_state()
250 if self._strategy.timeout != self.INFINITE_PERIOD:
251 self._onTimeout.start(seconds=0)
252 _moduleLogger.info("%s Starting State Machine" % (self._name, ))
255 _moduleLogger.info("%s Stopping State Machine" % (self._name, ))
256 self._onTimeout.cancel()
259 self._onTimeout.cancel()
260 self._callback = None
262 def set_state(self, newState):
263 if self._state == newState:
265 oldState = self._state
266 _moduleLogger.info("%s Transitioning from %s to %s" % (self._name, oldState, newState))
268 self._state = newState
269 self._reset_timers(initialize=True)
275 def reset_timers(self):
279 def request_reset_timers(self):
280 return self._callback
284 return self._strategies[self._state]
290 @misc_utils.log_exception(_moduleLogger)
291 def _request_reset_timers(self, *args):
294 def _reset_timers(self, initialize=False):
295 if self._onTimeout.is_running():
296 return # not started yet
297 _moduleLogger.info("%s Resetting State Machine" % (self._name, ))
298 self._onTimeout.cancel()
300 self._strategy.initialize_state()
302 self._strategy.reinitialize_state()
303 self._schedule_update()
305 def _schedule_update(self):
306 self._strategy.increment_state()
307 nextTimeout = self._strategy.timeout
308 if nextTimeout != self.INFINITE_PERIOD and nextTimeout < self._maxTime:
309 assert 0 < nextTimeout
310 self._onTimeout.start(seconds=nextTimeout)
311 _moduleLogger.info("%s Next update in %s seconds" % (self._name, nextTimeout, ))
313 _moduleLogger.info("%s No further updates (timeout is %s seconds)" % (self._name, nextTimeout, ))
315 @misc_utils.log_exception(_moduleLogger)
316 def _on_timeout(self):
317 self._schedule_update()
318 for item in self._updateItems:
320 item.update(force=True)
322 _moduleLogger.exception("Update failed for %r" % item)