diff --git a/Lib/sched.py b/Lib/sched.py index ff87874a3a4..14613cf2987 100644 --- a/Lib/sched.py +++ b/Lib/sched.py @@ -26,23 +26,19 @@ has another way to reference private data (besides global variables). import time import heapq from collections import namedtuple +from itertools import count import threading from time import monotonic as _time __all__ = ["scheduler"] -class Event(namedtuple('Event', 'time, priority, action, argument, kwargs')): - __slots__ = [] - def __eq__(s, o): return (s.time, s.priority) == (o.time, o.priority) - def __lt__(s, o): return (s.time, s.priority) < (o.time, o.priority) - def __le__(s, o): return (s.time, s.priority) <= (o.time, o.priority) - def __gt__(s, o): return (s.time, s.priority) > (o.time, o.priority) - def __ge__(s, o): return (s.time, s.priority) >= (o.time, o.priority) - +Event = namedtuple('Event', 'time, priority, sequence, action, argument, kwargs') Event.time.__doc__ = ('''Numeric type compatible with the return value of the timefunc function passed to the constructor.''') Event.priority.__doc__ = ('''Events scheduled for the same time will be executed in the order of their priority.''') +Event.sequence.__doc__ = ('''A continually increasing sequence number that + separates events if time and priority are equal.''') Event.action.__doc__ = ('''Executing the event means executing action(*argument, **kwargs)''') Event.argument.__doc__ = ('''argument is a sequence holding the positional @@ -61,6 +57,7 @@ class scheduler: self._lock = threading.RLock() self.timefunc = timefunc self.delayfunc = delayfunc + self._sequence_generator = count() def enterabs(self, time, priority, action, argument=(), kwargs=_sentinel): """Enter a new event in the queue at an absolute time. @@ -71,8 +68,10 @@ class scheduler: """ if kwargs is _sentinel: kwargs = {} - event = Event(time, priority, action, argument, kwargs) + with self._lock: + event = Event(time, priority, next(self._sequence_generator), + action, argument, kwargs) heapq.heappush(self._queue, event) return event # The ID @@ -136,7 +135,8 @@ class scheduler: with lock: if not q: break - time, priority, action, argument, kwargs = q[0] + (time, priority, sequence, action, + argument, kwargs) = q[0] now = timefunc() if time > now: delay = True diff --git a/Lib/test/test_sched.py b/Lib/test/test_sched.py index 491d7b3a745..7ae7baae85e 100644 --- a/Lib/test/test_sched.py +++ b/Lib/test/test_sched.py @@ -142,6 +142,17 @@ class TestCase(unittest.TestCase): self.assertTrue(q.empty()) self.assertEqual(timer.time(), 4) + def test_cancel_correct_event(self): + # bpo-19270 + events = [] + scheduler = sched.scheduler() + scheduler.enterabs(1, 1, events.append, ("a",)) + b = scheduler.enterabs(1, 1, events.append, ("b",)) + scheduler.enterabs(1, 1, events.append, ("c",)) + scheduler.cancel(b) + scheduler.run() + self.assertEqual(events, ["a", "c"]) + def test_empty(self): l = [] fun = lambda x: l.append(x) diff --git a/Misc/NEWS.d/next/Library/2020-10-16-22-48-01.bpo-19270.jd_gkA.rst b/Misc/NEWS.d/next/Library/2020-10-16-22-48-01.bpo-19270.jd_gkA.rst new file mode 100644 index 00000000000..6330a91a44c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-10-16-22-48-01.bpo-19270.jd_gkA.rst @@ -0,0 +1,2 @@ +:meth:`sched.scheduler.cancel()` will now cancel the correct event, if two +events with same priority are scheduled for the same time. Patch by Bar Harel.