• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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