def __init__(self, resolution=0.01): super(Scheduler, self).__init__(name='Scheduler(%d)' % id(self)) self.resolution = resolution self._condition = Condition() self._entries = Collection() self._last_uptime = None self._last_systime = None self._setupflags() self._setupwatches() self._checktimecallback = None self.debugout('instantiated', STANDARDDEBUG)
class Scheduler(Thread): def __init__(self, resolution=0.01): super(Scheduler, self).__init__(name='Scheduler(%d)' % id(self)) self.resolution = resolution self._condition = Condition() self._entries = Collection() self._last_uptime = None self._last_systime = None self._setupflags() self._setupwatches() self._checktimecallback = None self.debugout('instantiated', STANDARDDEBUG) def set_checktime_callback(self, callback): self._checktimecallback = callback def _setupflags(self): # Flag indicates start() invoked. self._started = Event() # Flag indicate stop() invoked. self._stopped = Event() # Flag indicates scheduler is enabled. self._enabled = Event() # Flag indicates scheduler actively executing. self._running = Event() self._debug = debug self.enable() def _setupwatches(self): self._last_uptime = uptime.secs() self._last_systime = time.time() def enable(self): self.debugout('enabling', STANDARDDEBUG) self._enabled.set() def disable(self): self.debugout('disabled', STANDARDDEBUG) self._enabled.clear() def await_enable(self, timeout=None): return self._enabled.wait(timeout) def start(self, *args, **kw): if self.is_started(): raise TypeError('%s already started.' % self.getName()) self._started.set() self.setDaemon(True) self.debugout('starting', STANDARDDEBUG) super(Scheduler, self).start(*args, **kw) timecheckentry = self.every(time_check_period, self._check_time_progression) self.timecheckentry = timecheckentry self.debugout('created time checker %s', STANDARDDEBUG, timecheckentry) def stop(self, timeout=None): self._stopped.set() self._enabled.set() self._condition.acquire() try: self._condition.notify() finally: self._condition.release() return super(Scheduler, self).join(timeout) def is_enabled(self): return self._enabled.isSet() def is_disabled(self): return not self.is_enabled() def is_started(self): return self._started.isSet() def is_stopped(self): return self._stopped.isSet() def is_running(self): return self._running.isSet() def should_run(self): return self.is_started() and not self.is_stopped() def at(self, when, target, args=()): entry = AbsoluteEntry(self, when, target, args) self.add_entry(entry) return entry def after(self, delay, target, args=()): entry = RelativeEntry(self, delay, target, args) self.add_entry(entry) return entry def every(self, period, target, args=()): entry = RecurringEntry(self, period, target, args) self.add_entry(entry) return entry def at_time_do(self, when, target, *args): return self.at(when, target, args) def seconds_from_now_do(self, delay, target, *args): return self.after(delay, target, args) def every_seconds_do(self, period, target, *args): return self.every(period, target, args) def cancel_entry(self, entry): self.debugout('canceling entry %s', STANDARDDEBUG, entry) entry.cancel() cancel = cancel_entry def remove_entry(self, entry): self.debugout('removing entry %s', STANDARDDEBUG, entry) self._condition.acquire() try: index = self._entries.removeentry(entry) finally: self._condition.release() self.debugout('removed entry %s at %d', STANDARDDEBUG, entry, index) return index def add_entry(self, entry): self.debugout('adding entry %s', VERBOSEDEBUG, entry) self._condition.acquire() try: index = self._entries.addentry(entry) if index == 0: self._condition.notify() finally: self._condition.release() return index def enabled(self, value=None): if value is not None: if value: self.enable() else: self.disable() return self.is_enabled() def testdebug(self, level=1): return self.getdebug() >= level def getdebug(self): return self._debug def setdebug(self, level): self._debug = int(level) def debugout(self, message, dblevel=1, *args): """ String inside message may have format specifiers to be applied to variable lenth arguments '*args'. Objects passed in as arguments are only converted iff the debug level is equal to or greater than passed in debug level 'debuglevel.' This enhances performance while allowing methods to output debug messages without explicitly testing debug level. """ if self.testdebug(dblevel): if len(args): message = message % args self.logmessage(message, msglog.types.DB) if self.testdebug(VERBOSEWSTDOUT): # Also output to stdout if debug at or above 5. print '%s: %s' % (self, message) return def logmessage(self, message, mtype=msglog.types.INFO, autoprefix=True): """ Convenience method to log to message log. Default behaviour prepends 'str(self): ' to entry so that callers may skip doing so themselves. Set 'autoprefix' to False to prevent prepend behaviour from occuring. """ if autoprefix: message = '%s: %s' % (self, message) msglog.log('broadway', mtype, message) def run(self): log_startup(self, self.getName(), 'SchedulerThread') self.debugout('entering run() loop', STANDARDDEBUG) while self.should_run(): self._running.set() try: self._schedulerloop() except: msglog.exception(prefix='handled') msglog.log('broadway', msglog.types.WARN, '%s re-entering run loop.' % self.getName()) self._running.clear() else: self.debugout('exiting run() loop', STANDARDDEBUG) self.debugout('exiting run()', STANDARDDEBUG) def _schedulerloop(self): self._condition.acquire() try: while not self.is_stopped(): if self.is_enabled(): self.debugout('run() tick', VERBOSEDEBUG) tock = self._tick() if tock != 0: self.debugout('run() waiting(%0.3f)', VERBOSEDEBUG, tock) self._condition.wait(tock) else: self.debugout('run() skipping wait(0)', VERBOSEDEBUG) self.debugout('run() tock', VERBOSEDEBUG) else: self.debugout('run() awaiting enable', STANDARDDEBUG) self.await_enable() self.debugout('run() re-entering loop', STANDARDDEBUG) else: self.debugout('run() stopping', STANDARDDEBUG) finally: self._condition.release() def _tick(self): reschedule = [] entries = self._entries.popentries(uptime.secs()) self._condition.release() try: for entry in entries: try: entry.execute() except EActionExpired: pass except EActionPremature: message = 'entry %s execution premature, will reschedule' self.debugout(message, STANDARDDEBUG, entry) reschedule.append(entry) except: msglog.exception() else: if isinstance(entry, RecurringEntry): reschedule.append(entry) finally: self._condition.acquire() for entry in reschedule: entry.computewhen() self._entries.addentries(reschedule) nextrun = self._entries.nextruntime() if nextrun is not None: nextrun = max(0, nextrun - uptime.secs()) return nextrun def __str__(self): return self.getName() def __repr__(self): status = [self.getName()] if self.is_enabled(): status.append('enabled') if self.is_stopped(): status.append('stopped') elif self.is_started(): status.append('started') if self.is_running(): status.append('running') status.append('(%d entries)' % len(self._entries)) return '<%s>' % ' '.join(status) def _check_time_progression(self): self.debugout('checking time progression', VERBOSEDEBUG) curuptime = uptime.secs() cursystime = time.time() diff_uptime = curuptime - self._last_uptime diff_systime = cursystime - self._last_systime diff_diff = diff_systime - diff_uptime # If the difference between the way that time.time() is # progressing and the way that uptime.secs() is progressing # has grown larger than our allowable error (allow_time_error), # then have entry list rebuild itself so to correctly reflect # newly calculated uptime values and order of entries. if abs(diff_diff) > allow_time_error: self.debugout('time.time() drift %0.3fs', STANDARDDEBUG, diff_diff) self._condition.acquire() try: # Get all entries entries = self._entries.getentries() for entry in entries: # Have each recompute due time entry.computewhen() self.debugout('entry due-times recomputed', STANDARDDEBUG) # Create new entries collection from possibly unsorted entries self._entries.rebuild() self.debugout('entry collection rebuilt', STANDARDDEBUG) finally: # Executed within 'tick()', no need to notify. self._condition.release() # Reset baseline watch values to diff at time of check self._last_uptime = curuptime self._last_systime = cursystime else: self.debugout('drift %0.3fs ignored', VERBOSEDEBUG, diff_diff) # Callback hook for unit test monitoring resync if self._checktimecallback: self._checktimecallback() ## # Methods following this point are deprecated and have been # left here for backwards compatibility reasons. def debug_output(self, message=None, location=None): """ Use preferred 'debugout' method instead. """ if self._debug: if message: print '%f: SCHEDULER: %s' % (uptime.secs(), message) def debug(self, level=None): """ Use preferred methods 'getdebug' and 'setdebug' instead. """ if level is not None: self.setdebug(level) return self.getdebug()
class Scheduler(Thread): def __init__(self, resolution=0.01): super(Scheduler, self).__init__(name='Scheduler(%d)' % id(self)) self.resolution = resolution self._condition = Condition() self._entries = Collection() self._last_uptime = None self._last_systime = None self._setupflags() self._setupwatches() self._checktimecallback = None self.debugout('instantiated', STANDARDDEBUG) def set_checktime_callback(self, callback): self._checktimecallback = callback def _setupflags(self): # Flag indicates start() invoked. self._started = Event() # Flag indicate stop() invoked. self._stopped = Event() # Flag indicates scheduler is enabled. self._enabled = Event() # Flag indicates scheduler actively executing. self._running = Event() self._debug = debug self.enable() def _setupwatches(self): self._last_uptime = uptime.secs() self._last_systime = time.time() def enable(self): self.debugout('enabling', STANDARDDEBUG) self._enabled.set() def disable(self): self.debugout('disabled', STANDARDDEBUG) self._enabled.clear() def await_enable(self, timeout = None): return self._enabled.wait(timeout) def start(self, *args, **kw): if self.is_started(): raise TypeError('%s already started.' % self.getName()) self._started.set() self.setDaemon(True) self.debugout('starting', STANDARDDEBUG) super(Scheduler, self).start(*args, **kw) timecheckentry = self.every( time_check_period, self._check_time_progression) self.timecheckentry = timecheckentry self.debugout('created time checker %s', STANDARDDEBUG, timecheckentry) def stop(self, timeout = None): self._stopped.set() self._enabled.set() self._condition.acquire() try: self._condition.notify() finally: self._condition.release() return super(Scheduler, self).join(timeout) def is_enabled(self): return self._enabled.isSet() def is_disabled(self): return not self.is_enabled() def is_started(self): return self._started.isSet() def is_stopped(self): return self._stopped.isSet() def is_running(self): return self._running.isSet() def should_run(self): return self.is_started() and not self.is_stopped() def at(self, when, target, args=()): entry = AbsoluteEntry(self, when, target, args) self.add_entry(entry) return entry def after(self, delay, target, args=()): entry = RelativeEntry(self, delay, target, args) self.add_entry(entry) return entry def every(self, period, target, args=()): entry = RecurringEntry(self, period, target, args) self.add_entry(entry) return entry def at_time_do(self, when, target, *args): return self.at(when, target, args) def seconds_from_now_do(self, delay, target, *args): return self.after(delay, target, args) def every_seconds_do(self, period, target, *args): return self.every(period, target, args) def cancel_entry(self, entry): self.debugout('canceling entry %s', STANDARDDEBUG, entry) entry.cancel() cancel = cancel_entry def remove_entry(self, entry): self.debugout('removing entry %s', STANDARDDEBUG, entry) self._condition.acquire() try: index = self._entries.removeentry(entry) finally: self._condition.release() self.debugout('removed entry %s at %d', STANDARDDEBUG, entry, index) return index def add_entry(self, entry): self.debugout('adding entry %s', VERBOSEDEBUG, entry) self._condition.acquire() try: index = self._entries.addentry(entry) if index == 0: self._condition.notify() finally: self._condition.release() return index def enabled(self, value = None): if value is not None: if value: self.enable() else: self.disable() return self.is_enabled() def testdebug(self, level = 1): return self.getdebug() >= level def getdebug(self): return self._debug def setdebug(self, level): self._debug = int(level) def debugout(self, message, dblevel = 1, *args): """ String inside message may have format specifiers to be applied to variable lenth arguments '*args'. Objects passed in as arguments are only converted iff the debug level is equal to or greater than passed in debug level 'debuglevel.' This enhances performance while allowing methods to output debug messages without explicitly testing debug level. """ if self.testdebug(dblevel): if len(args): message = message % args self.logmessage(message, msglog.types.DB) if self.testdebug(VERBOSEWSTDOUT): # Also output to stdout if debug at or above 5. print '%s: %s' % (self, message) return def logmessage(self, message, mtype=msglog.types.INFO, autoprefix=True): """ Convenience method to log to message log. Default behaviour prepends 'str(self): ' to entry so that callers may skip doing so themselves. Set 'autoprefix' to False to prevent prepend behaviour from occuring. """ if autoprefix: message = '%s: %s' % (self, message) msglog.log('broadway', mtype, message) def run(self): log_startup(self, self.getName(), 'SchedulerThread') self.debugout('entering run() loop', STANDARDDEBUG) while self.should_run(): self._running.set() try: self._schedulerloop() except: msglog.exception(prefix = 'handled') msglog.log('broadway', msglog.types.WARN, '%s re-entering run loop.' % self.getName()) self._running.clear() else: self.debugout('exiting run() loop', STANDARDDEBUG) self.debugout('exiting run()', STANDARDDEBUG) def _schedulerloop(self): self._condition.acquire() try: while not self.is_stopped(): if self.is_enabled(): self.debugout('run() tick', VERBOSEDEBUG) tock = self._tick() if tock != 0: self.debugout('run() waiting(%0.3f)',VERBOSEDEBUG,tock) self._condition.wait(tock) else: self.debugout('run() skipping wait(0)', VERBOSEDEBUG) self.debugout('run() tock', VERBOSEDEBUG) else: self.debugout('run() awaiting enable', STANDARDDEBUG) self.await_enable() self.debugout('run() re-entering loop', STANDARDDEBUG) else: self.debugout('run() stopping', STANDARDDEBUG) finally: self._condition.release() def _tick(self): reschedule = [] entries = self._entries.popentries(uptime.secs()) self._condition.release() try: for entry in entries: try: entry.execute() except EActionExpired: pass except EActionPremature: message = 'entry %s execution premature, will reschedule' self.debugout(message, STANDARDDEBUG, entry) reschedule.append(entry) except: msglog.exception() else: if isinstance(entry, RecurringEntry): reschedule.append(entry) finally: self._condition.acquire() for entry in reschedule: entry.computewhen() self._entries.addentries(reschedule) nextrun = self._entries.nextruntime() if nextrun is not None: nextrun = max(0, nextrun - uptime.secs()) return nextrun def __str__(self): return self.getName() def __repr__(self): status = [self.getName()] if self.is_enabled(): status.append('enabled') if self.is_stopped(): status.append('stopped') elif self.is_started(): status.append('started') if self.is_running(): status.append('running') status.append('(%d entries)' % len(self._entries)) return '<%s>' % ' '.join(status) def _check_time_progression(self): self.debugout('checking time progression', VERBOSEDEBUG) curuptime = uptime.secs() cursystime = time.time() diff_uptime = curuptime - self._last_uptime diff_systime = cursystime - self._last_systime diff_diff = diff_systime - diff_uptime # If the difference between the way that time.time() is # progressing and the way that uptime.secs() is progressing # has grown larger than our allowable error (allow_time_error), # then have entry list rebuild itself so to correctly reflect # newly calculated uptime values and order of entries. if abs(diff_diff) > allow_time_error: self.debugout('time.time() drift %0.3fs', STANDARDDEBUG, diff_diff) self._condition.acquire() try: # Get all entries entries = self._entries.getentries() for entry in entries: # Have each recompute due time entry.computewhen() self.debugout('entry due-times recomputed', STANDARDDEBUG) # Create new entries collection from possibly unsorted entries self._entries.rebuild() self.debugout('entry collection rebuilt', STANDARDDEBUG) finally: # Executed within 'tick()', no need to notify. self._condition.release() # Reset baseline watch values to diff at time of check self._last_uptime = curuptime self._last_systime = cursystime else: self.debugout('drift %0.3fs ignored', VERBOSEDEBUG, diff_diff) # Callback hook for unit test monitoring resync if self._checktimecallback: self._checktimecallback() ## # Methods following this point are deprecated and have been # left here for backwards compatibility reasons. def debug_output(self, message=None, location=None): """ Use preferred 'debugout' method instead. """ if self._debug: if message: print '%f: SCHEDULER: %s' % (uptime.secs(), message) def debug(self, level = None): """ Use preferred methods 'getdebug' and 'setdebug' instead. """ if level is not None: self.setdebug(level) return self.getdebug()