| // Copyright 2015 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/base/atomicops.h" |
| #include "src/base/platform/platform.h" |
| #include "src/tasks/cancelable-task.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| namespace { |
| |
| using ResultType = std::atomic<CancelableTaskManager::Id>; |
| |
| class CancelableTaskManagerTest; |
| |
| class TestTask : public Task, public Cancelable { |
| public: |
| enum Mode { kDoNothing, kWaitTillCancelTriggered, kCheckNotRun }; |
| |
| TestTask(CancelableTaskManagerTest* test, ResultType* result, Mode mode); |
| |
| // Task override. |
| void Run() final; |
| |
| private: |
| ResultType* const result_; |
| const Mode mode_; |
| CancelableTaskManagerTest* const test_; |
| }; |
| |
| class SequentialRunner { |
| public: |
| explicit SequentialRunner(std::unique_ptr<TestTask> task) |
| : task_(std::move(task)), task_id_(task_->id()) {} |
| |
| void Run() { |
| task_->Run(); |
| task_.reset(); |
| } |
| |
| CancelableTaskManager::Id task_id() const { return task_id_; } |
| |
| private: |
| std::unique_ptr<TestTask> task_; |
| const CancelableTaskManager::Id task_id_; |
| }; |
| |
| class ThreadedRunner final : public base::Thread { |
| public: |
| explicit ThreadedRunner(std::unique_ptr<TestTask> task) |
| : Thread(Options("runner thread")), |
| task_(std::move(task)), |
| task_id_(task_->id()) {} |
| |
| void Run() override { |
| task_->Run(); |
| task_.reset(); |
| } |
| |
| CancelableTaskManager::Id task_id() const { return task_id_; } |
| |
| private: |
| std::unique_ptr<TestTask> task_; |
| const CancelableTaskManager::Id task_id_; |
| }; |
| |
| class CancelableTaskManagerTest : public ::testing::Test { |
| public: |
| CancelableTaskManager* manager() { return &manager_; } |
| |
| std::unique_ptr<TestTask> NewTask( |
| ResultType* result, TestTask::Mode mode = TestTask::kDoNothing) { |
| return base::make_unique<TestTask>(this, result, mode); |
| } |
| |
| void CancelAndWait() { |
| cancel_triggered_.store(true); |
| manager_.CancelAndWait(); |
| } |
| |
| TryAbortResult TryAbortAll() { |
| cancel_triggered_.store(true); |
| return manager_.TryAbortAll(); |
| } |
| |
| bool cancel_triggered() const { return cancel_triggered_.load(); } |
| |
| private: |
| CancelableTaskManager manager_; |
| std::atomic<bool> cancel_triggered_{false}; |
| }; |
| |
| TestTask::TestTask(CancelableTaskManagerTest* test, ResultType* result, |
| Mode mode) |
| : Cancelable(test->manager()), result_(result), mode_(mode), test_(test) {} |
| |
| void TestTask::Run() { |
| if (!TryRun()) return; |
| |
| result_->store(id()); |
| |
| switch (mode_) { |
| case kWaitTillCancelTriggered: |
| // Simple busy wait until the main thread tried to cancel. |
| while (!test_->cancel_triggered()) { |
| } |
| break; |
| case kCheckNotRun: |
| // Check that we never execute {RunInternal}. |
| EXPECT_TRUE(false); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| } // namespace |
| |
| TEST_F(CancelableTaskManagerTest, EmptyCancelableTaskManager) { |
| CancelAndWait(); |
| } |
| |
| TEST_F(CancelableTaskManagerTest, SequentialCancelAndWait) { |
| ResultType result1{0}; |
| SequentialRunner runner1(NewTask(&result1, TestTask::kCheckNotRun)); |
| EXPECT_EQ(0u, result1); |
| CancelAndWait(); |
| EXPECT_EQ(0u, result1); |
| runner1.Run(); |
| EXPECT_EQ(0u, result1); |
| } |
| |
| TEST_F(CancelableTaskManagerTest, SequentialMultipleTasks) { |
| ResultType result1{0}; |
| ResultType result2{0}; |
| SequentialRunner runner1(NewTask(&result1)); |
| SequentialRunner runner2(NewTask(&result2)); |
| EXPECT_EQ(1u, runner1.task_id()); |
| EXPECT_EQ(2u, runner2.task_id()); |
| |
| EXPECT_EQ(0u, result1); |
| runner1.Run(); |
| EXPECT_EQ(1u, result1); |
| |
| EXPECT_EQ(0u, result2); |
| runner2.Run(); |
| EXPECT_EQ(2u, result2); |
| |
| CancelAndWait(); |
| EXPECT_EQ(TryAbortResult::kTaskRemoved, manager()->TryAbort(1)); |
| EXPECT_EQ(TryAbortResult::kTaskRemoved, manager()->TryAbort(2)); |
| } |
| |
| TEST_F(CancelableTaskManagerTest, ThreadedMultipleTasksStarted) { |
| ResultType result1{0}; |
| ResultType result2{0}; |
| ThreadedRunner runner1(NewTask(&result1, TestTask::kWaitTillCancelTriggered)); |
| ThreadedRunner runner2(NewTask(&result2, TestTask::kWaitTillCancelTriggered)); |
| runner1.Start(); |
| runner2.Start(); |
| // Busy wait on result to make sure both tasks are done. |
| while (result1.load() == 0 || result2.load() == 0) { |
| } |
| CancelAndWait(); |
| runner1.Join(); |
| runner2.Join(); |
| EXPECT_EQ(1u, result1); |
| EXPECT_EQ(2u, result2); |
| } |
| |
| TEST_F(CancelableTaskManagerTest, ThreadedMultipleTasksNotRun) { |
| ResultType result1{0}; |
| ResultType result2{0}; |
| ThreadedRunner runner1(NewTask(&result1, TestTask::kCheckNotRun)); |
| ThreadedRunner runner2(NewTask(&result2, TestTask::kCheckNotRun)); |
| CancelAndWait(); |
| // Tasks are canceled, hence the runner will bail out and not update result. |
| runner1.Start(); |
| runner2.Start(); |
| runner1.Join(); |
| runner2.Join(); |
| EXPECT_EQ(0u, result1); |
| EXPECT_EQ(0u, result2); |
| } |
| |
| TEST_F(CancelableTaskManagerTest, RemoveBeforeCancelAndWait) { |
| ResultType result1{0}; |
| ThreadedRunner runner1(NewTask(&result1, TestTask::kCheckNotRun)); |
| CancelableTaskManager::Id id = runner1.task_id(); |
| EXPECT_EQ(1u, id); |
| EXPECT_EQ(TryAbortResult::kTaskAborted, manager()->TryAbort(id)); |
| runner1.Start(); |
| runner1.Join(); |
| CancelAndWait(); |
| EXPECT_EQ(0u, result1); |
| } |
| |
| TEST_F(CancelableTaskManagerTest, RemoveAfterCancelAndWait) { |
| ResultType result1{0}; |
| ThreadedRunner runner1(NewTask(&result1)); |
| CancelableTaskManager::Id id = runner1.task_id(); |
| EXPECT_EQ(1u, id); |
| runner1.Start(); |
| runner1.Join(); |
| CancelAndWait(); |
| EXPECT_EQ(TryAbortResult::kTaskRemoved, manager()->TryAbort(id)); |
| EXPECT_EQ(1u, result1); |
| } |
| |
| TEST_F(CancelableTaskManagerTest, RemoveUnmanagedId) { |
| EXPECT_EQ(TryAbortResult::kTaskRemoved, manager()->TryAbort(1)); |
| EXPECT_EQ(TryAbortResult::kTaskRemoved, manager()->TryAbort(2)); |
| CancelAndWait(); |
| EXPECT_EQ(TryAbortResult::kTaskRemoved, manager()->TryAbort(1)); |
| EXPECT_EQ(TryAbortResult::kTaskRemoved, manager()->TryAbort(3)); |
| } |
| |
| TEST_F(CancelableTaskManagerTest, EmptyTryAbortAll) { |
| EXPECT_EQ(TryAbortResult::kTaskRemoved, TryAbortAll()); |
| CancelAndWait(); |
| } |
| |
| TEST_F(CancelableTaskManagerTest, ThreadedMultipleTasksNotRunTryAbortAll) { |
| ResultType result1{0}; |
| ResultType result2{0}; |
| ThreadedRunner runner1(NewTask(&result1, TestTask::kCheckNotRun)); |
| ThreadedRunner runner2(NewTask(&result2, TestTask::kCheckNotRun)); |
| EXPECT_EQ(TryAbortResult::kTaskAborted, TryAbortAll()); |
| // Tasks are canceled, hence the runner will bail out and not update result. |
| runner1.Start(); |
| runner2.Start(); |
| runner1.Join(); |
| runner2.Join(); |
| EXPECT_EQ(0u, result1); |
| EXPECT_EQ(0u, result2); |
| CancelAndWait(); |
| } |
| |
| TEST_F(CancelableTaskManagerTest, ThreadedMultipleTasksStartedTryAbortAll) { |
| ResultType result1{0}; |
| ResultType result2{0}; |
| ThreadedRunner runner1(NewTask(&result1, TestTask::kWaitTillCancelTriggered)); |
| ThreadedRunner runner2(NewTask(&result2, TestTask::kWaitTillCancelTriggered)); |
| runner1.Start(); |
| // Busy wait on result to make sure task1 is done. |
| while (result1.load() == 0) { |
| } |
| // If the task saw that we triggered the cancel and finished *before* the |
| // actual cancel happened, we get {kTaskAborted}. Otherwise, we get |
| // {kTaskRunning}. |
| EXPECT_THAT(TryAbortAll(), |
| testing::AnyOf(testing::Eq(TryAbortResult::kTaskAborted), |
| testing::Eq(TryAbortResult::kTaskRunning))); |
| runner2.Start(); |
| runner1.Join(); |
| runner2.Join(); |
| EXPECT_EQ(1u, result1); |
| EXPECT_EQ(0u, result2); |
| CancelAndWait(); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |