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:
jochen 2017-01-04 05:45:56 -08:00 committed by Commit bot
parent 20defd29e0
commit e426fdd52b
6 changed files with 483 additions and 28 deletions

View File

@ -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()) {}

View File

@ -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.

View File

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

View File

@ -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);
};

View File

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

View File

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