def clear_jobs(self): """Clear current Job queue and start fresh.""" if self._jobs.empty(): # Guards against getting stuck waiting for self._mutex when # thread is waiting for self._jobs to not be empty. return with self._mutex: self._cleared = True self._jobs = PriorityQueue()
def __init__(self, bot): """Requires bot as argument for logging.""" threading.Thread.__init__(self) self.bot = bot self._jobs = PriorityQueue() # While PriorityQueue it self is thread safe, this mutex is needed # to stop old jobs being put into new queue after clearing the # queue. self._mutex = threading.Lock() # self.cleared is used for more fine grained locking. self._cleared = False
class JobScheduler(threading.Thread): """Calls jobs assigned to it in steady intervals. JobScheduler is a thread that keeps track of Jobs and calls them every X seconds, where X is a property of the Job. It maintains jobs in a priority queue, where the next job to be called is always the first item. Thread safety is maintained with a mutex that is released during long operations, so methods add_job and clear_jobs can be safely called from the main thread. """ min_reaction_time = 30.0 # seconds """How often should scheduler checks for changes in the job list.""" def __init__(self, bot): """Requires bot as argument for logging.""" threading.Thread.__init__(self) self.bot = bot self._jobs = PriorityQueue() # While PriorityQueue it self is thread safe, this mutex is needed # to stop old jobs being put into new queue after clearing the # queue. self._mutex = threading.Lock() # self.cleared is used for more fine grained locking. self._cleared = False def add_job(self, job): """Add a Job to the current job queue.""" self._jobs.put(job) def clear_jobs(self): """Clear current Job queue and start fresh.""" if self._jobs.empty(): # Guards against getting stuck waiting for self._mutex when # thread is waiting for self._jobs to not be empty. return with self._mutex: self._cleared = True self._jobs = PriorityQueue() def run(self): """Run forever.""" while True: try: self._do_next_job() except Exception: # Modules exceptions are caught earlier, so this is a bit # more serious. Options are to either stop the main thread # or continue this thread and hope that it won't happen # again. self.bot.error() # Sleep a bit to guard against busy-looping and filling # the log with useless error messages. time.sleep(10.0) # seconds def _do_next_job(self): """Wait until there is a job and do it.""" with self._mutex: # Wait until the next job should be executed. # This has to be a loop, because signals stop time.sleep(). while True: job = self._jobs.peek() difference = job.next_time - time.time() duration = min(difference, self.min_reaction_time) if duration <= 0: break with released(self._mutex): time.sleep(duration) self._cleared = False job = self._jobs.get() with released(self._mutex): if job.func.thread: t = threading.Thread( target=self._call, args=(job.func,) ) t.start() else: self._call(job.func) job.next() # If jobs were cleared during the call, don't put an old job # into the new job queue. if not self._cleared: self._jobs.put(job) def _call(self, func): """Wrapper for collecting errors from modules.""" # Willie.bot.call is way too specialized to be used instead. try: func(self.bot) except Exception: self.bot.error()
class JobScheduler(threading.Thread): """Calls jobs assigned to it in steady intervals. JobScheduler is a thread that keeps track of Jobs and calls them every X seconds, where X is a property of the Job. It maintains jobs in a priority queue, where the next job to be called is always the first item. Thread safety is maintained with a mutex that is released during long operations, so methods add_job and clear_jobs can be safely called from the main thread. """ min_reaction_time = 30.0 # seconds """How often should scheduler checks for changes in the job list.""" def __init__(self, bot): """Requires bot as argument for logging.""" threading.Thread.__init__(self) self.bot = bot self._jobs = PriorityQueue() # While PriorityQueue it self is thread safe, this mutex is needed # to stop old jobs being put into new queue after clearing the # queue. self._mutex = threading.Lock() # self.cleared is used for more fine grained locking. self._cleared = False def add_job(self, job): """Add a Job to the current job queue.""" self._jobs.put(job) def clear_jobs(self): """Clear current Job queue and start fresh.""" if self._jobs.empty(): # Guards against getting stuck waiting for self._mutex when # thread is waiting for self._jobs to not be empty. return with self._mutex: self._cleared = True self._jobs = PriorityQueue() def run(self): """Run forever.""" while True: try: self._do_next_job() except Exception: # Modules exceptions are caught earlier, so this is a bit # more serious. Options are to either stop the main thread # or continue this thread and hope that it won't happen # again. self.bot.error() # Sleep a bit to guard against busy-looping and filling # the log with useless error messages. time.sleep(10.0) # seconds def _do_next_job(self): """Wait until there is a job and do it.""" with self._mutex: # Wait until the next job should be executed. # This has to be a loop, because signals stop time.sleep(). while True: job = self._jobs.peek() difference = job.next_time - time.time() duration = min(difference, self.min_reaction_time) if duration <= 0: break with released(self._mutex): time.sleep(duration) self._cleared = False job = self._jobs.get() with released(self._mutex): if job.func.thread: t = threading.Thread(target=self._call, args=(job.func, )) t.start() else: self._call(job.func) job.next() # If jobs were cleared during the call, don't put an old job # into the new job queue. if not self._cleared: self._jobs.put(job) def _call(self, func): """Wrapper for collecting errors from modules.""" # Willie.bot.call is way too specialized to be used instead. try: func(self.bot) except Exception: self.bot.error()