v8/test/unittests/cancelable-tasks-unittest.cc
Andreas Haas 62fa048749 [wasm] Reschedule compilation tasks
At the moment, the whole WebAssembly compilation may run in a single
background task. On a low-end device, this can mean that the background
thread is busy for seconds and thereby blocks other tasks, see e.g.
https://crbug.com/914757.

With this CL we re-schedule compilation tasks after every 50ms. These
50ms are an arbitrary number. I don't want to introduce too much
overhead, but since this is in the background we also don't have to
make tasks super short.

Tasks which are going to compile with TurboFan will be posted with
lower priority.

This change requires changes in the CancelableTaskManager. At the
moment it is not possible that a background task posts a new task
which is managed by the same task manager as itself. The problem is
about how to deal with another thread which calls CancelAndWait
concurrently. At the moment, if a new task gets posted after the call
to CancelAndWait, then `CHECK(!canceled_)` in
CancelableTaskManager::Register will fail. If we used a lock to
synchronize the calls to CancelAndWait and Register, then there would
be a deadlock, where the thread which calls CancelAndWait waits for
the task which wants to call Register, but at the same time blocks that
task by holding the lock.

With the change here, posting a task after the call to CancelAndWait
will just immediately cancel the new task. This matches the behavior
you would get if CancelAndWait is called right after calling Register.

Bug: chromium:914757
Change-Id: I6d57aba161db8a915ec0d745658e0c28d25219a8
Reviewed-on: https://chromium-review.googlesource.com/c/1411884
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Reviewed-by: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#58898}
2019-01-17 18:27:08 +00:00

268 lines
7.4 KiB
C++

// 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<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