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#include <deque>
9
10#include "base/bind.h"
11#include "base/bind_helpers.h"
12#include "base/location.h"
13#include "base/logging.h"
14#include "base/memory/ref_counted.h"
15#include "base/memory/weak_ptr.h"
16#include "base/run_loop.h"
17#include "base/single_thread_task_runner.h"
18#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:
28  ~CancelableTaskTrackerTest() override { RunCurrentLoopUntilIdle(); }
29
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
42void AddFailureAt(const tracked_objects::Location& location) {
43  ADD_FAILURE_AT(location.file_name(), location.line_number());
44}
45
46// Returns a closure that fails if run.
47Closure MakeExpectedNotRunClosure(const tracked_objects::Location& location) {
48  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:
57  explicit RunChecker(const tracked_objects::Location& location)
58      : 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:
69  tracked_objects::Location location_;
70  bool called_;
71};
72
73// Returns a closure that fails on destruction if it hasn't been run.
74Closure MakeExpectedRunClosure(const tracked_objects::Location& location) {
75  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
87  ignore_result(task_tracker_.PostTask(worker_thread.task_runner().get(),
88                                       FROM_HERE,
89                                       MakeExpectedRunClosure(FROM_HERE)));
90
91  ignore_result(task_tracker_.PostTaskAndReply(
92      worker_thread.task_runner().get(), FROM_HERE,
93      MakeExpectedRunClosure(FROM_HERE), MakeExpectedRunClosure(FROM_HERE)));
94
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
115  EXPECT_EQ(1U, test_task_runner->GetPendingTasks().size());
116
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
167  CancelableTaskTracker::TaskId task_id = task_tracker_.PostTaskAndReply(
168      worker_thread.task_runner().get(), FROM_HERE, Bind(&DoNothing),
169      MakeExpectedNotRunClosure(FROM_HERE));
170  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());
195  other_thread.task_runner()->PostTask(
196      FROM_HERE, Bind(&ExpectIsCanceled, is_canceled, false));
197  other_thread.Stop();
198
199  task_tracker_.TryCancel(task_id);
200
201  ASSERT_TRUE(other_thread.Start());
202  other_thread.task_runner()->PostTask(
203      FROM_HERE, Bind(&ExpectIsCanceled, is_canceled, true));
204  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  }
345};
346
347// Duplicated from base/threading/thread_checker.h so that we can be
348// good citizens there and undef the macro.
349#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
350#define ENABLE_THREAD_CHECKER 1
351#else
352#define ENABLE_THREAD_CHECKER 0
353#endif
354
355// Runs |fn| with |task_tracker|, expecting it to crash in debug mode.
356void MaybeRunDeadlyTaskTrackerMemberFunction(
357    CancelableTaskTracker* task_tracker,
358    const Callback<void(CancelableTaskTracker*)>& fn) {
359// CancelableTask uses DCHECKs with its ThreadChecker (itself only
360// enabled in debug mode).
361#if ENABLE_THREAD_CHECKER
362  EXPECT_DEATH_IF_SUPPORTED(fn.Run(task_tracker), "");
363#endif
364}
365
366void PostDoNothingTask(CancelableTaskTracker* task_tracker) {
367  ignore_result(task_tracker->PostTask(
368      scoped_refptr<TestSimpleTaskRunner>(new TestSimpleTaskRunner()).get(),
369      FROM_HERE,
370      Bind(&DoNothing)));
371}
372
373TEST_F(CancelableTaskTrackerDeathTest, PostFromDifferentThread) {
374  Thread bad_thread("bad thread");
375  ASSERT_TRUE(bad_thread.Start());
376
377  bad_thread.task_runner()->PostTask(
378      FROM_HERE, Bind(&MaybeRunDeadlyTaskTrackerMemberFunction,
379                      Unretained(&task_tracker_), Bind(&PostDoNothingTask)));
380}
381
382void TryCancel(CancelableTaskTracker::TaskId task_id,
383               CancelableTaskTracker* task_tracker) {
384  task_tracker->TryCancel(task_id);
385}
386
387TEST_F(CancelableTaskTrackerDeathTest, CancelOnDifferentThread) {
388  scoped_refptr<TestSimpleTaskRunner> test_task_runner(
389      new TestSimpleTaskRunner());
390
391  Thread bad_thread("bad thread");
392  ASSERT_TRUE(bad_thread.Start());
393
394  CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask(
395      test_task_runner.get(), FROM_HERE, Bind(&DoNothing));
396  EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
397
398  bad_thread.task_runner()->PostTask(
399      FROM_HERE, Bind(&MaybeRunDeadlyTaskTrackerMemberFunction,
400                      Unretained(&task_tracker_), Bind(&TryCancel, task_id)));
401
402  test_task_runner->RunUntilIdle();
403}
404
405TEST_F(CancelableTaskTrackerDeathTest, CancelAllOnDifferentThread) {
406  scoped_refptr<TestSimpleTaskRunner> test_task_runner(
407      new TestSimpleTaskRunner());
408
409  Thread bad_thread("bad thread");
410  ASSERT_TRUE(bad_thread.Start());
411
412  CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask(
413      test_task_runner.get(), FROM_HERE, Bind(&DoNothing));
414  EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
415
416  bad_thread.task_runner()->PostTask(
417      FROM_HERE,
418      Bind(&MaybeRunDeadlyTaskTrackerMemberFunction, Unretained(&task_tracker_),
419           Bind(&CancelableTaskTracker::TryCancelAll)));
420
421  test_task_runner->RunUntilIdle();
422}
423
424}  // namespace base
425