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