Implement async AbortAll for the compiler dispatcher
BUG=v8:5215 R=marja@chromium.org,vogelheim@chromium.org Review-Url: https://codereview.chromium.org/2615603002 Cr-Commit-Position: refs/heads/master@{#42068}
This commit is contained in:
parent
20defd29e0
commit
e426fdd52b
@ -93,6 +93,24 @@ void CancelableTaskManager::CancelAndWait() {
|
||||
}
|
||||
}
|
||||
|
||||
CancelableTaskManager::TryAbortResult CancelableTaskManager::TryAbortAll() {
|
||||
// Clean up all cancelable fore- and background tasks. Tasks are canceled on
|
||||
// the way if possible, i.e., if they have not started yet.
|
||||
base::LockGuard<base::Mutex> guard(&mutex_);
|
||||
|
||||
if (cancelable_tasks_.empty()) return kTaskRemoved;
|
||||
|
||||
for (auto it = cancelable_tasks_.begin(); it != cancelable_tasks_.end();) {
|
||||
if (it->second->Cancel()) {
|
||||
it = cancelable_tasks_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return cancelable_tasks_.empty() ? kTaskAborted : kTaskRunning;
|
||||
}
|
||||
|
||||
CancelableTask::CancelableTask(Isolate* isolate)
|
||||
: CancelableTask(isolate, isolate->cancelable_task_manager()) {}
|
||||
|
||||
|
@ -45,6 +45,17 @@ class V8_EXPORT_PRIVATE CancelableTaskManager {
|
||||
// already running. This disallows subsequent Register calls.
|
||||
void CancelAndWait();
|
||||
|
||||
// Tries to cancel all remaining registered tasks. The return value indicates
|
||||
// whether
|
||||
//
|
||||
// 1) No tasks were registered (kTaskRemoved), or
|
||||
//
|
||||
// 2) There is at least one remaining task that couldn't be cancelled
|
||||
// (kTaskRunning), or
|
||||
//
|
||||
// 3) All registered tasks were cancelled (kTaskAborted).
|
||||
TryAbortResult TryAbortAll();
|
||||
|
||||
private:
|
||||
// Only called by {Cancelable} destructor. The task is done with executing,
|
||||
// but needs to be removed.
|
||||
|
@ -52,13 +52,11 @@ bool DoNextStepOnMainThread(Isolate* isolate, CompilerDispatcherJob* job,
|
||||
break;
|
||||
}
|
||||
|
||||
if (job->status() == CompileJobStatus::kFailed) {
|
||||
DCHECK(isolate->has_pending_exception());
|
||||
if (exception_handling == ExceptionHandling::kSwallow) {
|
||||
isolate->clear_pending_exception();
|
||||
}
|
||||
} else {
|
||||
DCHECK(!isolate->has_pending_exception());
|
||||
DCHECK_EQ(job->status() == CompileJobStatus::kFailed,
|
||||
isolate->has_pending_exception());
|
||||
if (job->status() == CompileJobStatus::kFailed &&
|
||||
exception_handling == ExceptionHandling::kSwallow) {
|
||||
isolate->clear_pending_exception();
|
||||
}
|
||||
return job->status() != CompileJobStatus::kFailed;
|
||||
}
|
||||
@ -97,6 +95,32 @@ const double kMaxIdleTimeToExpectInMs = 40;
|
||||
|
||||
} // namespace
|
||||
|
||||
class CompilerDispatcher::AbortTask : public CancelableTask {
|
||||
public:
|
||||
AbortTask(Isolate* isolate, CancelableTaskManager* task_manager,
|
||||
CompilerDispatcher* dispatcher);
|
||||
~AbortTask() override;
|
||||
|
||||
// CancelableTask implementation.
|
||||
void RunInternal() override;
|
||||
|
||||
private:
|
||||
CompilerDispatcher* dispatcher_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AbortTask);
|
||||
};
|
||||
|
||||
CompilerDispatcher::AbortTask::AbortTask(Isolate* isolate,
|
||||
CancelableTaskManager* task_manager,
|
||||
CompilerDispatcher* dispatcher)
|
||||
: CancelableTask(isolate, task_manager), dispatcher_(dispatcher) {}
|
||||
|
||||
CompilerDispatcher::AbortTask::~AbortTask() {}
|
||||
|
||||
void CompilerDispatcher::AbortTask::RunInternal() {
|
||||
dispatcher_->AbortInactiveJobs();
|
||||
}
|
||||
|
||||
class CompilerDispatcher::BackgroundTask : public CancelableTask {
|
||||
public:
|
||||
BackgroundTask(Isolate* isolate, CancelableTaskManager* task_manager,
|
||||
@ -156,9 +180,12 @@ CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform,
|
||||
max_stack_size_(max_stack_size),
|
||||
tracer_(new CompilerDispatcherTracer(isolate_)),
|
||||
task_manager_(new CancelableTaskManager()),
|
||||
abort_(false),
|
||||
idle_task_scheduled_(false),
|
||||
num_scheduled_background_tasks_(0),
|
||||
main_thread_blocking_on_job_(nullptr) {}
|
||||
main_thread_blocking_on_job_(nullptr),
|
||||
block_for_testing_(false),
|
||||
semaphore_for_testing_(0) {}
|
||||
|
||||
CompilerDispatcher::~CompilerDispatcher() {
|
||||
// To avoid crashing in unit tests due to unfished jobs.
|
||||
@ -169,6 +196,11 @@ CompilerDispatcher::~CompilerDispatcher() {
|
||||
bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) {
|
||||
if (!IsEnabled()) return false;
|
||||
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
if (abort_) return false;
|
||||
}
|
||||
|
||||
// We only handle functions (no eval / top-level code / wasm) that are
|
||||
// attached to a script.
|
||||
if (!function->script()->IsScript() || !function->is_function() ||
|
||||
@ -223,17 +255,68 @@ bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
|
||||
bool result = job->second->status() != CompileJobStatus::kFailed;
|
||||
job->second->ResetOnMainThread();
|
||||
jobs_.erase(job);
|
||||
if (jobs_.empty()) {
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
abort_ = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void CompilerDispatcher::AbortAll(BlockingBehavior blocking) {
|
||||
// TODO(jochen): Implement support for non-blocking abort.
|
||||
DCHECK(blocking == BlockingBehavior::kBlock);
|
||||
for (auto& kv : jobs_) {
|
||||
WaitForJobIfRunningOnBackground(kv.second.get());
|
||||
kv.second->ResetOnMainThread();
|
||||
bool background_tasks_running =
|
||||
task_manager_->TryAbortAll() == CancelableTaskManager::kTaskRunning;
|
||||
if (!background_tasks_running || blocking == BlockingBehavior::kBlock) {
|
||||
for (auto& it : jobs_) {
|
||||
WaitForJobIfRunningOnBackground(it.second.get());
|
||||
it.second->ResetOnMainThread();
|
||||
}
|
||||
jobs_.clear();
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
DCHECK(pending_background_jobs_.empty());
|
||||
DCHECK(running_background_jobs_.empty());
|
||||
abort_ = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
abort_ = true;
|
||||
pending_background_jobs_.clear();
|
||||
}
|
||||
AbortInactiveJobs();
|
||||
|
||||
// All running background jobs might already have scheduled idle tasks instead
|
||||
// of abort tasks. Schedule a single abort task here to make sure they get
|
||||
// processed as soon as possible (and not first when we have idle time).
|
||||
ScheduleAbortTask();
|
||||
}
|
||||
|
||||
void CompilerDispatcher::AbortInactiveJobs() {
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
// Since we schedule two abort tasks per async abort, we might end up
|
||||
// here with nothing left to do.
|
||||
if (!abort_) return;
|
||||
}
|
||||
for (auto it = jobs_.begin(); it != jobs_.end();) {
|
||||
auto job = it;
|
||||
++it;
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
if (running_background_jobs_.find(job->second.get()) !=
|
||||
running_background_jobs_.end()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
job->second->ResetOnMainThread();
|
||||
jobs_.erase(job);
|
||||
}
|
||||
if (jobs_.empty()) {
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
abort_ = false;
|
||||
}
|
||||
jobs_.clear();
|
||||
}
|
||||
|
||||
CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor(
|
||||
@ -265,6 +348,12 @@ void CompilerDispatcher::ScheduleIdleTaskIfNeeded() {
|
||||
ScheduleIdleTaskFromAnyThread();
|
||||
}
|
||||
|
||||
void CompilerDispatcher::ScheduleAbortTask() {
|
||||
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
|
||||
platform_->CallOnForegroundThread(
|
||||
v8_isolate, new AbortTask(isolate_, task_manager_.get(), this));
|
||||
}
|
||||
|
||||
void CompilerDispatcher::ConsiderJobForBackgroundProcessing(
|
||||
CompilerDispatcherJob* job) {
|
||||
if (!CanRunOnAnyThread(job)) return;
|
||||
@ -304,6 +393,12 @@ void CompilerDispatcher::DoBackgroundWork() {
|
||||
}
|
||||
}
|
||||
if (job == nullptr) return;
|
||||
|
||||
if (V8_UNLIKELY(block_for_testing_.Value())) {
|
||||
block_for_testing_.SetValue(false);
|
||||
semaphore_for_testing_.Wait();
|
||||
}
|
||||
|
||||
DoNextStepOnBackgroundThread(job);
|
||||
|
||||
ScheduleMoreBackgroundTasksIfNeeded();
|
||||
@ -315,6 +410,13 @@ void CompilerDispatcher::DoBackgroundWork() {
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
running_background_jobs_.erase(job);
|
||||
|
||||
if (running_background_jobs_.empty() && abort_) {
|
||||
// This is the last background job that finished. The abort task
|
||||
// scheduled by AbortAll might already have ran, so schedule another
|
||||
// one to be on the safe side.
|
||||
ScheduleAbortTask();
|
||||
}
|
||||
|
||||
if (main_thread_blocking_on_job_ == job) {
|
||||
main_thread_blocking_on_job_ = nullptr;
|
||||
main_thread_blocking_signal_.NotifyOne();
|
||||
@ -325,9 +427,16 @@ void CompilerDispatcher::DoBackgroundWork() {
|
||||
}
|
||||
|
||||
void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
|
||||
bool aborted = false;
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
idle_task_scheduled_ = false;
|
||||
aborted = abort_;
|
||||
}
|
||||
|
||||
if (aborted) {
|
||||
AbortInactiveJobs();
|
||||
return;
|
||||
}
|
||||
|
||||
// Number of jobs that are unlikely to make progress during any idle callback
|
||||
|
@ -10,9 +10,11 @@
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "src/base/atomic-utils.h"
|
||||
#include "src/base/macros.h"
|
||||
#include "src/base/platform/condition-variable.h"
|
||||
#include "src/base/platform/mutex.h"
|
||||
#include "src/base/platform/semaphore.h"
|
||||
#include "src/globals.h"
|
||||
#include "testing/gtest/include/gtest/gtest_prod.h"
|
||||
|
||||
@ -85,20 +87,28 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
|
||||
FRIEND_TEST(CompilerDispatcherTest, IdleTaskSmallIdleTime);
|
||||
FRIEND_TEST(IgnitionCompilerDispatcherTest, CompileOnBackgroundThread);
|
||||
FRIEND_TEST(IgnitionCompilerDispatcherTest, FinishNowWithBackgroundTask);
|
||||
FRIEND_TEST(IgnitionCompilerDispatcherTest,
|
||||
AsyncAbortAllPendingBackgroundTask);
|
||||
FRIEND_TEST(IgnitionCompilerDispatcherTest,
|
||||
AsyncAbortAllRunningBackgroundTask);
|
||||
FRIEND_TEST(IgnitionCompilerDispatcherTest, FinishNowDuringAbortAll);
|
||||
|
||||
typedef std::multimap<std::pair<int, int>,
|
||||
std::unique_ptr<CompilerDispatcherJob>>
|
||||
JobMap;
|
||||
class AbortTask;
|
||||
class BackgroundTask;
|
||||
class IdleTask;
|
||||
|
||||
void WaitForJobIfRunningOnBackground(CompilerDispatcherJob* job);
|
||||
bool IsEnabled() const;
|
||||
void AbortInactiveJobs();
|
||||
JobMap::const_iterator GetJobFor(Handle<SharedFunctionInfo> shared) const;
|
||||
void ConsiderJobForBackgroundProcessing(CompilerDispatcherJob* job);
|
||||
void ScheduleMoreBackgroundTasksIfNeeded();
|
||||
void ScheduleIdleTaskFromAnyThread();
|
||||
void ScheduleIdleTaskIfNeeded();
|
||||
void ScheduleAbortTask();
|
||||
void DoBackgroundWork();
|
||||
void DoIdleWork(double deadline_in_seconds);
|
||||
|
||||
@ -117,6 +127,9 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
|
||||
// the mutex |mutex_| while accessing them.
|
||||
base::Mutex mutex_;
|
||||
|
||||
// True if the dispatcher is in the process of aborting running tasks.
|
||||
bool abort_;
|
||||
|
||||
bool idle_task_scheduled_;
|
||||
|
||||
// Number of currently scheduled BackgroundTask objects.
|
||||
@ -134,6 +147,10 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
|
||||
CompilerDispatcherJob* main_thread_blocking_on_job_;
|
||||
base::ConditionVariable main_thread_blocking_signal_;
|
||||
|
||||
// Test support.
|
||||
base::AtomicValue<bool> block_for_testing_;
|
||||
base::Semaphore semaphore_for_testing_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CompilerDispatcher);
|
||||
};
|
||||
|
||||
|
@ -214,5 +214,50 @@ TEST(CancelableTask, RemoveUnmanagedId) {
|
||||
EXPECT_FALSE(manager.TryAbort(3));
|
||||
}
|
||||
|
||||
TEST(CancelableTask, EmptyTryAbortAll) {
|
||||
CancelableTaskManager manager;
|
||||
EXPECT_EQ(manager.TryAbortAll(), CancelableTaskManager::kTaskRemoved);
|
||||
}
|
||||
|
||||
TEST(CancelableTask, ThreadedMultipleTasksNotRunTryAbortAll) {
|
||||
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);
|
||||
EXPECT_EQ(manager.TryAbortAll(), CancelableTaskManager::kTaskAborted);
|
||||
// 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, ThreadedMultipleTasksStartedTryAbortAll) {
|
||||
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();
|
||||
// Busy wait on result to make sure task1 is done.
|
||||
while (GetValue(&result1) == 0) {
|
||||
}
|
||||
EXPECT_EQ(manager.TryAbortAll(), CancelableTaskManager::kTaskRunning);
|
||||
runner2.Start();
|
||||
runner1.Join();
|
||||
runner2.Join();
|
||||
EXPECT_EQ(GetValue(&result1), 1);
|
||||
EXPECT_EQ(GetValue(&result2), 0);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -70,9 +70,11 @@ namespace {
|
||||
|
||||
class MockPlatform : public v8::Platform {
|
||||
public:
|
||||
MockPlatform() : idle_task_(nullptr), time_(0.0), time_step_(0.0), sem_(0) {}
|
||||
MockPlatform() : time_(0.0), time_step_(0.0), idle_task_(nullptr), sem_(0) {}
|
||||
~MockPlatform() override {
|
||||
EXPECT_TRUE(tasks_.empty());
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
EXPECT_TRUE(foreground_tasks_.empty());
|
||||
EXPECT_TRUE(background_tasks_.empty());
|
||||
EXPECT_TRUE(idle_task_ == nullptr);
|
||||
}
|
||||
|
||||
@ -80,11 +82,13 @@ class MockPlatform : public v8::Platform {
|
||||
|
||||
void CallOnBackgroundThread(Task* task,
|
||||
ExpectedRuntime expected_runtime) override {
|
||||
tasks_.push_back(task);
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
background_tasks_.push_back(task);
|
||||
}
|
||||
|
||||
void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override {
|
||||
UNREACHABLE();
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
foreground_tasks_.push_back(task);
|
||||
}
|
||||
|
||||
void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task,
|
||||
@ -94,6 +98,7 @@ class MockPlatform : public v8::Platform {
|
||||
|
||||
void CallIdleOnForegroundThread(v8::Isolate* isolate,
|
||||
IdleTask* task) override {
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
ASSERT_TRUE(idle_task_ == nullptr);
|
||||
idle_task_ = task;
|
||||
}
|
||||
@ -106,21 +111,39 @@ class MockPlatform : public v8::Platform {
|
||||
}
|
||||
|
||||
void RunIdleTask(double deadline_in_seconds, double time_step) {
|
||||
ASSERT_TRUE(idle_task_ != nullptr);
|
||||
time_step_ = time_step;
|
||||
IdleTask* task = idle_task_;
|
||||
idle_task_ = nullptr;
|
||||
IdleTask* task;
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
task = idle_task_;
|
||||
ASSERT_TRUE(idle_task_ != nullptr);
|
||||
idle_task_ = nullptr;
|
||||
}
|
||||
task->Run(deadline_in_seconds);
|
||||
delete task;
|
||||
}
|
||||
|
||||
bool IdleTaskPending() const { return idle_task_; }
|
||||
bool IdleTaskPending() {
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
return idle_task_;
|
||||
}
|
||||
|
||||
bool BackgroundTasksPending() const { return !tasks_.empty(); }
|
||||
bool BackgroundTasksPending() {
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
return !background_tasks_.empty();
|
||||
}
|
||||
|
||||
bool ForegroundTasksPending() {
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
return !foreground_tasks_.empty();
|
||||
}
|
||||
|
||||
void RunBackgroundTasksAndBlock(Platform* platform) {
|
||||
std::vector<Task*> tasks;
|
||||
tasks.swap(tasks_);
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
tasks.swap(background_tasks_);
|
||||
}
|
||||
platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, true),
|
||||
kShortRunningTask);
|
||||
sem_.Wait();
|
||||
@ -128,20 +151,50 @@ class MockPlatform : public v8::Platform {
|
||||
|
||||
void RunBackgroundTasks(Platform* platform) {
|
||||
std::vector<Task*> tasks;
|
||||
tasks.swap(tasks_);
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
tasks.swap(background_tasks_);
|
||||
}
|
||||
platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, false),
|
||||
kShortRunningTask);
|
||||
}
|
||||
|
||||
void RunForegroundTasks() {
|
||||
std::vector<Task*> tasks;
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
tasks.swap(foreground_tasks_);
|
||||
}
|
||||
for (auto& task : tasks) {
|
||||
task->Run();
|
||||
delete task;
|
||||
}
|
||||
}
|
||||
|
||||
void ClearBackgroundTasks() {
|
||||
std::vector<Task*> tasks;
|
||||
tasks.swap(tasks_);
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
tasks.swap(background_tasks_);
|
||||
}
|
||||
for (auto& task : tasks) {
|
||||
delete task;
|
||||
}
|
||||
}
|
||||
|
||||
void ClearForegroundTasks() {
|
||||
std::vector<Task*> tasks;
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
tasks.swap(foreground_tasks_);
|
||||
}
|
||||
for (auto& task : tasks) {
|
||||
delete task;
|
||||
}
|
||||
}
|
||||
|
||||
void ClearIdleTask() {
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
ASSERT_TRUE(idle_task_ != nullptr);
|
||||
delete idle_task_;
|
||||
idle_task_ = nullptr;
|
||||
@ -171,11 +224,16 @@ class MockPlatform : public v8::Platform {
|
||||
DISALLOW_COPY_AND_ASSIGN(TaskWrapper);
|
||||
};
|
||||
|
||||
IdleTask* idle_task_;
|
||||
double time_;
|
||||
double time_step_;
|
||||
|
||||
std::vector<Task*> tasks_;
|
||||
// Protects all *_tasks_.
|
||||
base::Mutex mutex_;
|
||||
|
||||
IdleTask* idle_task_;
|
||||
std::vector<Task*> background_tasks_;
|
||||
std::vector<Task*> foreground_tasks_;
|
||||
|
||||
base::Semaphore sem_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MockPlatform);
|
||||
@ -459,5 +517,202 @@ TEST_F(CompilerDispatcherTest, FinishNowException) {
|
||||
platform.ClearIdleTask();
|
||||
}
|
||||
|
||||
TEST_F(IgnitionCompilerDispatcherTest, AsyncAbortAllPendingBackgroundTask) {
|
||||
MockPlatform platform;
|
||||
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
|
||||
|
||||
const char script[] =
|
||||
"function g() { var y = 1; function f11(x) { return x * y }; return f11; "
|
||||
"} g();";
|
||||
Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script));
|
||||
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
|
||||
|
||||
ASSERT_FALSE(platform.IdleTaskPending());
|
||||
ASSERT_TRUE(dispatcher.Enqueue(shared));
|
||||
ASSERT_TRUE(platform.IdleTaskPending());
|
||||
|
||||
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
|
||||
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
|
||||
CompileJobStatus::kInitial);
|
||||
|
||||
// Make compiling super expensive, and advance job as much as possible on the
|
||||
// foreground thread.
|
||||
dispatcher.tracer_->RecordCompile(50000.0, 1);
|
||||
platform.RunIdleTask(10.0, 0.0);
|
||||
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
|
||||
CompileJobStatus::kReadyToCompile);
|
||||
|
||||
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
|
||||
ASSERT_FALSE(shared->is_compiled());
|
||||
ASSERT_FALSE(platform.IdleTaskPending());
|
||||
ASSERT_TRUE(platform.BackgroundTasksPending());
|
||||
|
||||
// The background task hasn't yet started, so we can just cancel it.
|
||||
dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock);
|
||||
ASSERT_FALSE(platform.ForegroundTasksPending());
|
||||
|
||||
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
|
||||
ASSERT_FALSE(shared->is_compiled());
|
||||
|
||||
platform.RunBackgroundTasksAndBlock(V8::GetCurrentPlatform());
|
||||
|
||||
if (platform.IdleTaskPending()) platform.ClearIdleTask();
|
||||
ASSERT_FALSE(platform.BackgroundTasksPending());
|
||||
ASSERT_FALSE(platform.ForegroundTasksPending());
|
||||
}
|
||||
|
||||
TEST_F(IgnitionCompilerDispatcherTest, AsyncAbortAllRunningBackgroundTask) {
|
||||
MockPlatform platform;
|
||||
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
|
||||
|
||||
const char script1[] =
|
||||
"function g() { var y = 1; function f11(x) { return x * y }; return f11; "
|
||||
"} g();";
|
||||
Handle<JSFunction> f1 = Handle<JSFunction>::cast(RunJS(isolate(), script1));
|
||||
Handle<SharedFunctionInfo> shared1(f1->shared(), i_isolate());
|
||||
|
||||
const char script2[] =
|
||||
"function g() { var y = 1; function f12(x) { return x * y }; return f12; "
|
||||
"} g();";
|
||||
Handle<JSFunction> f2 = Handle<JSFunction>::cast(RunJS(isolate(), script2));
|
||||
Handle<SharedFunctionInfo> shared2(f2->shared(), i_isolate());
|
||||
|
||||
ASSERT_FALSE(platform.IdleTaskPending());
|
||||
ASSERT_TRUE(dispatcher.Enqueue(shared1));
|
||||
ASSERT_TRUE(platform.IdleTaskPending());
|
||||
|
||||
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
|
||||
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
|
||||
CompileJobStatus::kInitial);
|
||||
|
||||
// Make compiling super expensive, and advance job as much as possible on the
|
||||
// foreground thread.
|
||||
dispatcher.tracer_->RecordCompile(50000.0, 1);
|
||||
platform.RunIdleTask(10.0, 0.0);
|
||||
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
|
||||
CompileJobStatus::kReadyToCompile);
|
||||
|
||||
ASSERT_TRUE(dispatcher.IsEnqueued(shared1));
|
||||
ASSERT_FALSE(shared1->is_compiled());
|
||||
ASSERT_FALSE(platform.IdleTaskPending());
|
||||
ASSERT_TRUE(platform.BackgroundTasksPending());
|
||||
|
||||
// Kick off background tasks and freeze them.
|
||||
dispatcher.block_for_testing_.SetValue(true);
|
||||
platform.RunBackgroundTasks(V8::GetCurrentPlatform());
|
||||
|
||||
// Busy loop until the background task started running.
|
||||
while (dispatcher.block_for_testing_.Value()) {
|
||||
}
|
||||
dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock);
|
||||
ASSERT_TRUE(platform.ForegroundTasksPending());
|
||||
|
||||
// We can't schedule new tasks while we're aborting.
|
||||
ASSERT_FALSE(dispatcher.Enqueue(shared2));
|
||||
|
||||
// Run the first AbortTask. Since the background job is still pending, it
|
||||
// can't do anything.
|
||||
platform.RunForegroundTasks();
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
|
||||
ASSERT_TRUE(dispatcher.abort_);
|
||||
}
|
||||
|
||||
// Release background task.
|
||||
dispatcher.semaphore_for_testing_.Signal();
|
||||
|
||||
// Busy loop until the background task scheduled another AbortTask task.
|
||||
while (!platform.ForegroundTasksPending()) {
|
||||
}
|
||||
|
||||
platform.RunForegroundTasks();
|
||||
ASSERT_TRUE(dispatcher.jobs_.empty());
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
|
||||
ASSERT_FALSE(dispatcher.abort_);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(platform.IdleTaskPending());
|
||||
platform.RunIdleTask(5.0, 1.0);
|
||||
ASSERT_FALSE(platform.BackgroundTasksPending());
|
||||
ASSERT_FALSE(platform.ForegroundTasksPending());
|
||||
|
||||
// Now it's possible to enqueue new functions again.
|
||||
ASSERT_TRUE(dispatcher.Enqueue(shared2));
|
||||
ASSERT_TRUE(platform.IdleTaskPending());
|
||||
ASSERT_FALSE(platform.BackgroundTasksPending());
|
||||
ASSERT_FALSE(platform.ForegroundTasksPending());
|
||||
platform.ClearIdleTask();
|
||||
}
|
||||
|
||||
TEST_F(IgnitionCompilerDispatcherTest, FinishNowDuringAbortAll) {
|
||||
MockPlatform platform;
|
||||
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
|
||||
|
||||
const char script[] =
|
||||
"function g() { var y = 1; function f13(x) { return x * y }; return f13; "
|
||||
"} g();";
|
||||
Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script));
|
||||
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
|
||||
|
||||
ASSERT_FALSE(platform.IdleTaskPending());
|
||||
ASSERT_TRUE(dispatcher.Enqueue(shared));
|
||||
ASSERT_TRUE(platform.IdleTaskPending());
|
||||
|
||||
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
|
||||
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
|
||||
CompileJobStatus::kInitial);
|
||||
|
||||
// Make compiling super expensive, and advance job as much as possible on the
|
||||
// foreground thread.
|
||||
dispatcher.tracer_->RecordCompile(50000.0, 1);
|
||||
platform.RunIdleTask(10.0, 0.0);
|
||||
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
|
||||
CompileJobStatus::kReadyToCompile);
|
||||
|
||||
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
|
||||
ASSERT_FALSE(shared->is_compiled());
|
||||
ASSERT_FALSE(platform.IdleTaskPending());
|
||||
ASSERT_TRUE(platform.BackgroundTasksPending());
|
||||
|
||||
// Kick off background tasks and freeze them.
|
||||
dispatcher.block_for_testing_.SetValue(true);
|
||||
platform.RunBackgroundTasks(V8::GetCurrentPlatform());
|
||||
|
||||
// Busy loop until the background task started running.
|
||||
while (dispatcher.block_for_testing_.Value()) {
|
||||
}
|
||||
dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock);
|
||||
ASSERT_TRUE(platform.ForegroundTasksPending());
|
||||
|
||||
// Run the first AbortTask. Since the background job is still pending, it
|
||||
// can't do anything.
|
||||
platform.RunForegroundTasks();
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
|
||||
ASSERT_TRUE(dispatcher.abort_);
|
||||
}
|
||||
|
||||
// While the background thread holds on to a job, it is still enqueud.
|
||||
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
|
||||
|
||||
// Release background task.
|
||||
dispatcher.semaphore_for_testing_.Signal();
|
||||
|
||||
// Force the compilation to finish, even while aborting.
|
||||
ASSERT_TRUE(dispatcher.FinishNow(shared));
|
||||
ASSERT_TRUE(dispatcher.jobs_.empty());
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
|
||||
ASSERT_FALSE(dispatcher.abort_);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(platform.ForegroundTasksPending());
|
||||
ASSERT_TRUE(platform.IdleTaskPending());
|
||||
ASSERT_FALSE(platform.BackgroundTasksPending());
|
||||
platform.ClearForegroundTasks();
|
||||
platform.ClearIdleTask();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user