377 lines
12 KiB
C++
377 lines
12 KiB
C++
|
|
// Copyright 2014 The Chromium Authors
|
||
|
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
|
// found in the LICENSE file.
|
||
|
|
|
||
|
|
#include "base/task/cancelable_task_tracker.h"
|
||
|
|
|
||
|
|
#include <cstddef>
|
||
|
|
#include <tuple>
|
||
|
|
|
||
|
|
#include "base/check_op.h"
|
||
|
|
#include "base/functional/bind.h"
|
||
|
|
#include "base/functional/callback_helpers.h"
|
||
|
|
#include "base/location.h"
|
||
|
|
#include "base/memory/ref_counted.h"
|
||
|
|
#include "base/memory/weak_ptr.h"
|
||
|
|
#include "base/run_loop.h"
|
||
|
|
#include "base/task/single_thread_task_runner.h"
|
||
|
|
#include "base/test/bind.h"
|
||
|
|
#include "base/test/gtest_util.h"
|
||
|
|
#include "base/test/task_environment.h"
|
||
|
|
#include "base/test/test_simple_task_runner.h"
|
||
|
|
#include "base/threading/thread.h"
|
||
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
||
|
|
|
||
|
|
namespace base {
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
class CancelableTaskTrackerTest : public testing::Test {
|
||
|
|
protected:
|
||
|
|
~CancelableTaskTrackerTest() override { RunLoop().RunUntilIdle(); }
|
||
|
|
|
||
|
|
CancelableTaskTracker task_tracker_;
|
||
|
|
|
||
|
|
private:
|
||
|
|
// Needed by CancelableTaskTracker methods.
|
||
|
|
test::TaskEnvironment task_environment_;
|
||
|
|
};
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
// With the task tracker, post a task, a task with a reply, and get a
|
||
|
|
// new task id without canceling any of them. The tasks and the reply
|
||
|
|
// should run and the "is canceled" callback should return false.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, NoCancel) {
|
||
|
|
Thread worker_thread("worker thread");
|
||
|
|
ASSERT_TRUE(worker_thread.Start());
|
||
|
|
|
||
|
|
std::ignore =
|
||
|
|
task_tracker_.PostTask(worker_thread.task_runner().get(), FROM_HERE,
|
||
|
|
MakeExpectedRunClosure(FROM_HERE));
|
||
|
|
|
||
|
|
std::ignore = task_tracker_.PostTaskAndReply(
|
||
|
|
worker_thread.task_runner().get(), FROM_HERE,
|
||
|
|
MakeExpectedRunClosure(FROM_HERE), MakeExpectedRunClosure(FROM_HERE));
|
||
|
|
|
||
|
|
CancelableTaskTracker::IsCanceledCallback is_canceled;
|
||
|
|
std::ignore = task_tracker_.NewTrackedTaskId(&is_canceled);
|
||
|
|
|
||
|
|
worker_thread.Stop();
|
||
|
|
|
||
|
|
RunLoop().RunUntilIdle();
|
||
|
|
|
||
|
|
EXPECT_FALSE(is_canceled.Run());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Post a task with the task tracker but cancel it before running the
|
||
|
|
// task runner. The task should not run.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, CancelPostedTask) {
|
||
|
|
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
|
||
|
|
new TestSimpleTaskRunner());
|
||
|
|
|
||
|
|
CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask(
|
||
|
|
test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
|
||
|
|
|
||
|
|
EXPECT_EQ(1U, test_task_runner->NumPendingTasks());
|
||
|
|
|
||
|
|
task_tracker_.TryCancel(task_id);
|
||
|
|
|
||
|
|
test_task_runner->RunUntilIdle();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Post a task with reply with the task tracker and cancel it before
|
||
|
|
// running the task runner. Neither the task nor the reply should
|
||
|
|
// run.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, CancelPostedTaskAndReply) {
|
||
|
|
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
|
||
|
|
new TestSimpleTaskRunner());
|
||
|
|
|
||
|
|
CancelableTaskTracker::TaskId task_id =
|
||
|
|
task_tracker_.PostTaskAndReply(test_task_runner.get(),
|
||
|
|
FROM_HERE,
|
||
|
|
MakeExpectedNotRunClosure(FROM_HERE),
|
||
|
|
MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
|
||
|
|
|
||
|
|
task_tracker_.TryCancel(task_id);
|
||
|
|
|
||
|
|
test_task_runner->RunUntilIdle();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Post a task with reply with the task tracker and cancel it after
|
||
|
|
// running the task runner but before running the current message
|
||
|
|
// loop. The task should run but the reply should not.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, CancelReply) {
|
||
|
|
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
|
||
|
|
new TestSimpleTaskRunner());
|
||
|
|
|
||
|
|
CancelableTaskTracker::TaskId task_id =
|
||
|
|
task_tracker_.PostTaskAndReply(test_task_runner.get(),
|
||
|
|
FROM_HERE,
|
||
|
|
MakeExpectedRunClosure(FROM_HERE),
|
||
|
|
MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
|
||
|
|
|
||
|
|
test_task_runner->RunUntilIdle();
|
||
|
|
|
||
|
|
task_tracker_.TryCancel(task_id);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Post a task with reply with the task tracker on a worker thread and
|
||
|
|
// cancel it before running the current message loop. The task should
|
||
|
|
// run but the reply should not.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, CancelReplyDifferentThread) {
|
||
|
|
Thread worker_thread("worker thread");
|
||
|
|
ASSERT_TRUE(worker_thread.Start());
|
||
|
|
|
||
|
|
CancelableTaskTracker::TaskId task_id = task_tracker_.PostTaskAndReply(
|
||
|
|
worker_thread.task_runner().get(), FROM_HERE, DoNothing(),
|
||
|
|
MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
|
||
|
|
|
||
|
|
task_tracker_.TryCancel(task_id);
|
||
|
|
|
||
|
|
worker_thread.Stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
void ExpectIsCanceled(
|
||
|
|
const CancelableTaskTracker::IsCanceledCallback& is_canceled,
|
||
|
|
bool expected_is_canceled) {
|
||
|
|
EXPECT_EQ(expected_is_canceled, is_canceled.Run());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create a new task ID and check its status on a separate thread
|
||
|
|
// before and after canceling. The is-canceled callback should be
|
||
|
|
// thread-safe (i.e., nothing should blow up).
|
||
|
|
TEST_F(CancelableTaskTrackerTest, NewTrackedTaskIdDifferentThread) {
|
||
|
|
CancelableTaskTracker::IsCanceledCallback is_canceled;
|
||
|
|
CancelableTaskTracker::TaskId task_id =
|
||
|
|
task_tracker_.NewTrackedTaskId(&is_canceled);
|
||
|
|
|
||
|
|
EXPECT_FALSE(is_canceled.Run());
|
||
|
|
|
||
|
|
Thread other_thread("other thread");
|
||
|
|
ASSERT_TRUE(other_thread.Start());
|
||
|
|
other_thread.task_runner()->PostTask(
|
||
|
|
FROM_HERE, BindOnce(&ExpectIsCanceled, is_canceled, false));
|
||
|
|
other_thread.Stop();
|
||
|
|
|
||
|
|
task_tracker_.TryCancel(task_id);
|
||
|
|
|
||
|
|
ASSERT_TRUE(other_thread.Start());
|
||
|
|
other_thread.task_runner()->PostTask(
|
||
|
|
FROM_HERE, BindOnce(&ExpectIsCanceled, is_canceled, true));
|
||
|
|
other_thread.Stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
// With the task tracker, post a task, a task with a reply, get a new
|
||
|
|
// task id, and then cancel all of them. None of the tasks nor the
|
||
|
|
// reply should run and the "is canceled" callback should return
|
||
|
|
// true.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, CancelAll) {
|
||
|
|
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
|
||
|
|
new TestSimpleTaskRunner());
|
||
|
|
|
||
|
|
std::ignore = task_tracker_.PostTask(test_task_runner.get(), FROM_HERE,
|
||
|
|
MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
|
||
|
|
std::ignore = task_tracker_.PostTaskAndReply(
|
||
|
|
test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE),
|
||
|
|
MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
|
||
|
|
CancelableTaskTracker::IsCanceledCallback is_canceled;
|
||
|
|
std::ignore = task_tracker_.NewTrackedTaskId(&is_canceled);
|
||
|
|
|
||
|
|
task_tracker_.TryCancelAll();
|
||
|
|
|
||
|
|
test_task_runner->RunUntilIdle();
|
||
|
|
|
||
|
|
RunLoop().RunUntilIdle();
|
||
|
|
|
||
|
|
EXPECT_TRUE(is_canceled.Run());
|
||
|
|
}
|
||
|
|
|
||
|
|
// With the task tracker, post a task, a task with a reply, get a new
|
||
|
|
// task id, and then cancel all of them. None of the tasks nor the
|
||
|
|
// reply should run and the "is canceled" callback should return
|
||
|
|
// true.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, DestructionCancelsAll) {
|
||
|
|
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
|
||
|
|
new TestSimpleTaskRunner());
|
||
|
|
|
||
|
|
CancelableTaskTracker::IsCanceledCallback is_canceled;
|
||
|
|
|
||
|
|
{
|
||
|
|
// Create another task tracker with a smaller scope.
|
||
|
|
CancelableTaskTracker task_tracker;
|
||
|
|
|
||
|
|
std::ignore = task_tracker.PostTask(test_task_runner.get(), FROM_HERE,
|
||
|
|
MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
|
||
|
|
std::ignore = task_tracker.PostTaskAndReply(
|
||
|
|
test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE),
|
||
|
|
MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
|
||
|
|
std::ignore = task_tracker_.NewTrackedTaskId(&is_canceled);
|
||
|
|
}
|
||
|
|
|
||
|
|
test_task_runner->RunUntilIdle();
|
||
|
|
|
||
|
|
RunLoop().RunUntilIdle();
|
||
|
|
|
||
|
|
EXPECT_FALSE(is_canceled.Run());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Post a task and cancel it. HasTrackedTasks() should return false as soon as
|
||
|
|
// TryCancel() returns, otherwise we may have leaked per-task state.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, HasTrackedTasksCancelById) {
|
||
|
|
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
|
||
|
|
new TestSimpleTaskRunner());
|
||
|
|
|
||
|
|
EXPECT_FALSE(task_tracker_.HasTrackedTasks());
|
||
|
|
|
||
|
|
CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask(
|
||
|
|
test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
EXPECT_TRUE(task_tracker_.HasTrackedTasks());
|
||
|
|
|
||
|
|
task_tracker_.TryCancel(task_id);
|
||
|
|
EXPECT_FALSE(task_tracker_.HasTrackedTasks());
|
||
|
|
|
||
|
|
test_task_runner->RunUntilIdle();
|
||
|
|
RunLoop().RunUntilIdle();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Post a task and then cancel all tasks. HasTrackedTasks() should return false
|
||
|
|
// as soon as TryCancelAll() is called.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPostCancelAll) {
|
||
|
|
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
|
||
|
|
new TestSimpleTaskRunner());
|
||
|
|
|
||
|
|
EXPECT_FALSE(task_tracker_.HasTrackedTasks());
|
||
|
|
|
||
|
|
std::ignore = task_tracker_.PostTask(test_task_runner.get(), FROM_HERE,
|
||
|
|
MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
|
||
|
|
task_tracker_.TryCancelAll();
|
||
|
|
|
||
|
|
EXPECT_FALSE(task_tracker_.HasTrackedTasks());
|
||
|
|
|
||
|
|
test_task_runner->RunUntilIdle();
|
||
|
|
RunLoop().RunUntilIdle();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Post a task with a reply and cancel it. HasTrackedTasks() should return false
|
||
|
|
// as soon as TryCancelAll() is called.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPostWithReplyCancelAll) {
|
||
|
|
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
|
||
|
|
new TestSimpleTaskRunner());
|
||
|
|
|
||
|
|
EXPECT_FALSE(task_tracker_.HasTrackedTasks());
|
||
|
|
|
||
|
|
std::ignore = task_tracker_.PostTaskAndReply(
|
||
|
|
test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE),
|
||
|
|
MakeExpectedNotRunClosure(FROM_HERE));
|
||
|
|
|
||
|
|
task_tracker_.TryCancelAll();
|
||
|
|
|
||
|
|
EXPECT_FALSE(task_tracker_.HasTrackedTasks());
|
||
|
|
|
||
|
|
test_task_runner->RunUntilIdle();
|
||
|
|
RunLoop().RunUntilIdle();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create a new tracked task ID. HasTrackedTasks() should return false as soon
|
||
|
|
// as TryCancelAll() is called.
|
||
|
|
TEST_F(CancelableTaskTrackerTest, HasTrackedTasksIsCancelledCancelAll) {
|
||
|
|
EXPECT_FALSE(task_tracker_.HasTrackedTasks());
|
||
|
|
|
||
|
|
CancelableTaskTracker::IsCanceledCallback is_canceled;
|
||
|
|
std::ignore = task_tracker_.NewTrackedTaskId(&is_canceled);
|
||
|
|
|
||
|
|
task_tracker_.TryCancelAll();
|
||
|
|
|
||
|
|
EXPECT_FALSE(task_tracker_.HasTrackedTasks());
|
||
|
|
}
|
||
|
|
|
||
|
|
// The death tests below make sure that calling task tracker member
|
||
|
|
// functions from a thread different from its owner thread DCHECKs in
|
||
|
|
// debug mode.
|
||
|
|
|
||
|
|
class CancelableTaskTrackerDeathTest : public CancelableTaskTrackerTest {
|
||
|
|
protected:
|
||
|
|
CancelableTaskTrackerDeathTest() {
|
||
|
|
// The default style "fast" does not support multi-threaded tests.
|
||
|
|
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Runs |fn| with |task_tracker|, expecting it to crash in debug mode.
|
||
|
|
void MaybeRunDeadlyTaskTrackerMemberFunction(
|
||
|
|
CancelableTaskTracker* task_tracker,
|
||
|
|
OnceCallback<void(CancelableTaskTracker*)> fn) {
|
||
|
|
EXPECT_DCHECK_DEATH(std::move(fn).Run(task_tracker));
|
||
|
|
}
|
||
|
|
|
||
|
|
void PostDoNothingTask(CancelableTaskTracker* task_tracker) {
|
||
|
|
std::ignore = task_tracker->PostTask(
|
||
|
|
scoped_refptr<TestSimpleTaskRunner>(new TestSimpleTaskRunner()).get(),
|
||
|
|
FROM_HERE, DoNothing());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(CancelableTaskTrackerDeathTest, PostFromDifferentThread) {
|
||
|
|
Thread bad_thread("bad thread");
|
||
|
|
ASSERT_TRUE(bad_thread.Start());
|
||
|
|
|
||
|
|
bad_thread.task_runner()->PostTask(
|
||
|
|
FROM_HERE,
|
||
|
|
BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
|
||
|
|
Unretained(&task_tracker_), BindOnce(&PostDoNothingTask)));
|
||
|
|
}
|
||
|
|
|
||
|
|
void TryCancel(CancelableTaskTracker::TaskId task_id,
|
||
|
|
CancelableTaskTracker* task_tracker) {
|
||
|
|
task_tracker->TryCancel(task_id);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(CancelableTaskTrackerDeathTest, CancelOnDifferentThread) {
|
||
|
|
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
|
||
|
|
new TestSimpleTaskRunner());
|
||
|
|
|
||
|
|
Thread bad_thread("bad thread");
|
||
|
|
ASSERT_TRUE(bad_thread.Start());
|
||
|
|
|
||
|
|
CancelableTaskTracker::TaskId task_id =
|
||
|
|
task_tracker_.PostTask(test_task_runner.get(), FROM_HERE, DoNothing());
|
||
|
|
EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
|
||
|
|
|
||
|
|
bad_thread.task_runner()->PostTask(
|
||
|
|
FROM_HERE,
|
||
|
|
BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
|
||
|
|
Unretained(&task_tracker_), BindOnce(&TryCancel, task_id)));
|
||
|
|
|
||
|
|
test_task_runner->RunUntilIdle();
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(CancelableTaskTrackerDeathTest, CancelAllOnDifferentThread) {
|
||
|
|
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
|
||
|
|
new TestSimpleTaskRunner());
|
||
|
|
|
||
|
|
Thread bad_thread("bad thread");
|
||
|
|
ASSERT_TRUE(bad_thread.Start());
|
||
|
|
|
||
|
|
CancelableTaskTracker::TaskId task_id =
|
||
|
|
task_tracker_.PostTask(test_task_runner.get(), FROM_HERE, DoNothing());
|
||
|
|
EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
|
||
|
|
|
||
|
|
bad_thread.task_runner()->PostTask(
|
||
|
|
FROM_HERE, BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
|
||
|
|
Unretained(&task_tracker_),
|
||
|
|
BindOnce(&CancelableTaskTracker::TryCancelAll)));
|
||
|
|
|
||
|
|
test_task_runner->RunUntilIdle();
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace base
|