// 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/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; 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 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 task_; const CancelableTaskManager::Id task_id_; }; class ThreadedRunner final : public base::Thread { public: explicit ThreadedRunner(std::unique_ptr 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 task_; const CancelableTaskManager::Id task_id_; }; class CancelableTaskManagerTest : public ::testing::Test { public: CancelableTaskManager* manager() { return &manager_; } std::unique_ptr NewTask( ResultType* result, TestTask::Mode mode = TestTask::kDoNothing) { return base::make_unique(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 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()); } 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); } 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); } } // namespace internal } // namespace v8