Skip to content

Commit 6d38158

Browse files
committed
SERVER-42623 Remove events from scheduler using is operator.
1 parent 45b43da commit 6d38158

File tree

3 files changed

+76
-2
lines changed

3 files changed

+76
-2
lines changed

buildscripts/resmokelib/logging/flush.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
"""
55

66
import logging
7-
import sched
87
import threading
98
import time
109

10+
from ..utils import scheduler
11+
1112
_FLUSH_THREAD_LOCK = threading.Lock()
1213
_FLUSH_THREAD = None
1314

@@ -93,7 +94,7 @@ def interruptible_sleep(secs):
9394
self.__schedule_updated.wait(secs)
9495
self.__schedule_updated.clear()
9596

96-
self.__scheduler = sched.scheduler(time.time, interruptible_sleep)
97+
self.__scheduler = scheduler.Scheduler(time.monotonic, interruptible_sleep)
9798
self.__schedule_updated = threading.Event()
9899
self.__should_stop = threading.Event()
99100
self.__terminated = threading.Event()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Version of sched.scheduler with a fixed cancel() method."""
2+
3+
import heapq
4+
import sched
5+
6+
7+
class Scheduler(sched.scheduler):
8+
"""A thread-safe, general purpose event scheduler."""
9+
10+
def cancel(self, event):
11+
"""Remove an event from the queue.
12+
13+
Raises a ValueError if the event is not in the queue.
14+
"""
15+
16+
# The changes from https://hg.python.org/cpython/rev/d8802b055474 made it so sched.Event
17+
# instances returned by sched.scheduler.enter() and sched.scheduler.enterabs() are treated
18+
# as equal if they have the same (time, priority). It is therefore possible to remove the
19+
# wrong event from the list when sched.scheduler.cancel() is called. Note that this is still
20+
# true even with time.monotonic being the default timefunc, as GetTickCount64() on Windows
21+
# only has a resolution of ~15ms. We therefore use the `is` operator to remove the correct
22+
# event from the list.
23+
with self._lock:
24+
for i in range(len(self._queue)):
25+
if self._queue[i] is event:
26+
del self._queue[i]
27+
heapq.heapify(self._queue)
28+
return
29+
30+
raise ValueError("event not in list")
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Unit tests for buildscripts/resmokelib/utils/scheduler.py."""
2+
3+
import sched
4+
import unittest
5+
6+
from buildscripts.resmokelib.utils import scheduler as _scheduler
7+
8+
# pylint: disable=missing-docstring
9+
10+
11+
def noop():
12+
pass
13+
14+
15+
class TestScheduler(unittest.TestCase):
16+
"""Unit tests for the Scheduler class."""
17+
scheduler = _scheduler.Scheduler
18+
19+
def setUp(self):
20+
self.__scheduler = self.scheduler()
21+
22+
def test_cancel_with_identical_time_and_priority(self):
23+
event1 = self.__scheduler.enterabs(time=0, priority=0, action=noop)
24+
event2 = self.__scheduler.enterabs(time=0, priority=0, action=noop)
25+
26+
self.__scheduler.cancel(event1)
27+
self.assertIs(self.__scheduler.queue[0], event2)
28+
29+
# Attempting to cancel the same event should fail because it has already been removed.
30+
with self.assertRaises(ValueError):
31+
self.__scheduler.cancel(event1)
32+
33+
self.__scheduler.cancel(event2)
34+
self.assertEqual(self.__scheduler.queue, [])
35+
36+
37+
class TestBuiltinScheduler(TestScheduler):
38+
"""Unit tests for the sched.scheduler class."""
39+
scheduler = sched.scheduler
40+
41+
def test_cancel_with_identical_time_and_priority(self):
42+
with self.assertRaises(AssertionError):
43+
super().test_cancel_with_identical_time_and_priority()

0 commit comments

Comments
 (0)