// 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/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
namespace {
class TestTask : public Task, public Cancelable {
public:
enum Mode { kDoNothing, kWaitTillCanceledAgain, kCheckNotRun };
TestTask(CancelableTaskManager* parent, base::AtomicWord* result,
Mode mode = kDoNothing)
: Cancelable(parent), result_(result), mode_(mode) {}
// Task overrides.
void Run() final {
if (TryRun()) {
RunInternal();
}
}
private:
void RunInternal() {
base::Release_Store(result_, id());
switch (mode_) {
case kWaitTillCanceledAgain:
// Simple busy wait until the main thread tried to cancel.
while (CancelAttempts() == 0) {
}
break;
case kCheckNotRun:
// Check that we never execute {RunInternal}.
EXPECT_TRUE(false);
break;
default:
break;
}
}
base::AtomicWord* result_;
Mode mode_;
};
class SequentialRunner {
public:
explicit SequentialRunner(TestTask* task) : task_(task) {}
void Run() {
task_->Run();
delete task_;
}
private:
TestTask* task_;
};
class ThreadedRunner final : public base::Thread {
public:
explicit ThreadedRunner(TestTask* task)
: Thread(Options("runner thread")), task_(task) {}
virtual void Run() {
task_->Run();
delete task_;
}
private:
TestTask* task_;
};
typedef base::AtomicWord ResultType;
intptr_t GetValue(ResultType* result) { return base::Acquire_Load(result); }
} // namespace
TEST(CancelableTask, EmptyCancelableTaskManager) {
CancelableTaskManager manager;
manager.CancelAndWait();
}
TEST(CancelableTask, SequentialCancelAndWait) {
CancelableTaskManager manager;
ResultType result1 = 0;
SequentialRunner runner1(
new TestTask(&manager, &result1, TestTask::kCheckNotRun));
EXPECT_EQ(GetValue(&result1), 0);
manager.CancelAndWait();
EXPECT_EQ(GetValue(&result1), 0);
runner1.Run(); // Run to avoid leaking the Task.
EXPECT_EQ(GetValue(&result1), 0);
}
TEST(CancelableTask, SequentialMultipleTasks) {
CancelableTaskManager manager;
ResultType result1 = 0;
ResultType result2 = 0;
TestTask* task1 = new TestTask(&manager, &result1);
TestTask* task2 = new TestTask(&manager, &result2);
SequentialRunner runner1(task1);
SequentialRunner runner2(task2);
EXPECT_EQ(task1->id(), 1u);
EXPECT_EQ(task2->id(), 2u);
EXPECT_EQ(GetValue(&result1), 0);
runner1.Run(); // Don't touch task1 after running it.
EXPECT_EQ(GetValue(&result1), 1);
EXPECT_EQ(GetValue(&result2), 0);
runner2.Run(); // Don't touch task2 after running it.
EXPECT_EQ(GetValue(&result2), 2);
manager.CancelAndWait();
EXPECT_FALSE(manager.TryAbort(1));
EXPECT_FALSE(manager.TryAbort(2));
}
TEST(CancelableTask, ThreadedMultipleTasksStarted) {
CancelableTaskManager manager;
ResultType result1 = 0;
ResultType result2 = 0;
TestTask* task1 =
new TestTask(&manager, &result1, TestTask::kWaitTillCanceledAgain);
TestTask* task2 =
new TestTask(&manager, &result2, TestTask::kWaitTillCanceledAgain);
ThreadedRunner runner1(task1);
ThreadedRunner runner2(task2);
runner1.Start();
runner2.Start();
// Busy wait on result to make sure both tasks are done.
while ((GetValue(&result1) == 0) || (GetValue(&result2) == 0)) {
}
manager.CancelAndWait();
runner1.Join();
runner2.Join();
EXPECT_EQ(GetValue(&result1), 1);
EXPECT_EQ(GetValue(&result2), 2);
}
TEST(CancelableTask, ThreadedMultipleTasksNotRun) {
CancelableTaskManager manager;
ResultType result1 = 0;
ResultType result2 = 0;
TestTask* task1 = new TestTask(&manager, &result1, TestTask::kCheckNotRun);
TestTask* task2 = new TestTask(&manager, &result2, TestTask::kCheckNotRun);
ThreadedRunner runner1(task1);
ThreadedRunner runner2(task2);
manager.CancelAndWait();
// Tasks are canceled, hence the runner will bail out and not update result.
runner1.Start();
runner2.Start();
runner1.Join();
runner2.Join();
EXPECT_EQ(GetValue(&result1), 0);
EXPECT_EQ(GetValue(&result2), 0);
}
TEST(CancelableTask, RemoveBeforeCancelAndWait) {
CancelableTaskManager manager;
ResultType result1 = 0;
TestTask* task1 = new TestTask(&manager, &result1, TestTask::kCheckNotRun);
ThreadedRunner runner1(task1);
uint32_t id = task1->id();
EXPECT_EQ(id, 1u);
EXPECT_TRUE(manager.TryAbort(id));
runner1.Start();
runner1.Join();
manager.CancelAndWait();
EXPECT_EQ(GetValue(&result1), 0);
}
TEST(CancelableTask, RemoveAfterCancelAndWait) {
CancelableTaskManager manager;
ResultType result1 = 0;
TestTask* task1 = new TestTask(&manager, &result1);
ThreadedRunner runner1(task1);
uint32_t id = task1->id();
EXPECT_EQ(id, 1u);
runner1.Start();
runner1.Join();
manager.CancelAndWait();
EXPECT_FALSE(manager.TryAbort(id));
EXPECT_EQ(GetValue(&result1), 1);
}
TEST(CancelableTask, RemoveUnmanagedId) {
CancelableTaskManager manager;
EXPECT_FALSE(manager.TryAbort(1));
EXPECT_FALSE(manager.TryAbort(2));
manager.CancelAndWait();
EXPECT_FALSE(manager.TryAbort(1));
EXPECT_FALSE(manager.TryAbort(3));
}
} // namespace internal
} // namespace v8