def test_acquire_lock(self): """ When scheduler acquires a lock, besides creating a key, it should also set an expiry that's a few seconds longer than it's polling interval so it automatically expires if scheduler is unexpectedly terminated. """ key = '%s_lock' % Scheduler.scheduler_key self.assertNotIn(key, tl(self.testconn.keys('*'))) scheduler = Scheduler(connection=self.testconn, interval=20) self.assertTrue(scheduler.acquire_lock()) self.assertIn(key, tl(self.testconn.keys('*'))) self.assertEqual(self.testconn.ttl(key), 30) scheduler.remove_lock() self.assertNotIn(key, tl(self.testconn.keys('*')))
def test_lock_handover_between_multiple_schedulers(self): lock_key = Scheduler.scheduler_lock_key self.assertNotIn(lock_key, tl(self.testconn.keys('*'))) scheduler1 = Scheduler(connection=self.testconn, interval=20) scheduler2 = Scheduler(connection=self.testconn, interval=20) scheduler1.register_birth() scheduler1.acquire_lock() scheduler2.register_birth() scheduler2.acquire_lock() # Both schedulers are still active/registered self.assertIn(scheduler1.key, tl(self.testconn.keys('*'))) self.assertIn(scheduler2.key, tl(self.testconn.keys('*'))) scheduler1.remove_lock() self.assertNotIn(lock_key, tl(self.testconn.keys('*'))) scheduler2.acquire_lock() self.assertIn(lock_key, tl(self.testconn.keys('*')))
def test_no_two_schedulers_acquire_lock(self): """ Ensure that no two schedulers can acquire the lock at the same time. When removing the lock, only the scheduler which originally acquired the lock can remove the lock. """ key = '%s_lock' % Scheduler.scheduler_key self.assertNotIn(key, tl(self.testconn.keys('*'))) scheduler1 = Scheduler(connection=self.testconn, interval=20) scheduler2 = Scheduler(connection=self.testconn, interval=20) self.assertTrue(scheduler1.acquire_lock()) self.assertFalse(scheduler2.acquire_lock()) self.assertIn(key, tl(self.testconn.keys('*'))) scheduler2.remove_lock() self.assertIn(key, tl(self.testconn.keys('*'))) scheduler1.remove_lock() self.assertNotIn(key, tl(self.testconn.keys('*')))
class QueueScheduler: def __init__(self, queue_name, app): self.app = app self.logger = self.app.logger.bind(queue_name=queue_name) self.scheduler = Scheduler(queue_name=queue_name, connection=app.redis) def move_jobs(self): if self.scheduler.acquire_lock(): try: jobs = self.scheduler.get_jobs() self.logger.debug("Lock acquired. Enqueuing scheduled jobs...", jobs=jobs) self.scheduler.enqueue_jobs() finally: self.scheduler.remove_lock() else: self.logger.debug( "Lock could not be acquired. Enqueuing scheduled jobs skipped. Trying again next cycle." )
def test_small_float_interval(self): """ Test that scheduler accepts 'interval' of type float, less than 1 second. """ key = Scheduler.scheduler_key lock_key = '%s_lock' % Scheduler.scheduler_key self.assertNotIn(key, tl(self.testconn.keys('*'))) scheduler = Scheduler(connection=self.testconn, interval=0.1) # testing interval = 0.1 second self.assertEqual(scheduler._interval, 0.1) #acquire lock self.assertTrue(scheduler.acquire_lock()) self.assertIn(lock_key, tl(self.testconn.keys('*'))) self.assertEqual(self.testconn.ttl(lock_key), 10) # int(0.1) + 10 = 10 #enqueue a job now = datetime.utcnow() job = scheduler.enqueue_at(now, say_hello) self.assertIn(job, self.scheduler.get_jobs_to_queue()) self.assertEqual(len(list(self.scheduler.get_jobs())), 1) #remove the lock scheduler.remove_lock() #test that run works with the small floating-point interval def send_stop_signal(): """ Sleep for 1 second, then send a INT signal to ourself, so the signal handler installed by scheduler.run() is called. """ time.sleep(1) os.kill(os.getpid(), signal.SIGINT) thread = Thread(target=send_stop_signal) thread.start() self.assertRaises(SystemExit, scheduler.run) thread.join() #all jobs must have been scheduled during 1 second self.assertEqual(len(list(scheduler.get_jobs())), 0)