1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/task/cancelable_task_tracker.h"
6
7 #include <cstddef>
8
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/memory/weak_ptr.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/run_loop.h"
17 #include "base/single_thread_task_runner.h"
18 #include "base/test/gtest_util.h"
19 #include "base/test/test_simple_task_runner.h"
20 #include "base/threading/thread.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22
23 namespace base {
24
25 namespace {
26
27 class CancelableTaskTrackerTest : public testing::Test {
28 protected:
~CancelableTaskTrackerTest()29 ~CancelableTaskTrackerTest() override { RunCurrentLoopUntilIdle(); }
30
RunCurrentLoopUntilIdle()31 void RunCurrentLoopUntilIdle() {
32 RunLoop run_loop;
33 run_loop.RunUntilIdle();
34 }
35
36 CancelableTaskTracker task_tracker_;
37
38 private:
39 // Needed by CancelableTaskTracker methods.
40 MessageLoop message_loop_;
41 };
42
AddFailureAt(const Location & location)43 void AddFailureAt(const Location& location) {
44 ADD_FAILURE_AT(location.file_name(), location.line_number());
45 }
46
47 // Returns a closure that fails if run.
MakeExpectedNotRunClosure(const Location & location)48 Closure MakeExpectedNotRunClosure(const Location& location) {
49 return Bind(&AddFailureAt, location);
50 }
51
52 // A helper class for MakeExpectedRunClosure() that fails if it is
53 // destroyed without Run() having been called. This class may be used
54 // from multiple threads as long as Run() is called at most once
55 // before destruction.
56 class RunChecker {
57 public:
RunChecker(const Location & location)58 explicit RunChecker(const Location& location)
59 : location_(location), called_(false) {}
60
~RunChecker()61 ~RunChecker() {
62 if (!called_) {
63 ADD_FAILURE_AT(location_.file_name(), location_.line_number());
64 }
65 }
66
Run()67 void Run() { called_ = true; }
68
69 private:
70 Location location_;
71 bool called_;
72 };
73
74 // Returns a closure that fails on destruction if it hasn't been run.
MakeExpectedRunClosure(const Location & location)75 Closure MakeExpectedRunClosure(const Location& location) {
76 return Bind(&RunChecker::Run, Owned(new RunChecker(location)));
77 }
78
79 } // namespace
80
81 // With the task tracker, post a task, a task with a reply, and get a
82 // new task id without canceling any of them. The tasks and the reply
83 // should run and the "is canceled" callback should return false.
TEST_F(CancelableTaskTrackerTest,NoCancel)84 TEST_F(CancelableTaskTrackerTest, NoCancel) {
85 Thread worker_thread("worker thread");
86 ASSERT_TRUE(worker_thread.Start());
87
88 ignore_result(task_tracker_.PostTask(worker_thread.task_runner().get(),
89 FROM_HERE,
90 MakeExpectedRunClosure(FROM_HERE)));
91
92 ignore_result(task_tracker_.PostTaskAndReply(
93 worker_thread.task_runner().get(), FROM_HERE,
94 MakeExpectedRunClosure(FROM_HERE), MakeExpectedRunClosure(FROM_HERE)));
95
96 CancelableTaskTracker::IsCanceledCallback is_canceled;
97 ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled));
98
99 worker_thread.Stop();
100
101 RunCurrentLoopUntilIdle();
102
103 EXPECT_FALSE(is_canceled.Run());
104 }
105
106 // Post a task with the task tracker but cancel it before running the
107 // task runner. The task should not run.
TEST_F(CancelableTaskTrackerTest,CancelPostedTask)108 TEST_F(CancelableTaskTrackerTest, CancelPostedTask) {
109 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
110 new TestSimpleTaskRunner());
111
112 CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask(
113 test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE));
114 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
115
116 EXPECT_EQ(1U, test_task_runner->NumPendingTasks());
117
118 task_tracker_.TryCancel(task_id);
119
120 test_task_runner->RunUntilIdle();
121 }
122
123 // Post a task with reply with the task tracker and cancel it before
124 // running the task runner. Neither the task nor the reply should
125 // run.
TEST_F(CancelableTaskTrackerTest,CancelPostedTaskAndReply)126 TEST_F(CancelableTaskTrackerTest, CancelPostedTaskAndReply) {
127 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
128 new TestSimpleTaskRunner());
129
130 CancelableTaskTracker::TaskId task_id =
131 task_tracker_.PostTaskAndReply(test_task_runner.get(),
132 FROM_HERE,
133 MakeExpectedNotRunClosure(FROM_HERE),
134 MakeExpectedNotRunClosure(FROM_HERE));
135 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
136
137 task_tracker_.TryCancel(task_id);
138
139 test_task_runner->RunUntilIdle();
140 }
141
142 // Post a task with reply with the task tracker and cancel it after
143 // running the task runner but before running the current message
144 // loop. The task should run but the reply should not.
TEST_F(CancelableTaskTrackerTest,CancelReply)145 TEST_F(CancelableTaskTrackerTest, CancelReply) {
146 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
147 new TestSimpleTaskRunner());
148
149 CancelableTaskTracker::TaskId task_id =
150 task_tracker_.PostTaskAndReply(test_task_runner.get(),
151 FROM_HERE,
152 MakeExpectedRunClosure(FROM_HERE),
153 MakeExpectedNotRunClosure(FROM_HERE));
154 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
155
156 test_task_runner->RunUntilIdle();
157
158 task_tracker_.TryCancel(task_id);
159 }
160
161 // Post a task with reply with the task tracker on a worker thread and
162 // cancel it before running the current message loop. The task should
163 // run but the reply should not.
TEST_F(CancelableTaskTrackerTest,CancelReplyDifferentThread)164 TEST_F(CancelableTaskTrackerTest, CancelReplyDifferentThread) {
165 Thread worker_thread("worker thread");
166 ASSERT_TRUE(worker_thread.Start());
167
168 CancelableTaskTracker::TaskId task_id = task_tracker_.PostTaskAndReply(
169 worker_thread.task_runner().get(), FROM_HERE, DoNothing(),
170 MakeExpectedNotRunClosure(FROM_HERE));
171 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
172
173 task_tracker_.TryCancel(task_id);
174
175 worker_thread.Stop();
176 }
177
ExpectIsCanceled(const CancelableTaskTracker::IsCanceledCallback & is_canceled,bool expected_is_canceled)178 void ExpectIsCanceled(
179 const CancelableTaskTracker::IsCanceledCallback& is_canceled,
180 bool expected_is_canceled) {
181 EXPECT_EQ(expected_is_canceled, is_canceled.Run());
182 }
183
184 // Create a new task ID and check its status on a separate thread
185 // before and after canceling. The is-canceled callback should be
186 // thread-safe (i.e., nothing should blow up).
TEST_F(CancelableTaskTrackerTest,NewTrackedTaskIdDifferentThread)187 TEST_F(CancelableTaskTrackerTest, NewTrackedTaskIdDifferentThread) {
188 CancelableTaskTracker::IsCanceledCallback is_canceled;
189 CancelableTaskTracker::TaskId task_id =
190 task_tracker_.NewTrackedTaskId(&is_canceled);
191
192 EXPECT_FALSE(is_canceled.Run());
193
194 Thread other_thread("other thread");
195 ASSERT_TRUE(other_thread.Start());
196 other_thread.task_runner()->PostTask(
197 FROM_HERE, BindOnce(&ExpectIsCanceled, is_canceled, false));
198 other_thread.Stop();
199
200 task_tracker_.TryCancel(task_id);
201
202 ASSERT_TRUE(other_thread.Start());
203 other_thread.task_runner()->PostTask(
204 FROM_HERE, BindOnce(&ExpectIsCanceled, is_canceled, true));
205 other_thread.Stop();
206 }
207
208 // With the task tracker, post a task, a task with a reply, get a new
209 // task id, and then cancel all of them. None of the tasks nor the
210 // reply should run and the "is canceled" callback should return
211 // true.
TEST_F(CancelableTaskTrackerTest,CancelAll)212 TEST_F(CancelableTaskTrackerTest, CancelAll) {
213 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
214 new TestSimpleTaskRunner());
215
216 ignore_result(task_tracker_.PostTask(
217 test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE)));
218
219 ignore_result(
220 task_tracker_.PostTaskAndReply(test_task_runner.get(),
221 FROM_HERE,
222 MakeExpectedNotRunClosure(FROM_HERE),
223 MakeExpectedNotRunClosure(FROM_HERE)));
224
225 CancelableTaskTracker::IsCanceledCallback is_canceled;
226 ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled));
227
228 task_tracker_.TryCancelAll();
229
230 test_task_runner->RunUntilIdle();
231
232 RunCurrentLoopUntilIdle();
233
234 EXPECT_TRUE(is_canceled.Run());
235 }
236
237 // With the task tracker, post a task, a task with a reply, get a new
238 // task id, and then cancel all of them. None of the tasks nor the
239 // reply should run and the "is canceled" callback should return
240 // true.
TEST_F(CancelableTaskTrackerTest,DestructionCancelsAll)241 TEST_F(CancelableTaskTrackerTest, DestructionCancelsAll) {
242 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
243 new TestSimpleTaskRunner());
244
245 CancelableTaskTracker::IsCanceledCallback is_canceled;
246
247 {
248 // Create another task tracker with a smaller scope.
249 CancelableTaskTracker task_tracker;
250
251 ignore_result(task_tracker.PostTask(test_task_runner.get(),
252 FROM_HERE,
253 MakeExpectedNotRunClosure(FROM_HERE)));
254
255 ignore_result(
256 task_tracker.PostTaskAndReply(test_task_runner.get(),
257 FROM_HERE,
258 MakeExpectedNotRunClosure(FROM_HERE),
259 MakeExpectedNotRunClosure(FROM_HERE)));
260
261 ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled));
262 }
263
264 test_task_runner->RunUntilIdle();
265
266 RunCurrentLoopUntilIdle();
267
268 EXPECT_FALSE(is_canceled.Run());
269 }
270
271 // Post a task and cancel it. HasTrackedTasks() should return false as soon as
272 // TryCancelAll() is called.
TEST_F(CancelableTaskTrackerTest,HasTrackedTasksPost)273 TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPost) {
274 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
275 new TestSimpleTaskRunner());
276
277 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
278
279 ignore_result(task_tracker_.PostTask(
280 test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE)));
281
282 task_tracker_.TryCancelAll();
283
284 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
285
286 test_task_runner->RunUntilIdle();
287 RunCurrentLoopUntilIdle();
288 }
289
290 // Post a task with a reply and cancel it. HasTrackedTasks() should return false
291 // as soon as TryCancelAll() is called.
TEST_F(CancelableTaskTrackerTest,HasTrackedTasksPostWithReply)292 TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPostWithReply) {
293 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
294 new TestSimpleTaskRunner());
295
296 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
297
298 ignore_result(
299 task_tracker_.PostTaskAndReply(test_task_runner.get(),
300 FROM_HERE,
301 MakeExpectedNotRunClosure(FROM_HERE),
302 MakeExpectedNotRunClosure(FROM_HERE)));
303
304 task_tracker_.TryCancelAll();
305
306 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
307
308 test_task_runner->RunUntilIdle();
309 RunCurrentLoopUntilIdle();
310 }
311
312 // Create a new tracked task ID. HasTrackedTasks() should return false as soon
313 // as TryCancelAll() is called.
TEST_F(CancelableTaskTrackerTest,HasTrackedTasksIsCancelled)314 TEST_F(CancelableTaskTrackerTest, HasTrackedTasksIsCancelled) {
315 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
316
317 CancelableTaskTracker::IsCanceledCallback is_canceled;
318 ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled));
319
320 task_tracker_.TryCancelAll();
321
322 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
323 }
324
325 // The death tests below make sure that calling task tracker member
326 // functions from a thread different from its owner thread DCHECKs in
327 // debug mode.
328
329 class CancelableTaskTrackerDeathTest : public CancelableTaskTrackerTest {
330 protected:
CancelableTaskTrackerDeathTest()331 CancelableTaskTrackerDeathTest() {
332 // The default style "fast" does not support multi-threaded tests.
333 ::testing::FLAGS_gtest_death_test_style = "threadsafe";
334 }
335 };
336
337 // Runs |fn| with |task_tracker|, expecting it to crash in debug mode.
MaybeRunDeadlyTaskTrackerMemberFunction(CancelableTaskTracker * task_tracker,const Callback<void (CancelableTaskTracker *)> & fn)338 void MaybeRunDeadlyTaskTrackerMemberFunction(
339 CancelableTaskTracker* task_tracker,
340 const Callback<void(CancelableTaskTracker*)>& fn) {
341 EXPECT_DCHECK_DEATH(fn.Run(task_tracker));
342 }
343
PostDoNothingTask(CancelableTaskTracker * task_tracker)344 void PostDoNothingTask(CancelableTaskTracker* task_tracker) {
345 ignore_result(task_tracker->PostTask(
346 scoped_refptr<TestSimpleTaskRunner>(new TestSimpleTaskRunner()).get(),
347 FROM_HERE, DoNothing()));
348 }
349
TEST_F(CancelableTaskTrackerDeathTest,PostFromDifferentThread)350 TEST_F(CancelableTaskTrackerDeathTest, PostFromDifferentThread) {
351 Thread bad_thread("bad thread");
352 ASSERT_TRUE(bad_thread.Start());
353
354 bad_thread.task_runner()->PostTask(
355 FROM_HERE,
356 BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
357 Unretained(&task_tracker_), Bind(&PostDoNothingTask)));
358 }
359
TryCancel(CancelableTaskTracker::TaskId task_id,CancelableTaskTracker * task_tracker)360 void TryCancel(CancelableTaskTracker::TaskId task_id,
361 CancelableTaskTracker* task_tracker) {
362 task_tracker->TryCancel(task_id);
363 }
364
TEST_F(CancelableTaskTrackerDeathTest,CancelOnDifferentThread)365 TEST_F(CancelableTaskTrackerDeathTest, CancelOnDifferentThread) {
366 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
367 new TestSimpleTaskRunner());
368
369 Thread bad_thread("bad thread");
370 ASSERT_TRUE(bad_thread.Start());
371
372 CancelableTaskTracker::TaskId task_id =
373 task_tracker_.PostTask(test_task_runner.get(), FROM_HERE, DoNothing());
374 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
375
376 bad_thread.task_runner()->PostTask(
377 FROM_HERE,
378 BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
379 Unretained(&task_tracker_), Bind(&TryCancel, task_id)));
380
381 test_task_runner->RunUntilIdle();
382 }
383
TEST_F(CancelableTaskTrackerDeathTest,CancelAllOnDifferentThread)384 TEST_F(CancelableTaskTrackerDeathTest, CancelAllOnDifferentThread) {
385 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
386 new TestSimpleTaskRunner());
387
388 Thread bad_thread("bad thread");
389 ASSERT_TRUE(bad_thread.Start());
390
391 CancelableTaskTracker::TaskId task_id =
392 task_tracker_.PostTask(test_task_runner.get(), FROM_HERE, DoNothing());
393 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
394
395 bad_thread.task_runner()->PostTask(
396 FROM_HERE, BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
397 Unretained(&task_tracker_),
398 Bind(&CancelableTaskTracker::TryCancelAll)));
399
400 test_task_runner->RunUntilIdle();
401 }
402
403 } // namespace base
404