Skip to content

Commit d5ce32c

Browse files
committed
removed Job.run_immediately and related code
1 parent 09ddc1b commit d5ce32c

File tree

2 files changed

+49
-283
lines changed

2 files changed

+49
-283
lines changed

telegram/ext/jobqueue.py

Lines changed: 49 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,9 @@
2323
import warnings
2424
import datetime
2525
from numbers import Number
26-
from threading import Thread, Lock, RLock, Event
26+
from threading import Thread, Lock, Event
2727
from queue import PriorityQueue, Empty
2828

29-
from telegram.utils.promise import Promise
30-
3129

3230
class Days(object):
3331
MON, TUE, WED, THU, FRI, SAT, SUN = range(7)
@@ -58,7 +56,6 @@ def __init__(self, bot, prevent_autostart=None):
5856
self.logger = logging.getLogger(self.__class__.__name__)
5957
self.__start_lock = Lock()
6058
self.__next_peek_lock = Lock() # to protect self._next_peek & self.__tick
61-
self.__queue_lock = RLock() # to protect self.queue
6259
self.__tick = Event()
6360
self.__thread = None
6461
""":type: Thread"""
@@ -132,8 +129,7 @@ def _put(self, job, next_t=None, last_t=None):
132129

133130
self.logger.debug('Putting job %s with t=%f', job.name, next_t)
134131

135-
with self.__queue_lock:
136-
self.queue.put((next_t, job))
132+
self.queue.put((next_t, job))
137133

