4 @todo Look into switching from POLL_TIME = min(F * 2^n, MAX) to POLL_TIME = min(CONST + F * 2^n, MAX)
5 @todo Look into supporting more states that have a different F and MAX
14 import util.algorithms as algorithms
15 import util.coroutines as coroutines
18 _moduleLogger = logging.getLogger("gvoice.state_machine")
21 def _to_milliseconds(**kwd):
22 if "milliseconds" in kwd:
23 return kwd["milliseconds"]
24 elif "seconds" in kwd:
25 return kwd["seconds"] * 1000
26 elif "minutes" in kwd:
27 return kwd["minutes"] * 1000 * 60
28 raise KeyError("Unknown arg: %r" % kwd)
31 class StateMachine(object):
33 STATE_ACTIVE = "active"
37 _ACTION_UPDATE = "update"
38 _ACTION_RESET = "reset"
41 _INITIAL_ACTIVE_PERIOD = int(_to_milliseconds(seconds=5))
42 _FINAL_ACTIVE_PERIOD = int(_to_milliseconds(minutes=2))
43 _IDLE_PERIOD = int(_to_milliseconds(minutes=10))
48 def __init__(self, initItems, updateItems):
49 self._initItems = initItems
50 self._updateItems = updateItems
52 self._actions = Queue.Queue()
53 self._state = self.STATE_ACTIVE
54 self._timeoutId = None
56 self._currentPeriod = self._INITIAL_ACTIVE_PERIOD
57 self._set_initial_period()
60 assert self._thread is None
61 self._thread = threading.Thread(target=self._run)
62 self._thread.setDaemon(self._IS_DAEMON)
66 if self._thread is not None:
67 self._actions.put(self._ACTION_STOP)
70 _moduleLogger.info("Stopping an already stopped state machine")
72 def set_state(self, state):
79 def reset_timers(self):
80 self._actions.put(self._ACTION_RESET)
83 def request_reset_timers(self, args):
87 logging.basicConfig(level=logging.DEBUG)
88 _moduleLogger.info("Starting State Machine")
89 for item in self._initItems:
93 _moduleLogger.exception("Initial update failed for %r" % item)
95 # empty the task queue
96 actions = list(algorithms.itr_available(self._actions, initiallyBlock = False))
97 self._schedule_update()
98 if len(self._updateItems) == 0:
102 # block till we get a task, or get all the tasks if we were late
103 actions = list(algorithms.itr_available(self._actions, initiallyBlock = True))
105 if self._ACTION_STOP in actions:
106 _moduleLogger.info("Requested to stop")
109 elif self._ACTION_RESET in actions:
110 _moduleLogger.info("Reseting timers")
112 elif self._ACTION_UPDATE in actions:
113 _moduleLogger.info("Update")
114 for item in self._updateItems:
116 item.update(force=True)
118 _moduleLogger.exception("Update failed for %r" % item)
119 self._schedule_update()
121 def _set_initial_period(self):
122 self._currentPeriod = self._INITIAL_ACTIVE_PERIOD / 2 # We will double it later
124 def _reset_timers(self):
126 self._set_initial_period()
127 self._schedule_update()
129 def _schedule_update(self):
130 nextTimeout = self._calculate_step(self._state, self._currentPeriod)
131 nextTimeout = int(nextTimeout)
132 if nextTimeout != self._INFINITE_PERIOD:
133 self._timeoutId = gobject.timeout_add(nextTimeout, self._on_timeout)
134 self._currentPeriod = nextTimeout
136 def _stop_update(self):
137 if self._timeoutId is None:
139 gobject.source_remove(self._timeoutId)
140 self._timeoutId = None
142 def _on_timeout(self):
143 self._actions.put(self._ACTION_UPDATE)
144 return False # do not continue
147 def _calculate_step(cls, state, period):
148 if state == cls.STATE_ACTIVE:
149 return min(period * 2, cls._FINAL_ACTIVE_PERIOD)
150 elif state == cls.STATE_IDLE:
151 return cls._IDLE_PERIOD
152 elif state == cls.STATE_DND:
153 return cls._INFINITE_PERIOD
155 raise RuntimeError("Unknown state: %r" % (state, ))