class CronDaemon(): __metaclass__ = Singleton def __init__(self, *events): self.events = list(events) self.lock = threading.Lock() self.thread = StoppableThread( ) # the thread the manager loop is running in def register(self, event): self.lock.acquire() try: self.events.append(event) finally: self.lock.release() def remove(self, event): self.lock.acquire() try: self.events.remove(event) finally: self.lock.release() def start(self): """Start the process loop in a thread.""" if self.thread.is_alive(): return self.thread = StoppableThread(target=self.run, name=self.__class__.__name__ + timestamp()) self.thread.start() def stop(self, timeout=None): """Stop the process loop.""" self.thread.stop(timeout=timeout) def run(self): logging.getLogger().info('Starting Cron Daemon.') t = datetime(*datetime.now().timetuple()[:5]) while 1: self.lock.acquire() try: logging.getLogger().log( 5, 'Checking events at ' + str(datetime.now()) + ' with ' + str(t) + '.') for e in self.events: e.check(t) finally: self.lock.release() t += timedelta(minutes=1) while datetime.now() < t: delta = (t - datetime.now()).total_seconds() self.thread.stop_request.wait(delta) if self.thread.stop_request.isSet(): return logging.getLogger().info('Shutting down Cron Daemon.')
class JobManager( ): # ToDo: In principle this need not be a singleton. Then there could be different job managers handling different sets of resources. However currently we need singleton since the JobManager is called explicitly on ManagedJob class. __metaclass__ = Singleton """Provides a queue for starting and stopping jobs according to their priority.""" def __init__(self): self.thread = StoppableThread( ) # the thread the manager loop is running in self.lock = threading.Condition( ) # lock to control access to 'queue' and 'running' self.queue = [] self.running = None self.refresh_interval = 0.1 # seconds def submit(self, job): """ Submit a job. If there is no job running, the job is appended to the queue. If the job is the running job or the job is already in the queue, do nothing. If job.priority =< priority of the running job, the job is appended to the queue and the queue sorted according to priority. If job.priority > priority of the running job, the job is inserted at the first position of the queue, the running job is stopped and inserted again at the first position of the queue. """ logging.debug('Attempt to submit job ' + str(job)) self.lock.acquire() running = self.running queue = self.queue if job is running or job in queue: logging.info('The job ' + str(job) + ' is already running or in the queue.') self.lock.release() return queue.append(job) queue.sort(cmp=lambda x, y: cmp(x.priority, y.priority), reverse=True) # ToDo: Job sorting not thoroughly tested job.state = 'wait' logging.debug('Notifying process thread.') self.lock.notify() self.lock.release() logging.debug('Job ' + str(job) + ' submitted.') def remove(self, job): """ Remove a job. If the job is running, stop it. If the job is in the queue, remove it. If the job is not found, this will result in an exception. """ logging.debug('Attempt to remove job ' + str(job)) self.lock.acquire() try: if job is self.running: logging.debug('Job ' + str(job) + ' is running. Attempt stop.') job.stop() logging.debug('Job ' + str(job) + ' removed.') else: if not job in self.queue: logging.debug('Job ' + str(job) + ' neither running nor in queue. Returning.') else: logging.debug('Job ' + str(job) + ' is in queue. Attempt remove.') self.queue.remove(job) logging.debug('Job ' + str(job) + ' removed.') job.state = 'idle' # ToDo: improve handling of state. Move handling to Job? finally: self.lock.release() def start(self): """Start the process loop in a thread.""" if self.thread.is_alive(): return logging.getLogger().info('Starting Job Manager.') self.thread = StoppableThread(target=self._process, name=self.__class__.__name__ + timestamp()) self.thread.start() def stop(self, timeout=None): """Stop the process loop.""" self.thread.stop_request.set() self.lock.acquire() self.lock.notify() self.lock.release() self.thread.stop(timeout=timeout) def _process(self): """ The process loop. Use .start() and .stop() methods to start and stop processing of the queue. """ while True: self.thread.stop_request.wait(self.refresh_interval) if self.thread.stop_request.isSet(): break # ToDo: jobs can be in queue before process loop is started # what happens when manager is stopped while jobs are running? self.lock.acquire() if self.running is None: if self.queue == []: logging.debug( 'No job running. No job in queue. Waiting for notification.' ) self.lock.wait() logging.debug('Caught notification.') if self.thread.stop_request.isSet(): self.lock.release() break logging.debug('Attempt to fetch first job in queue.') self.running = self.queue.pop(0) logging.debug('Found job ' + str(self.running) + '. Starting.') self.running.start() elif not self.running.thread.is_alive(): print '\x07' # beep logging.debug('Job ' + str(self.running) + ' stopped.') self.running = None if self.queue != []: logging.debug('Attempt to fetch first job in queue.') self.running = self.queue.pop(0) logging.debug('Found job ' + str(self.running) + '. Starting.') self.running.start() elif self.queue != [] and self.queue[ 0].priority > self.running.priority: logging.debug( 'Found job ' + str(self.queue[0]) + ' in queue with higher priority than running job. Attempt to stop running job.' ) self.running.stop() if self.running.state != 'done': logging.debug('Reinserting job ' + str(self.running) + ' in queue.') self.queue.insert(0, self.running) self.queue.sort( cmp=lambda x, y: cmp(x.priority, y.priority), reverse=True ) # ToDo: Job sorting not thoroughly tested self.running.state = 'wait' self.running = self.queue.pop(0) logging.debug('Found job ' + str(self.running) + '. Starting.') self.running.start() self.lock.release()