blob: b95b1589c8140a6479919f86ce4fce515ea828a2 [file] [log] [blame]
blundell@chromium.org00b75982014-02-06 22:47:13 +09001// 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>
blundell@chromium.org00b75982014-02-06 22:47:13 +09008
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"
blundell@chromium.org00b75982014-02-06 22:47:13 +090015#include "base/run_loop.h"
skyostil97aefe12015-05-01 04:06:15 +090016#include "base/single_thread_task_runner.h"
wez22afa882017-01-18 02:51:02 +090017#include "base/test/gtest_util.h"
blundell@chromium.org00b75982014-02-06 22:47:13 +090018#include "base/test/test_simple_task_runner.h"
19#include "base/threading/thread.h"
20#include "testing/gtest/include/gtest/gtest.h"
21
22namespace base {
23
24namespace {
25
26class CancelableTaskTrackerTest : public testing::Test {
27 protected:
dchengca87abb2014-12-23 11:56:47 +090028 ~CancelableTaskTrackerTest() override { RunCurrentLoopUntilIdle(); }
blundell@chromium.org00b75982014-02-06 22:47:13 +090029
30 void RunCurrentLoopUntilIdle() {
31 RunLoop run_loop;
32 run_loop.RunUntilIdle();
33 }
34
35 CancelableTaskTracker task_tracker_;
36
37 private:
38 // Needed by CancelableTaskTracker methods.
39 MessageLoop message_loop_;
40};
41
Brett Wilson89388db2017-09-12 14:22:16 +090042void AddFailureAt(const Location& location) {
blundell@chromium.org00b75982014-02-06 22:47:13 +090043 ADD_FAILURE_AT(location.file_name(), location.line_number());
44}
45
46// Returns a closure that fails if run.
Brett Wilson89388db2017-09-12 14:22:16 +090047Closure MakeExpectedNotRunClosure(const Location& location) {
blundell@chromium.org00b75982014-02-06 22:47:13 +090048 return Bind(&AddFailureAt, location);
49}
50
51// A helper class for MakeExpectedRunClosure() that fails if it is
52// destroyed without Run() having been called. This class may be used
53// from multiple threads as long as Run() is called at most once
54// before destruction.
55class RunChecker {
56 public:
Brett Wilson89388db2017-09-12 14:22:16 +090057 explicit RunChecker(const Location& location)
blundell@chromium.org00b75982014-02-06 22:47:13 +090058 : location_(location), called_(false) {}
59
60 ~RunChecker() {
61 if (!called_) {
62 ADD_FAILURE_AT(location_.file_name(), location_.line_number());
63 }
64 }
65
66 void Run() { called_ = true; }
67
68 private:
Brett Wilson89388db2017-09-12 14:22:16 +090069 Location location_;
blundell@chromium.org00b75982014-02-06 22:47:13 +090070 bool called_;
71};
72
73// Returns a closure that fails on destruction if it hasn't been run.
Brett Wilson89388db2017-09-12 14:22:16 +090074Closure MakeExpectedRunClosure(const Location& location) {
blundell@chromium.org00b75982014-02-06 22:47:13 +090075 return Bind(&RunChecker::Run, Owned(new RunChecker(location)));
76}
77
78} // namespace
79
80// With the task tracker, post a task, a task with a reply, and get a
81// new task id without canceling any of them. The tasks and the reply
82// should run and the "is canceled" callback should return false.
83TEST_F(CancelableTaskTrackerTest, NoCancel) {
84 Thread worker_thread("worker thread");
85 ASSERT_TRUE(worker_thread.Start());
86
skyostil97aefe12015-05-01 04:06:15 +090087 ignore_result(task_tracker_.PostTask(worker_thread.task_runner().get(),
blundell@chromium.org00b75982014-02-06 22:47:13 +090088 FROM_HERE,
89 MakeExpectedRunClosure(FROM_HERE)));
90
skyostil97aefe12015-05-01 04:06:15 +090091 ignore_result(task_tracker_.PostTaskAndReply(
92 worker_thread.task_runner().get(), FROM_HERE,
93 MakeExpectedRunClosure(FROM_HERE), MakeExpectedRunClosure(FROM_HERE)));
blundell@chromium.org00b75982014-02-06 22:47:13 +090094
95 CancelableTaskTracker::IsCanceledCallback is_canceled;
96 ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled));
97
98 worker_thread.Stop();
99
100 RunCurrentLoopUntilIdle();
101
102 EXPECT_FALSE(is_canceled.Run());
103}
104
105// Post a task with the task tracker but cancel it before running the
106// task runner. The task should not run.
107TEST_F(CancelableTaskTrackerTest, CancelPostedTask) {
108 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
109 new TestSimpleTaskRunner());
110
111 CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask(
112 test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE));
113 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
114
tzike9b374c2016-09-14 14:54:43 +0900115 EXPECT_EQ(1U, test_task_runner->NumPendingTasks());
blundell@chromium.org00b75982014-02-06 22:47:13 +0900116
117 task_tracker_.TryCancel(task_id);
118
119 test_task_runner->RunUntilIdle();
120}
121
122// Post a task with reply with the task tracker and cancel it before
123// running the task runner. Neither the task nor the reply should
124// run.
125TEST_F(CancelableTaskTrackerTest, CancelPostedTaskAndReply) {
126 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
127 new TestSimpleTaskRunner());
128
129 CancelableTaskTracker::TaskId task_id =
130 task_tracker_.PostTaskAndReply(test_task_runner.get(),
131 FROM_HERE,
132 MakeExpectedNotRunClosure(FROM_HERE),
133 MakeExpectedNotRunClosure(FROM_HERE));
134 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
135
136 task_tracker_.TryCancel(task_id);
137
138 test_task_runner->RunUntilIdle();
139}
140
141// Post a task with reply with the task tracker and cancel it after
142// running the task runner but before running the current message
143// loop. The task should run but the reply should not.
144TEST_F(CancelableTaskTrackerTest, CancelReply) {
145 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
146 new TestSimpleTaskRunner());
147
148 CancelableTaskTracker::TaskId task_id =
149 task_tracker_.PostTaskAndReply(test_task_runner.get(),
150 FROM_HERE,
151 MakeExpectedRunClosure(FROM_HERE),
152 MakeExpectedNotRunClosure(FROM_HERE));
153 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
154
155 test_task_runner->RunUntilIdle();
156
157 task_tracker_.TryCancel(task_id);
158}
159
160// Post a task with reply with the task tracker on a worker thread and
161// cancel it before running the current message loop. The task should
162// run but the reply should not.
163TEST_F(CancelableTaskTrackerTest, CancelReplyDifferentThread) {
164 Thread worker_thread("worker thread");
165 ASSERT_TRUE(worker_thread.Start());
166
skyostil97aefe12015-05-01 04:06:15 +0900167 CancelableTaskTracker::TaskId task_id = task_tracker_.PostTaskAndReply(
Peter Kasting24efe5e2018-02-24 09:03:01 +0900168 worker_thread.task_runner().get(), FROM_HERE, DoNothing(),
skyostil97aefe12015-05-01 04:06:15 +0900169 MakeExpectedNotRunClosure(FROM_HERE));
blundell@chromium.org00b75982014-02-06 22:47:13 +0900170 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
171
172 task_tracker_.TryCancel(task_id);
173
174 worker_thread.Stop();
175}
176
177void ExpectIsCanceled(
178 const CancelableTaskTracker::IsCanceledCallback& is_canceled,
179 bool expected_is_canceled) {
180 EXPECT_EQ(expected_is_canceled, is_canceled.Run());
181}
182
183// Create a new task ID and check its status on a separate thread
184// before and after canceling. The is-canceled callback should be
185// thread-safe (i.e., nothing should blow up).
186TEST_F(CancelableTaskTrackerTest, NewTrackedTaskIdDifferentThread) {
187 CancelableTaskTracker::IsCanceledCallback is_canceled;
188 CancelableTaskTracker::TaskId task_id =
189 task_tracker_.NewTrackedTaskId(&is_canceled);
190
191 EXPECT_FALSE(is_canceled.Run());
192
193 Thread other_thread("other thread");
194 ASSERT_TRUE(other_thread.Start());
skyostil97aefe12015-05-01 04:06:15 +0900195 other_thread.task_runner()->PostTask(
tzik6bdbeb22017-04-12 00:00:44 +0900196 FROM_HERE, BindOnce(&ExpectIsCanceled, is_canceled, false));
blundell@chromium.org00b75982014-02-06 22:47:13 +0900197 other_thread.Stop();
198
199 task_tracker_.TryCancel(task_id);
200
201 ASSERT_TRUE(other_thread.Start());
skyostil97aefe12015-05-01 04:06:15 +0900202 other_thread.task_runner()->PostTask(
tzik6bdbeb22017-04-12 00:00:44 +0900203 FROM_HERE, BindOnce(&ExpectIsCanceled, is_canceled, true));
blundell@chromium.org00b75982014-02-06 22:47:13 +0900204 other_thread.Stop();
205}
206
207// With the task tracker, post a task, a task with a reply, get a new
208// task id, and then cancel all of them. None of the tasks nor the
209// reply should run and the "is canceled" callback should return
210// true.
211TEST_F(CancelableTaskTrackerTest, CancelAll) {
212 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
213 new TestSimpleTaskRunner());
214
215 ignore_result(task_tracker_.PostTask(
216 test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE)));
217
218 ignore_result(
219 task_tracker_.PostTaskAndReply(test_task_runner.get(),
220 FROM_HERE,
221 MakeExpectedNotRunClosure(FROM_HERE),
222 MakeExpectedNotRunClosure(FROM_HERE)));
223
224 CancelableTaskTracker::IsCanceledCallback is_canceled;
225 ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled));
226
227 task_tracker_.TryCancelAll();
228
229 test_task_runner->RunUntilIdle();
230
231 RunCurrentLoopUntilIdle();
232
233 EXPECT_TRUE(is_canceled.Run());
234}
235
236// With the task tracker, post a task, a task with a reply, get a new
237// task id, and then cancel all of them. None of the tasks nor the
238// reply should run and the "is canceled" callback should return
239// true.
240TEST_F(CancelableTaskTrackerTest, DestructionCancelsAll) {
241 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
242 new TestSimpleTaskRunner());
243
244 CancelableTaskTracker::IsCanceledCallback is_canceled;
245
246 {
247 // Create another task tracker with a smaller scope.
248 CancelableTaskTracker task_tracker;
249
250 ignore_result(task_tracker.PostTask(test_task_runner.get(),
251 FROM_HERE,
252 MakeExpectedNotRunClosure(FROM_HERE)));
253
254 ignore_result(
255 task_tracker.PostTaskAndReply(test_task_runner.get(),
256 FROM_HERE,
257 MakeExpectedNotRunClosure(FROM_HERE),
258 MakeExpectedNotRunClosure(FROM_HERE)));
259
260 ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled));
261 }
262
263 test_task_runner->RunUntilIdle();
264
265 RunCurrentLoopUntilIdle();
266
267 EXPECT_FALSE(is_canceled.Run());
268}
269
270// Post a task and cancel it. HasTrackedTasks() should return true
271// from when the task is posted until the (do-nothing) reply task is
272// flushed.
273TEST_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 test_task_runner->RunUntilIdle();
285
286 EXPECT_TRUE(task_tracker_.HasTrackedTasks());
287
288 RunCurrentLoopUntilIdle();
289
290 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
291}
292
293// Post a task with a reply and cancel it. HasTrackedTasks() should
294// return true from when the task is posted until it is canceled.
295TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPostWithReply) {
296 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
297 new TestSimpleTaskRunner());
298
299 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
300
301 ignore_result(
302 task_tracker_.PostTaskAndReply(test_task_runner.get(),
303 FROM_HERE,
304 MakeExpectedNotRunClosure(FROM_HERE),
305 MakeExpectedNotRunClosure(FROM_HERE)));
306
307 task_tracker_.TryCancelAll();
308
309 test_task_runner->RunUntilIdle();
310
311 EXPECT_TRUE(task_tracker_.HasTrackedTasks());
312
313 RunCurrentLoopUntilIdle();
314
315 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
316}
317
318// Create a new tracked task ID. HasTrackedTasks() should return true
319// until the IsCanceledCallback is destroyed.
320TEST_F(CancelableTaskTrackerTest, HasTrackedTasksIsCancelled) {
321 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
322
323 CancelableTaskTracker::IsCanceledCallback is_canceled;
324 ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled));
325
326 task_tracker_.TryCancelAll();
327
328 EXPECT_TRUE(task_tracker_.HasTrackedTasks());
329
330 is_canceled.Reset();
331
332 EXPECT_FALSE(task_tracker_.HasTrackedTasks());
333}
334
335// The death tests below make sure that calling task tracker member
336// functions from a thread different from its owner thread DCHECKs in
337// debug mode.
338
339class CancelableTaskTrackerDeathTest : public CancelableTaskTrackerTest {
340 protected:
341 CancelableTaskTrackerDeathTest() {
342 // The default style "fast" does not support multi-threaded tests.
343 ::testing::FLAGS_gtest_death_test_style = "threadsafe";
344 }
blundell@chromium.org00b75982014-02-06 22:47:13 +0900345};
346
blundell@chromium.org00b75982014-02-06 22:47:13 +0900347// Runs |fn| with |task_tracker|, expecting it to crash in debug mode.
348void MaybeRunDeadlyTaskTrackerMemberFunction(
349 CancelableTaskTracker* task_tracker,
350 const Callback<void(CancelableTaskTracker*)>& fn) {
wez22afa882017-01-18 02:51:02 +0900351 EXPECT_DCHECK_DEATH(fn.Run(task_tracker));
blundell@chromium.org00b75982014-02-06 22:47:13 +0900352}
353
354void PostDoNothingTask(CancelableTaskTracker* task_tracker) {
355 ignore_result(task_tracker->PostTask(
356 scoped_refptr<TestSimpleTaskRunner>(new TestSimpleTaskRunner()).get(),
Peter Kasting24efe5e2018-02-24 09:03:01 +0900357 FROM_HERE, DoNothing()));
blundell@chromium.org00b75982014-02-06 22:47:13 +0900358}
359
360TEST_F(CancelableTaskTrackerDeathTest, PostFromDifferentThread) {
361 Thread bad_thread("bad thread");
362 ASSERT_TRUE(bad_thread.Start());
363
skyostil97aefe12015-05-01 04:06:15 +0900364 bad_thread.task_runner()->PostTask(
tzik6bdbeb22017-04-12 00:00:44 +0900365 FROM_HERE,
366 BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
367 Unretained(&task_tracker_), Bind(&PostDoNothingTask)));
blundell@chromium.org00b75982014-02-06 22:47:13 +0900368}
369
370void TryCancel(CancelableTaskTracker::TaskId task_id,
371 CancelableTaskTracker* task_tracker) {
372 task_tracker->TryCancel(task_id);
373}
374
375TEST_F(CancelableTaskTrackerDeathTest, CancelOnDifferentThread) {
376 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
377 new TestSimpleTaskRunner());
378
379 Thread bad_thread("bad thread");
380 ASSERT_TRUE(bad_thread.Start());
381
Peter Kasting24efe5e2018-02-24 09:03:01 +0900382 CancelableTaskTracker::TaskId task_id =
383 task_tracker_.PostTask(test_task_runner.get(), FROM_HERE, DoNothing());
blundell@chromium.org00b75982014-02-06 22:47:13 +0900384 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
385
skyostil97aefe12015-05-01 04:06:15 +0900386 bad_thread.task_runner()->PostTask(
tzik6bdbeb22017-04-12 00:00:44 +0900387 FROM_HERE,
388 BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
389 Unretained(&task_tracker_), Bind(&TryCancel, task_id)));
blundell@chromium.org00b75982014-02-06 22:47:13 +0900390
391 test_task_runner->RunUntilIdle();
392}
393
394TEST_F(CancelableTaskTrackerDeathTest, CancelAllOnDifferentThread) {
395 scoped_refptr<TestSimpleTaskRunner> test_task_runner(
396 new TestSimpleTaskRunner());
397
398 Thread bad_thread("bad thread");
399 ASSERT_TRUE(bad_thread.Start());
400
Peter Kasting24efe5e2018-02-24 09:03:01 +0900401 CancelableTaskTracker::TaskId task_id =
402 task_tracker_.PostTask(test_task_runner.get(), FROM_HERE, DoNothing());
blundell@chromium.org00b75982014-02-06 22:47:13 +0900403 EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
404
skyostil97aefe12015-05-01 04:06:15 +0900405 bad_thread.task_runner()->PostTask(
tzik6bdbeb22017-04-12 00:00:44 +0900406 FROM_HERE, BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
407 Unretained(&task_tracker_),
408 Bind(&CancelableTaskTracker::TryCancelAll)));
blundell@chromium.org00b75982014-02-06 22:47:13 +0900409
410 test_task_runner->RunUntilIdle();
411}
412
413} // namespace base