138134
# Wake up the loop if this job should be executed next
139135
self._set_next_peek(next_t)
@@ -227,47 +223,6 @@ def daily_job(self, callback, time, days=Days.EVERY_DAY, context=None, name=None
227223
self._put(job, next_t=time)
228224
return job
229225

230-
def update_job_due_time(self, job, due):
231-
"""
232-
Changes the due time of a job.
233-
234-
Args:
235-
job (Job): The job of which the due time should be changed. Must already exist in the
236-
queue.
237-
due (float): The new due time as a timestamp
238-
239-
Returns:
240-
float: The old due time of the job as a timestamp
241-
"""
242-
243-
with self.__queue_lock:
244-
cache = list()
245-
# Pop jobs from the priority queue one by one and check if it's the one we need.
246-
# All other jobs are stashed away in the 'cache' list. Once the right job is found,
247-
# it's put back into the queue with the new due time, together with all cached jobs.
248-
try:
249-
while True:
250-
due_test, job_test = self.queue.get(block=False)
251-
252-
if job_test is job:
253-
self.queue.put((due, job_test))
254-
old_due = due_test
255-
break
256-
257-
else:
258-
cache.append((due_test, job_test))
259-
260-
except Empty:
261-
raise ValueError("Specified 'job' doesn't exist in the job queue")
262-
263-
finally:
264-
for item in reversed(cache):
265-
self.queue.put(item)
266-
267-
# Make sure the queue wakes up if the updated due time requires it
268-
self._set_next_peek(due)
269-
return old_due
270-
271226
def _set_next_peek(self, t):
272227
"""
273228
Set next peek if not defined or `t` is before next peek.
@@ -283,56 +238,55 @@ def tick(self):
283238
Run all jobs that are due and re-enqueue them with their interval.
284239
285240
"""
286-
with self.__queue_lock:
287-
now = time.time()
241+
now = time.time()
288242

289-
self.logger.debug('Ticking jobs with t=%f', now)
243+
self.logger.debug('Ticking jobs with t=%f', now)
290244

291-
while True:
245+
while True:
246+
try:
247+
t, job = self.queue.get(False)
248+
249+
except Empty:
250+
break
251+
252+
self.logger.debug('Peeked at %s with t=%f', job.name, t)
253+
254+
if t > now:
255+
# We can get here in two conditions:
256+
# 1. At the second or later pass of the while loop, after we've already
257+
# processed the job(s) we were supposed to at this time.
258+
# 2. At the first iteration of the loop only if `self.put()` had triggered
259+
# `self.__tick` because `self._next_peek` wasn't set
260+
self.logger.debug("Next task isn't due yet. Finished!")
261+
self.queue.put((t, job))
262+
self._set_next_peek(t)
263+
break
264+
265+
if job._remove.is_set():
266+
self.logger.debug('Removing job %s', job.name)
267+
job.job_queue = None
268+
continue
269+
270+
if job.enabled:
292271
try:
293-
t, job = self.queue.get(False)
294-
295-
except Empty:
296-
break
297-
298-
self.logger.debug('Peeked at %s with t=%f', job.name, t)
299-
300-
if t > now:
301-
# We can get here in two conditions:
302-
# 1. At the second or later pass of the while loop, after we've already
303-
# processed the job(s) we were supposed to at this time.
304-
# 2. At the first iteration of the loop only if `self.put()` had triggered
305-
# `self.__tick` because `self._next_peek` wasn't set
306-
self.logger.debug("Next task isn't due yet. Finished!")
307-
self.queue.put((t, job))
308-
self._set_next_peek(t)
309-
break
310-
311-
if job._remove.is_set():
312-
self.logger.debug('Removing job %s', job.name)
313-
job.job_queue = None
314-
continue
315-
316-
if job.enabled:
317-
try:
318-
current_week_day = datetime.datetime.now().weekday()
319-
if any(day == current_week_day for day in job.days):
320-
self.logger.debug('Running job %s', job.name)
321-
job.run(self.bot)
322-
323-
except:
324-
self.logger.exception(
325-
'An uncaught error was raised while executing job %s', job.name)
326-
327-
else:
328-
self.logger.debug('Skipping disabled job %s', job.name)
329-
330-
if job.repeat:
331-
self._put(job, last_t=t)
332-
333-
else:
334-
self.logger.debug('Dropping non-repeating job %s', job.name)
335-
job.job_queue = None
272+
current_week_day = datetime.datetime.now().weekday()
273+
if any(day == current_week_day for day in job.days):
274+
self.logger.debug('Running job %s', job.name)
275+
job.run(self.bot)
276+
277+
except:
278+
self.logger.exception('An uncaught error was raised while executing job %s',
279+
job.name)
280+
281+
else:
282+
self.logger.debug('Skipping disabled job %s', job.name)
283+
284+
if job.repeat:
285+
self._put(job, last_t=t)
286+
287+
else:
288+
self.logger.debug('Dropping non-repeating job %s', job.name)
289+
job.job_queue = None
336290

337291
def start(self):
338292
"""
@@ -387,8 +341,7 @@ def stop(self):
387341

388342
def jobs(self):
389343
"""Returns a tuple of all jobs that are currently in the ``JobQueue``"""
390-
with self.__queue_lock:
391-
return tuple(job[1] for job in self.queue.queue if job)
344+
return tuple(job[1] for job in self.queue.queue if job)
392345

393346

394347
class Job(object):
@@ -446,7 +399,6 @@ def __init__(self,
446399
self._remove = Event()
447400
self._enabled = Event()
448401
self._enabled.set()
449-
self._immediate_run_in_progress = Event()
450402

451403
def run(self, bot):
452404
"""Executes the callback function"""
@@ -459,99 +411,6 @@ def schedule_removal(self):
459411
"""
460412
self._remove.set()
461413

462-
def _wrap_callback(self, old_due_promise, keep_schedule, skip_next):
463-
"""Wraps the callback function into two other functions and returns the result"""
464-
original_callback = self.callback
465-
466-
def rescheduled(bot, job):
467-
"""The callback function for the rescheduled run"""
468-
# Run the original callback function
469-
original_callback(bot, job)
470-
471-
# If job is non-repeating, restore the callback, ignore everything else and bail
472-
if not self.repeat:
473-
self.callback = original_callback
474-
return
475-
476-
# Adjust the interval so that the next run is scheduled like it was before
477-
if keep_schedule:
478-
original_interval = self.interval
479-
480-
try:
481-
old_due = old_due_promise.result(timeout=1.0)
482-
483-
except TimeoutError:
484-
# Possible race condition when the JobQueue wants to run this job before it
485-
# could run the Promise in the main thread. The JobQueue thread waits for
486-
# the Promise to resolve, but the main thread waits for the JobQueue to release
487-
# the __queue_lock so it can reschedule this job.
488-
# In case that happens, simply wrap the callback function again and pretend
489-
# like nothing happened yet.
490-
self.callback = original_callback
491-
self.callback = self._wrap_callback(old_due_promise, keep_schedule, skip_next)
492-
return
493-
494-
self.interval = old_due - time.time()
495-
496-
def next_callback(bot, job):
497-
"""The callback function for the run that was originally next"""
498-
499-
# Restore callback and interval
500-
self.callback = original_callback
501-
502-
if keep_schedule:
503-
self.interval = original_interval
504-
505-
if not skip_next:
506-
original_callback(bot, job)
507-
508-
self._immediate_run_in_progress.clear()
509-
510-
self.callback = next_callback
511-
512-
return rescheduled
513-
514-
def run_immediately(self, keep_schedule=True, skip_next=True):
515-
"""
516-
Puts this job to the front of the job queue, scheduled to run immediately.
517-
518-
Args:
519-
keep_schedule (Optional[bool]): If set to ``True``, which is the default, the job will
520-
be re-scheduled so that the original schedule (as defined by the starting time and
521-
interval) is not changed. If set to ``False``, the schedule will reset so that the
522-
next scheduled time is calculated from the current time and the interval.
523-
This parameter is ignored if the job is not repeating.
524-
skip_next (Optional[bool]): If set to ``True``, which is the default, the next
525-
scheduled execution will be skipped, effectively re-scheduling it to now.
526-
If set to ``False``, this execution is an additional, completely unscheduled
527-
execution.
528-
This parameter is ignored if the job is not repeating.
529-
530-
Raises:
531-
NotImplementedError: If you use this method a second time before the job either ran
532-
the originally scheduled execution once, or has skipped it.
533-
ValueError: If the job is disabled at the moment
534-
"""
535-
if self._immediate_run_in_progress.is_set():
536-
raise NotImplementedError("You can't use 'run_immediately' again until the job has "
537-
"returned to it's normal state. Sorry about that.")
538-
539-
if not self.enabled:
540-
raise ValueError("Job is disabled")
541-
542-
self._immediate_run_in_progress.set()
543-
# Wrap the old due time in a Promise
544-
old_due_promise = Promise(
545-
self.job_queue.update_job_due_time, # pylint: disable=no-member
546-
[self, time.time()],
547-
{})
548-
549-
# Wrap the callback in the wrapper function
550-
self.callback = self._wrap_callback(old_due_promise, keep_schedule, skip_next)
551-
552-
# Run the promise to reschedule the job
553-
old_due_promise.run()
554-
555414
@property
556415
def enabled(self):
557416
return self._enabled.is_set()

tests/test_jobqueue.py

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -258,99 +258,6 @@ def test_daily_job(self):
258258
self.assertEqual(self.result, 1)
259259
self.assertAlmostEqual(self.jq.queue.get(False)[0], expected_time, delta=0.1)
260260

261-
def test_update_job_due_time(self):
262-
job = self.jq.one_time_job(self.job1, datetime.datetime(2030, 1, 1))
263-
264-
sleep(0.5)
265-
self.assertEqual(self.result, 0)
266-
267-
self.jq.update_job_due_time(job, time.time() + 1)
268-
269-
sleep(0.5)
270-
self.assertEqual(self.result, 0)
271-
272-
sleep(1.5)
273-
self.assertEqual(self.result, 1)
274-
275-
def test_job_run_immediately_one_time(self):
276-
job = self.jq.one_time_job(self.job1, datetime.datetime(2030, 1, 1))
277-
278-
sleep(0.5)
279-
self.assertEqual(self.result, 0)
280-
281-
job.run_immediately()
282-
283-
sleep(0.5)
284-
self.assertEqual(self.result, 1)
285-
286-
def test_job_run_immediately_skip(self):
287-
job = self.jq.repeating_job(self.job1, 1, first=2)
288-
289-
sleep(0.5) # 0.5s | no runs
290-
self.assertEqual(self.result, 0)
291-
292-
job.run_immediately(keep_schedule=False, skip_next=True)
293-
294-
sleep(0.5) # 1s | first run at 0.5s, rescheduled with interval=1 but skipping the next run
295-
self.assertEqual(self.result, 1)
296-
297-
sleep(1) # 2s | run at 1.5s was skipped
298-
self.assertEqual(self.result, 1)
299-
300-
sleep(1) # 3s | run at 2.5s was back to normal
301-
self.assertEqual(self.result, 2)
302-
303-
sleep(1) # 4s | just to confirm
304-
self.assertEqual(self.result, 3)
305-
306-
def test_job_run_immediately_keep(self):
307-
job = self.jq.repeating_job(self.job1, 1)
308-
309-
sleep(0.5) # 0.5s | no runs
310-
self.assertEqual(self.result, 0)
311-
312-
job.run_immediately(keep_schedule=True, skip_next=False)
313-
314-
# 0.75s | first run at 0.5s, rescheduled with interval=0.5 to keep up with the schedule
315-
sleep(0.25)
316-
self.assertEqual(self.result, 1)
317-
318-
sleep(0.5) # 1.25s | run at 1s, rescheduled with interval=1
319-
self.assertEqual(self.result, 2)
320-
321-
sleep(0.5) # 1.75s | last run still at 1s
322-
self.assertEqual(self.result, 2)
323-
324-
sleep(1) # 2.25s | run at 2s was back to normal
325-
self.assertEqual(self.result, 3)
326-
327-
sleep(1) # 3.25s | just to confirm
328-
self.assertEqual(self.result, 4)
329-
330-
def test_job_run_immediately_keep_skip(self):
331-
job = self.jq.repeating_job(self.job1, 1)
332-
333-
sleep(0.5) # 0.5s | no runs
334-
self.assertEqual(self.result, 0)
335-
336-
job.run_immediately(keep_schedule=True, skip_next=True)
337-
338-
# 0.75s | first run at 0.5s, rescheduled with interval=0.5 to keep up with the schedule...
339-
sleep(0.25)
340-
self.assertEqual(self.result, 1)
341-
342-
sleep(0.5) # 1.25s | ...but run at 1s was skipped, rescheduled with interval=1
343-
self.assertEqual(self.result, 1)
344-
345-
sleep(0.5) # 1.75s | last run still at 1s
346-
self.assertEqual(self.result, 1)
347-
348-
sleep(1) # 2.25s | the run at 2s was back to normal
349-
self.assertEqual(self.result, 2)
350-
351-
sleep(1) # 3.25s | just to confirm
352-
self.assertEqual(self.result, 3)
353-
354261

355262
if __name__ == '__main__':
356263
unittest.main()

0 commit comments

Comments
 (0)