blob: 471914ceefb355d0bdb23142fba66d64a9bbb90c [file] [log] [blame]
Sebastian Janssonecb68972019-01-18 10:30:54 +01001/*
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
Jonas Olssona4d87372019-07-05 19:08:33 +020011#include "rtc_base/task_utils/repeating_task.h"
12
Sebastian Janssonecb68972019-01-18 10:30:54 +010013#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 "absl/memory/memory.h"
19#include "rtc_base/event.h"
Danil Chapovalov07122bc2019-03-26 14:37:01 +010020#include "rtc_base/task_queue_for_test.h"
Sebastian Janssonecb68972019-01-18 10:30:54 +010021#include "test/gmock.h"
22#include "test/gtest.h"
23
24// NOTE: Since these tests rely on real time behavior, they will be flaky
25// if run on heavily loaded systems.
26namespace webrtc {
27namespace {
28using ::testing::AtLeast;
29using ::testing::Invoke;
30using ::testing::MockFunction;
31using ::testing::NiceMock;
32using ::testing::Return;
33
34constexpr TimeDelta kTimeout = TimeDelta::Millis<1000>();
35
36void Sleep(TimeDelta time_delta) {
37 // Note that Chromium style guide prohibits use of <thread> and <chrono> in
38 // production code, used here since webrtc::SleepMs may return early.
39 std::this_thread::sleep_for(std::chrono::microseconds(time_delta.us()));
40}
41
42class MockClosure {
43 public:
44 MOCK_METHOD0(Call, TimeDelta());
45 MOCK_METHOD0(Delete, void());
46};
47
48class MoveOnlyClosure {
49 public:
50 explicit MoveOnlyClosure(MockClosure* mock) : mock_(mock) {}
51 MoveOnlyClosure(const MoveOnlyClosure&) = delete;
52 MoveOnlyClosure(MoveOnlyClosure&& other) : mock_(other.mock_) {
53 other.mock_ = nullptr;
54 }
55 ~MoveOnlyClosure() {
56 if (mock_)
57 mock_->Delete();
58 }
59 TimeDelta operator()() { return mock_->Call(); }
60
61 private:
62 MockClosure* mock_;
63};
Sebastian Jansson46b4a0f2019-03-26 15:24:23 +010064
65// Helper closure class to stop repeating task on a task queue. This is
66// equivalent to [handle{move(handle)}] { handle.Stop(); } in c++14.
67class TaskHandleStopper {
68 public:
69 explicit TaskHandleStopper(RepeatingTaskHandle handle)
70 : handle_(std::move(handle)) {}
71 void operator()() { handle_.Stop(); }
72
73 private:
74 RepeatingTaskHandle handle_;
75};
Sebastian Janssonecb68972019-01-18 10:30:54 +010076} // namespace
77
78TEST(RepeatingTaskTest, TaskIsStoppedOnStop) {
Sebastian Janssonc46a9992019-04-04 16:49:05 +020079 const TimeDelta kShortInterval = TimeDelta::ms(50);
80 const TimeDelta kLongInterval = TimeDelta::ms(200);
Sebastian Janssonecb68972019-01-18 10:30:54 +010081 const int kShortIntervalCount = 4;
82 const int kMargin = 1;
83
Danil Chapovalov07122bc2019-03-26 14:37:01 +010084 TaskQueueForTest task_queue("TestQueue");
Sebastian Janssonecb68972019-01-18 10:30:54 +010085 std::atomic_int counter(0);
Danil Chapovalov4423c362019-03-06 18:41:39 +010086 auto handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] {
Sebastian Janssonecb68972019-01-18 10:30:54 +010087 if (++counter >= kShortIntervalCount)
88 return kLongInterval;
89 return kShortInterval;
90 });
91 // Sleep long enough to go through the initial phase.
92 Sleep(kShortInterval * (kShortIntervalCount + kMargin));
93 EXPECT_EQ(counter.load(), kShortIntervalCount);
94
Sebastian Jansson46b4a0f2019-03-26 15:24:23 +010095 task_queue.PostTask(TaskHandleStopper(std::move(handle)));
Sebastian Janssonecb68972019-01-18 10:30:54 +010096 // Sleep long enough that the task would run at least once more if not
97 // stopped.
98 Sleep(kLongInterval * 2);
99 EXPECT_EQ(counter.load(), kShortIntervalCount);
100}
101
102TEST(RepeatingTaskTest, CompensatesForLongRunTime) {
103 const int kTargetCount = 20;
104 const int kTargetCountMargin = 2;
105 const TimeDelta kRepeatInterval = TimeDelta::ms(2);
106 // Sleeping inside the task for longer than the repeat interval once, should
107 // be compensated for by repeating the task faster to catch up.
108 const TimeDelta kSleepDuration = TimeDelta::ms(20);
109 const int kSleepAtCount = 3;
110
111 std::atomic_int counter(0);
Danil Chapovalov07122bc2019-03-26 14:37:01 +0100112 TaskQueueForTest task_queue("TestQueue");
Danil Chapovalov4423c362019-03-06 18:41:39 +0100113 RepeatingTaskHandle::Start(task_queue.Get(), [&] {
Sebastian Janssonecb68972019-01-18 10:30:54 +0100114 if (++counter == kSleepAtCount)
115 Sleep(kSleepDuration);
116 return kRepeatInterval;
117 });
118 Sleep(kRepeatInterval * kTargetCount);
119 // Execution time should not have affected the run count,
120 // but we allow some margin to reduce flakiness.
121 EXPECT_GE(counter.load(), kTargetCount - kTargetCountMargin);
122}
123
124TEST(RepeatingTaskTest, CompensatesForShortRunTime) {
125 std::atomic_int counter(0);
Danil Chapovalov07122bc2019-03-26 14:37:01 +0100126 TaskQueueForTest task_queue("TestQueue");
Danil Chapovalov4423c362019-03-06 18:41:39 +0100127 RepeatingTaskHandle::Start(task_queue.Get(), [&] {
Sebastian Janssonecb68972019-01-18 10:30:54 +0100128 ++counter;
Sebastian Janssonc46a9992019-04-04 16:49:05 +0200129 // Sleeping for the 100 ms should be compensated.
130 Sleep(TimeDelta::ms(100));
131 return TimeDelta::ms(300);
Sebastian Janssonecb68972019-01-18 10:30:54 +0100132 });
Sebastian Janssonc46a9992019-04-04 16:49:05 +0200133 Sleep(TimeDelta::ms(400));
Sebastian Janssona497d122019-02-04 16:39:28 +0100134
Sebastian Janssonecb68972019-01-18 10:30:54 +0100135 // We expect that the task have been called twice, once directly at Start and
Sebastian Janssonc46a9992019-04-04 16:49:05 +0200136 // once after 300 ms has passed.
Sebastian Janssonecb68972019-01-18 10:30:54 +0100137 EXPECT_EQ(counter.load(), 2);
138}
139
140TEST(RepeatingTaskTest, CancelDelayedTaskBeforeItRuns) {
141 rtc::Event done;
142 MockClosure mock;
143 EXPECT_CALL(mock, Call).Times(0);
144 EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); }));
Danil Chapovalov07122bc2019-03-26 14:37:01 +0100145 TaskQueueForTest task_queue("queue");
Sebastian Janssonecb68972019-01-18 10:30:54 +0100146 auto handle = RepeatingTaskHandle::DelayedStart(
Danil Chapovalov4423c362019-03-06 18:41:39 +0100147 task_queue.Get(), TimeDelta::ms(100), MoveOnlyClosure(&mock));
Sebastian Jansson46b4a0f2019-03-26 15:24:23 +0100148 task_queue.PostTask(TaskHandleStopper(std::move(handle)));
Sebastian Janssonecb68972019-01-18 10:30:54 +0100149 EXPECT_TRUE(done.Wait(kTimeout.ms()));
150}
151
152TEST(RepeatingTaskTest, CancelTaskAfterItRuns) {
153 rtc::Event done;
154 MockClosure mock;
155 EXPECT_CALL(mock, Call).WillOnce(Return(TimeDelta::ms(100)));
156 EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); }));
Danil Chapovalov07122bc2019-03-26 14:37:01 +0100157 TaskQueueForTest task_queue("queue");
Danil Chapovalov4423c362019-03-06 18:41:39 +0100158 auto handle =
159 RepeatingTaskHandle::Start(task_queue.Get(), MoveOnlyClosure(&mock));
Sebastian Jansson46b4a0f2019-03-26 15:24:23 +0100160 task_queue.PostTask(TaskHandleStopper(std::move(handle)));
Sebastian Janssonecb68972019-01-18 10:30:54 +0100161 EXPECT_TRUE(done.Wait(kTimeout.ms()));
162}
163
164TEST(RepeatingTaskTest, TaskCanStopItself) {
165 std::atomic_int counter(0);
Danil Chapovalov07122bc2019-03-26 14:37:01 +0100166 TaskQueueForTest task_queue("TestQueue");
Sebastian Janssonecb68972019-01-18 10:30:54 +0100167 RepeatingTaskHandle handle;
168 task_queue.PostTask([&] {
Danil Chapovalov4423c362019-03-06 18:41:39 +0100169 handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] {
Sebastian Janssonecb68972019-01-18 10:30:54 +0100170 ++counter;
171 handle.Stop();
172 return TimeDelta::ms(2);
173 });
174 });
175 Sleep(TimeDelta::ms(10));
176 EXPECT_EQ(counter.load(), 1);
177}
178
179TEST(RepeatingTaskTest, ZeroReturnValueRepostsTheTask) {
180 NiceMock<MockClosure> closure;
181 rtc::Event done;
182 EXPECT_CALL(closure, Call())
183 .WillOnce(Return(TimeDelta::Zero()))
184 .WillOnce(Invoke([&done] {
185 done.Set();
186 return kTimeout;
187 }));
Danil Chapovalov07122bc2019-03-26 14:37:01 +0100188 TaskQueueForTest task_queue("queue");
Danil Chapovalov4423c362019-03-06 18:41:39 +0100189 RepeatingTaskHandle::Start(task_queue.Get(), MoveOnlyClosure(&closure));
Sebastian Janssonecb68972019-01-18 10:30:54 +0100190 EXPECT_TRUE(done.Wait(kTimeout.ms()));
191}
192
193TEST(RepeatingTaskTest, StartPeriodicTask) {
194 MockFunction<TimeDelta()> closure;
195 rtc::Event done;
196 EXPECT_CALL(closure, Call())
197 .WillOnce(Return(TimeDelta::ms(20)))
198 .WillOnce(Return(TimeDelta::ms(20)))
199 .WillOnce(Invoke([&done] {
200 done.Set();
201 return kTimeout;
202 }));
Danil Chapovalov07122bc2019-03-26 14:37:01 +0100203 TaskQueueForTest task_queue("queue");
Danil Chapovalov4423c362019-03-06 18:41:39 +0100204 RepeatingTaskHandle::Start(task_queue.Get(), closure.AsStdFunction());
Sebastian Janssonecb68972019-01-18 10:30:54 +0100205 EXPECT_TRUE(done.Wait(kTimeout.ms()));
206}
207
208TEST(RepeatingTaskTest, Example) {
209 class ObjectOnTaskQueue {
210 public:
211 void DoPeriodicTask() {}
212 TimeDelta TimeUntilNextRun() { return TimeDelta::ms(100); }
213 void StartPeriodicTask(RepeatingTaskHandle* handle,
Danil Chapovalov4423c362019-03-06 18:41:39 +0100214 TaskQueueBase* task_queue) {
Sebastian Janssonecb68972019-01-18 10:30:54 +0100215 *handle = RepeatingTaskHandle::Start(task_queue, [this] {
216 DoPeriodicTask();
217 return TimeUntilNextRun();
218 });
219 }
220 };
Danil Chapovalov07122bc2019-03-26 14:37:01 +0100221 TaskQueueForTest task_queue("queue");
Sebastian Janssonecb68972019-01-18 10:30:54 +0100222 auto object = absl::make_unique<ObjectOnTaskQueue>();
223 // Create and start the periodic task.
224 RepeatingTaskHandle handle;
Danil Chapovalov4423c362019-03-06 18:41:39 +0100225 object->StartPeriodicTask(&handle, task_queue.Get());
Sebastian Janssonecb68972019-01-18 10:30:54 +0100226 // Restart the task
Sebastian Jansson46b4a0f2019-03-26 15:24:23 +0100227 task_queue.PostTask(TaskHandleStopper(std::move(handle)));
Danil Chapovalov4423c362019-03-06 18:41:39 +0100228 object->StartPeriodicTask(&handle, task_queue.Get());
Sebastian Jansson46b4a0f2019-03-26 15:24:23 +0100229 task_queue.PostTask(TaskHandleStopper(std::move(handle)));
Sebastian Janssonecb68972019-01-18 10:30:54 +0100230 struct Destructor {
231 void operator()() { object.reset(); }
232 std::unique_ptr<ObjectOnTaskQueue> object;
233 };
234 task_queue.PostTask(Destructor{std::move(object)});
235 // Do not wait for the destructor closure in order to create a race between
236 // task queue destruction and running the desctructor closure.
237}
238
239} // namespace webrtc