1 /*
2 * Copyright 2019 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "rtc_base/task_utils/repeating_task.h"
12
13 #include <atomic>
14 #include <chrono> // Not allowed in production per Chromium style guide.
15 #include <memory>
16 #include <thread> // Not allowed in production per Chromium style guide.
17
18 #include "rtc_base/event.h"
19 #include "rtc_base/task_queue_for_test.h"
20 #include "test/gmock.h"
21 #include "test/gtest.h"
22
23 // NOTE: Since these tests rely on real time behavior, they will be flaky
24 // if run on heavily loaded systems.
25 namespace webrtc {
26 namespace {
27 using ::testing::AtLeast;
28 using ::testing::Invoke;
29 using ::testing::MockFunction;
30 using ::testing::NiceMock;
31 using ::testing::Return;
32
33 constexpr TimeDelta kTimeout = TimeDelta::Millis(1000);
34
Sleep(TimeDelta time_delta)35 void Sleep(TimeDelta time_delta) {
36 // Note that Chromium style guide prohibits use of <thread> and <chrono> in
37 // production code, used here since webrtc::SleepMs may return early.
38 std::this_thread::sleep_for(std::chrono::microseconds(time_delta.us()));
39 }
40
41 class MockClosure {
42 public:
43 MOCK_METHOD(TimeDelta, Call, ());
44 MOCK_METHOD(void, Delete, ());
45 };
46
47 class MockTaskQueue : public TaskQueueBase {
48 public:
MockTaskQueue()49 MockTaskQueue() : task_queue_setter_(this) {}
50
51 MOCK_METHOD(void, Delete, (), (override));
52 MOCK_METHOD(void, PostTask, (std::unique_ptr<QueuedTask> task), (override));
53 MOCK_METHOD(void,
54 PostDelayedTask,
55 (std::unique_ptr<QueuedTask> task, uint32_t milliseconds),
56 (override));
57
58 private:
59 CurrentTaskQueueSetter task_queue_setter_;
60 };
61
62 class MoveOnlyClosure {
63 public:
MoveOnlyClosure(MockClosure * mock)64 explicit MoveOnlyClosure(MockClosure* mock) : mock_(mock) {}
65 MoveOnlyClosure(const MoveOnlyClosure&) = delete;
MoveOnlyClosure(MoveOnlyClosure && other)66 MoveOnlyClosure(MoveOnlyClosure&& other) : mock_(other.mock_) {
67 other.mock_ = nullptr;
68 }
~MoveOnlyClosure()69 ~MoveOnlyClosure() {
70 if (mock_)
71 mock_->Delete();
72 }
operator ()()73 TimeDelta operator()() { return mock_->Call(); }
74
75 private:
76 MockClosure* mock_;
77 };
78 } // namespace
79
TEST(RepeatingTaskTest,TaskIsStoppedOnStop)80 TEST(RepeatingTaskTest, TaskIsStoppedOnStop) {
81 const TimeDelta kShortInterval = TimeDelta::Millis(50);
82 const TimeDelta kLongInterval = TimeDelta::Millis(200);
83 const int kShortIntervalCount = 4;
84 const int kMargin = 1;
85
86 TaskQueueForTest task_queue("TestQueue");
87 std::atomic_int counter(0);
88 auto handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] {
89 if (++counter >= kShortIntervalCount)
90 return kLongInterval;
91 return kShortInterval;
92 });
93 // Sleep long enough to go through the initial phase.
94 Sleep(kShortInterval * (kShortIntervalCount + kMargin));
95 EXPECT_EQ(counter.load(), kShortIntervalCount);
96
97 task_queue.PostTask(
98 [handle = std::move(handle)]() mutable { handle.Stop(); });
99 // Sleep long enough that the task would run at least once more if not
100 // stopped.
101 Sleep(kLongInterval * 2);
102 EXPECT_EQ(counter.load(), kShortIntervalCount);
103 }
104
TEST(RepeatingTaskTest,CompensatesForLongRunTime)105 TEST(RepeatingTaskTest, CompensatesForLongRunTime) {
106 const int kTargetCount = 20;
107 const int kTargetCountMargin = 2;
108 const TimeDelta kRepeatInterval = TimeDelta::Millis(2);
109 // Sleeping inside the task for longer than the repeat interval once, should
110 // be compensated for by repeating the task faster to catch up.
111 const TimeDelta kSleepDuration = TimeDelta::Millis(20);
112 const int kSleepAtCount = 3;
113
114 std::atomic_int counter(0);
115 TaskQueueForTest task_queue("TestQueue");
116 RepeatingTaskHandle::Start(task_queue.Get(), [&] {
117 if (++counter == kSleepAtCount)
118 Sleep(kSleepDuration);
119 return kRepeatInterval;
120 });
121 Sleep(kRepeatInterval * kTargetCount);
122 // Execution time should not have affected the run count,
123 // but we allow some margin to reduce flakiness.
124 EXPECT_GE(counter.load(), kTargetCount - kTargetCountMargin);
125 }
126
TEST(RepeatingTaskTest,CompensatesForShortRunTime)127 TEST(RepeatingTaskTest, CompensatesForShortRunTime) {
128 std::atomic_int counter(0);
129 TaskQueueForTest task_queue("TestQueue");
130 RepeatingTaskHandle::Start(task_queue.Get(), [&] {
131 ++counter;
132 // Sleeping for the 100 ms should be compensated.
133 Sleep(TimeDelta::Millis(100));
134 return TimeDelta::Millis(300);
135 });
136 Sleep(TimeDelta::Millis(400));
137
138 // We expect that the task have been called twice, once directly at Start and
139 // once after 300 ms has passed.
140 EXPECT_EQ(counter.load(), 2);
141 }
142
TEST(RepeatingTaskTest,CancelDelayedTaskBeforeItRuns)143 TEST(RepeatingTaskTest, CancelDelayedTaskBeforeItRuns) {
144 rtc::Event done;
145 MockClosure mock;
146 EXPECT_CALL(mock, Call).Times(0);
147 EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); }));
148 TaskQueueForTest task_queue("queue");
149 auto handle = RepeatingTaskHandle::DelayedStart(
150 task_queue.Get(), TimeDelta::Millis(100), MoveOnlyClosure(&mock));
151 task_queue.PostTask(
152 [handle = std::move(handle)]() mutable { handle.Stop(); });
153 EXPECT_TRUE(done.Wait(kTimeout.ms()));
154 }
155
TEST(RepeatingTaskTest,CancelTaskAfterItRuns)156 TEST(RepeatingTaskTest, CancelTaskAfterItRuns) {
157 rtc::Event done;
158 MockClosure mock;
159 EXPECT_CALL(mock, Call).WillOnce(Return(TimeDelta::Millis(100)));
160 EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); }));
161 TaskQueueForTest task_queue("queue");
162 auto handle =
163 RepeatingTaskHandle::Start(task_queue.Get(), MoveOnlyClosure(&mock));
164 task_queue.PostTask(
165 [handle = std::move(handle)]() mutable { handle.Stop(); });
166 EXPECT_TRUE(done.Wait(kTimeout.ms()));
167 }
168
TEST(RepeatingTaskTest,TaskCanStopItself)169 TEST(RepeatingTaskTest, TaskCanStopItself) {
170 std::atomic_int counter(0);
171 TaskQueueForTest task_queue("TestQueue");
172 RepeatingTaskHandle handle;
173 task_queue.PostTask([&] {
174 handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] {
175 ++counter;
176 handle.Stop();
177 return TimeDelta::Millis(2);
178 });
179 });
180 Sleep(TimeDelta::Millis(10));
181 EXPECT_EQ(counter.load(), 1);
182 }
183
TEST(RepeatingTaskTest,ZeroReturnValueRepostsTheTask)184 TEST(RepeatingTaskTest, ZeroReturnValueRepostsTheTask) {
185 NiceMock<MockClosure> closure;
186 rtc::Event done;
187 EXPECT_CALL(closure, Call())
188 .WillOnce(Return(TimeDelta::Zero()))
189 .WillOnce(Invoke([&done] {
190 done.Set();
191 return kTimeout;
192 }));
193 TaskQueueForTest task_queue("queue");
194 RepeatingTaskHandle::Start(task_queue.Get(), MoveOnlyClosure(&closure));
195 EXPECT_TRUE(done.Wait(kTimeout.ms()));
196 }
197
TEST(RepeatingTaskTest,StartPeriodicTask)198 TEST(RepeatingTaskTest, StartPeriodicTask) {
199 MockFunction<TimeDelta()> closure;
200 rtc::Event done;
201 EXPECT_CALL(closure, Call())
202 .WillOnce(Return(TimeDelta::Millis(20)))
203 .WillOnce(Return(TimeDelta::Millis(20)))
204 .WillOnce(Invoke([&done] {
205 done.Set();
206 return kTimeout;
207 }));
208 TaskQueueForTest task_queue("queue");
209 RepeatingTaskHandle::Start(task_queue.Get(), closure.AsStdFunction());
210 EXPECT_TRUE(done.Wait(kTimeout.ms()));
211 }
212
TEST(RepeatingTaskTest,Example)213 TEST(RepeatingTaskTest, Example) {
214 class ObjectOnTaskQueue {
215 public:
216 void DoPeriodicTask() {}
217 TimeDelta TimeUntilNextRun() { return TimeDelta::Millis(100); }
218 void StartPeriodicTask(RepeatingTaskHandle* handle,
219 TaskQueueBase* task_queue) {
220 *handle = RepeatingTaskHandle::Start(task_queue, [this] {
221 DoPeriodicTask();
222 return TimeUntilNextRun();
223 });
224 }
225 };
226 TaskQueueForTest task_queue("queue");
227 auto object = std::make_unique<ObjectOnTaskQueue>();
228 // Create and start the periodic task.
229 RepeatingTaskHandle handle;
230 object->StartPeriodicTask(&handle, task_queue.Get());
231 // Restart the task
232 task_queue.PostTask(
233 [handle = std::move(handle)]() mutable { handle.Stop(); });
234 object->StartPeriodicTask(&handle, task_queue.Get());
235 task_queue.PostTask(
236 [handle = std::move(handle)]() mutable { handle.Stop(); });
237 struct Destructor {
238 void operator()() { object.reset(); }
239 std::unique_ptr<ObjectOnTaskQueue> object;
240 };
241 task_queue.PostTask(Destructor{std::move(object)});
242 // Do not wait for the destructor closure in order to create a race between
243 // task queue destruction and running the desctructor closure.
244 }
245
TEST(RepeatingTaskTest,ClockIntegration)246 TEST(RepeatingTaskTest, ClockIntegration) {
247 std::unique_ptr<QueuedTask> delayed_task;
248 uint32_t expected_ms = 0;
249 SimulatedClock clock(Timestamp::Millis(0));
250
251 NiceMock<MockTaskQueue> task_queue;
252 ON_CALL(task_queue, PostDelayedTask)
253 .WillByDefault(
254 Invoke([&delayed_task, &expected_ms](std::unique_ptr<QueuedTask> task,
255 uint32_t milliseconds) {
256 EXPECT_EQ(milliseconds, expected_ms);
257 delayed_task = std::move(task);
258 }));
259
260 expected_ms = 100;
261 RepeatingTaskHandle handle = RepeatingTaskHandle::DelayedStart(
262 &task_queue, TimeDelta::Millis(100),
263 [&clock]() {
264 EXPECT_EQ(Timestamp::Millis(100), clock.CurrentTime());
265 // Simulate work happening for 10ms.
266 clock.AdvanceTimeMilliseconds(10);
267 return TimeDelta::Millis(100);
268 },
269 &clock);
270
271 clock.AdvanceTimeMilliseconds(100);
272 QueuedTask* task_to_run = delayed_task.release();
273 expected_ms = 90;
274 EXPECT_FALSE(task_to_run->Run());
275 EXPECT_NE(nullptr, delayed_task.get());
276 handle.Stop();
277 }
278
279 } // namespace webrtc
280