Reland "Use background tasks for the compiler dispatcher

Original issue's description:
> Use background tasks for the compiler dispatcher
>
> BUG=v8:5215
> R=marja@chromium.org,vogelheim@chromium.org
>
> Review-Url: https://codereview.chromium.org/2606263002
> Cr-Commit-Position: refs/heads/master@{#42035}
> Committed: 7a1b3a7beb

BUG=v8:5215
TBR=marja@chromium.org,vogelheim@chromium.org,rmcilroy@chromium.org

Review-Url: https://codereview.chromium.org/2613483002
Cr-Commit-Position: refs/heads/master@{#42040}
This commit is contained in:
jochen 2017-01-03 09:00:56 -08:00 committed by Commit bot
parent dc4586ce0c
commit efb329a8ab
5 changed files with 432 additions and 42 deletions

View File

@ -93,13 +93,18 @@ void CancelableTaskManager::CancelAndWait() {
}
}
CancelableTask::CancelableTask(Isolate* isolate)
: Cancelable(isolate->cancelable_task_manager()), isolate_(isolate) {}
: CancelableTask(isolate, isolate->cancelable_task_manager()) {}
CancelableTask::CancelableTask(Isolate* isolate, CancelableTaskManager* manager)
: Cancelable(manager), isolate_(isolate) {}
CancelableIdleTask::CancelableIdleTask(Isolate* isolate)
: Cancelable(isolate->cancelable_task_manager()), isolate_(isolate) {}
: CancelableIdleTask(isolate, isolate->cancelable_task_manager()) {}
CancelableIdleTask::CancelableIdleTask(Isolate* isolate,
CancelableTaskManager* manager)
: Cancelable(manager), isolate_(isolate) {}
} // namespace internal
} // namespace v8

View File

@ -126,6 +126,7 @@ class V8_EXPORT_PRIVATE Cancelable {
class CancelableTask : public Cancelable, public Task {
public:
explicit CancelableTask(Isolate* isolate);
CancelableTask(Isolate* isolate, CancelableTaskManager* manager);
// Task overrides.
void Run() final {
@ -148,6 +149,7 @@ class CancelableTask : public Cancelable, public Task {
class CancelableIdleTask : public Cancelable, public IdleTask {
public:
explicit CancelableIdleTask(Isolate* isolate);
CancelableIdleTask(Isolate* isolate, CancelableTaskManager* manager);
// IdleTask overrides.
void Run(double deadline_in_seconds) final {

View File

@ -66,15 +66,65 @@ bool IsFinished(CompilerDispatcherJob* job) {
job->status() == CompileJobStatus::kFailed;
}
bool CanRunOnAnyThread(CompilerDispatcherJob* job) {
return (job->status() == CompileJobStatus::kReadyToParse &&
job->can_parse_on_background_thread()) ||
(job->status() == CompileJobStatus::kReadyToCompile &&
job->can_compile_on_background_thread());
}
void DoNextStepOnBackgroundThread(CompilerDispatcherJob* job) {
DCHECK(CanRunOnAnyThread(job));
switch (job->status()) {
case CompileJobStatus::kReadyToParse:
job->Parse();
break;
case CompileJobStatus::kReadyToCompile:
job->Compile();
break;
default:
UNREACHABLE();
}
}
// Theoretically we get 50ms of idle time max, however it's unlikely that
// we'll get all of it so try to be a conservative.
const double kMaxIdleTimeToExpectInMs = 40;
} // namespace
class CompilerDispatcher::BackgroundTask : public CancelableTask {
public:
BackgroundTask(Isolate* isolate, CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher);
~BackgroundTask() override;
// CancelableTask implementation.
void RunInternal() override;
private:
CompilerDispatcher* dispatcher_;
DISALLOW_COPY_AND_ASSIGN(BackgroundTask);
};
CompilerDispatcher::BackgroundTask::BackgroundTask(
Isolate* isolate, CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher)
: CancelableTask(isolate, task_manager), dispatcher_(dispatcher) {}
CompilerDispatcher::BackgroundTask::~BackgroundTask() {}
void CompilerDispatcher::BackgroundTask::RunInternal() {
dispatcher_->DoBackgroundWork();
}
class CompilerDispatcher::IdleTask : public CancelableIdleTask {
public:
IdleTask(Isolate* isolate, CompilerDispatcher* dispatcher);
IdleTask(Isolate* isolate, CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher);
~IdleTask() override;
// CancelableIdleTask implementation.
@ -87,8 +137,9 @@ class CompilerDispatcher::IdleTask : public CancelableIdleTask {
};
CompilerDispatcher::IdleTask::IdleTask(Isolate* isolate,
CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher)
: CancelableIdleTask(isolate), dispatcher_(dispatcher) {}
: CancelableIdleTask(isolate, task_manager), dispatcher_(dispatcher) {}
CompilerDispatcher::IdleTask::~IdleTask() {}
@ -102,11 +153,15 @@ CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform,
platform_(platform),
max_stack_size_(max_stack_size),
tracer_(new CompilerDispatcherTracer(isolate_)),
idle_task_scheduled_(false) {}
task_manager_(new CancelableTaskManager()),
idle_task_scheduled_(false),
num_scheduled_background_tasks_(0),
main_thread_blocking_on_job_(nullptr) {}
CompilerDispatcher::~CompilerDispatcher() {
// To avoid crashing in unit tests due to unfished jobs.
AbortAll(BlockingBehavior::kBlock);
task_manager_->CancelAndWait();
}
bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) {
@ -138,12 +193,27 @@ bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const {
return GetJobFor(function) != jobs_.end();
}
void CompilerDispatcher::WaitForJobIfRunningOnBackground(
CompilerDispatcherJob* job) {
base::LockGuard<base::Mutex> lock(&mutex_);
if (running_background_jobs_.find(job) == running_background_jobs_.end()) {
pending_background_jobs_.erase(job);
return;
}
DCHECK_NULL(main_thread_blocking_on_job_);
main_thread_blocking_on_job_ = job;
while (main_thread_blocking_on_job_ != nullptr) {
main_thread_blocking_signal_.Wait(&mutex_);
}
DCHECK(pending_background_jobs_.find(job) == pending_background_jobs_.end());
DCHECK(running_background_jobs_.find(job) == running_background_jobs_.end());
}
bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
JobMap::const_iterator job = GetJobFor(function);
CHECK(job != jobs_.end());
// TODO(jochen): Check if there's an in-flight background task working on this
// job.
WaitForJobIfRunningOnBackground(job->second.get());
while (!IsFinished(job->second.get())) {
DoNextStepOnMainThread(isolate_, job->second.get(),
ExceptionHandling::kThrow);
@ -154,23 +224,11 @@ bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
return result;
}
void CompilerDispatcher::Abort(Handle<SharedFunctionInfo> function,
BlockingBehavior blocking) {
USE(blocking);
JobMap::const_iterator job = GetJobFor(function);
CHECK(job != jobs_.end());
// TODO(jochen): Check if there's an in-flight background task working on this
// job.
job->second->ResetOnMainThread();
jobs_.erase(job);
}
void CompilerDispatcher::AbortAll(BlockingBehavior blocking) {
USE(blocking);
// TODO(jochen): Check if there's an in-flight background task working on this
// job.
// TODO(jochen): Implement support for non-blocking abort.
DCHECK(blocking == BlockingBehavior::kBlock);
for (auto& kv : jobs_) {
WaitForJobIfRunningOnBackground(kv.second.get());
kv.second->ResetOnMainThread();
}
jobs_.clear();
@ -188,18 +246,87 @@ CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor(
return jobs_.end();
}
void CompilerDispatcher::ScheduleIdleTaskIfNeeded() {
void CompilerDispatcher::ScheduleIdleTaskFromAnyThread() {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
DCHECK(platform_->IdleTasksEnabled(v8_isolate));
{
base::LockGuard<base::Mutex> lock(&mutex_);
if (idle_task_scheduled_) return;
if (jobs_.empty()) return;
idle_task_scheduled_ = true;
platform_->CallIdleOnForegroundThread(v8_isolate,
new IdleTask(isolate_, this));
}
platform_->CallIdleOnForegroundThread(
v8_isolate, new IdleTask(isolate_, task_manager_.get(), this));
}
void CompilerDispatcher::ScheduleIdleTaskIfNeeded() {
if (jobs_.empty()) return;
ScheduleIdleTaskFromAnyThread();
}
void CompilerDispatcher::ConsiderJobForBackgroundProcessing(
CompilerDispatcherJob* job) {
if (!CanRunOnAnyThread(job)) return;
{
base::LockGuard<base::Mutex> lock(&mutex_);
pending_background_jobs_.insert(job);
}
ScheduleMoreBackgroundTasksIfNeeded();
}
void CompilerDispatcher::ScheduleMoreBackgroundTasksIfNeeded() {
if (FLAG_single_threaded) return;
{
base::LockGuard<base::Mutex> lock(&mutex_);
if (pending_background_jobs_.empty()) return;
if (platform_->NumberOfAvailableBackgroundThreads() <=
num_scheduled_background_tasks_) {
return;
}
++num_scheduled_background_tasks_;
}
platform_->CallOnBackgroundThread(
new BackgroundTask(isolate_, task_manager_.get(), this),
v8::Platform::kShortRunningTask);
}
void CompilerDispatcher::DoBackgroundWork() {
CompilerDispatcherJob* job = nullptr;
{
base::LockGuard<base::Mutex> lock(&mutex_);
--num_scheduled_background_tasks_;
if (!pending_background_jobs_.empty()) {
auto it = pending_background_jobs_.begin();
job = *it;
pending_background_jobs_.erase(it);
running_background_jobs_.insert(job);
}
}
if (job == nullptr) return;
DoNextStepOnBackgroundThread(job);
ScheduleMoreBackgroundTasksIfNeeded();
// Unconditionally schedule an idle task, as all background steps have to be
// followed by a main thread step.
ScheduleIdleTaskFromAnyThread();
{
base::LockGuard<base::Mutex> lock(&mutex_);
running_background_jobs_.erase(job);
if (main_thread_blocking_on_job_ == job) {
main_thread_blocking_on_job_ = nullptr;
main_thread_blocking_signal_.NotifyOne();
}
}
// Don't touch |this| anymore after this point, as it might have been
// deleted.
}
void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
{
base::LockGuard<base::Mutex> lock(&mutex_);
idle_task_scheduled_ = false;
}
// Number of jobs that are unlikely to make progress during any idle callback
// due to their estimated duration.
@ -214,6 +341,17 @@ void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
job != jobs_.end() && idle_time_in_seconds > 0.0;
idle_time_in_seconds =
deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) {
// Don't work on jobs that are being worked on by background tasks.
// Similarly, remove jobs we work on from the set of available background
// jobs.
std::unique_ptr<base::LockGuard<base::Mutex>> lock(
new base::LockGuard<base::Mutex>(&mutex_));
if (running_background_jobs_.find(job->second.get()) !=
running_background_jobs_.end()) {
++job;
continue;
}
auto it = pending_background_jobs_.find(job->second.get());
double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs();
if (idle_time_in_seconds <
(estimate_in_ms /
@ -222,14 +360,23 @@ void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
// have managed to finish the job in a large idle task to assess
// whether we should ask for another idle callback.
if (estimate_in_ms > kMaxIdleTimeToExpectInMs) ++too_long_jobs;
if (it == pending_background_jobs_.end()) {
lock.reset();
ConsiderJobForBackgroundProcessing(job->second.get());
}
++job;
} else if (IsFinished(job->second.get())) {
DCHECK(it == pending_background_jobs_.end());
job->second->ResetOnMainThread();
job = jobs_.erase(job);
break;
continue;
} else {
// Do one step, and keep processing the job (as we don't advance the
// iterator).
if (it != pending_background_jobs_.end()) {
pending_background_jobs_.erase(it);
}
lock.reset();
DoNextStepOnMainThread(isolate_, job->second.get(),
ExceptionHandling::kSwallow);
}

View File

@ -7,9 +7,12 @@
#include <map>
#include <memory>
#include <unordered_set>
#include <utility>
#include "src/base/macros.h"
#include "src/base/platform/condition-variable.h"
#include "src/base/platform/mutex.h"
#include "src/globals.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
@ -19,6 +22,7 @@ class Platform;
namespace internal {
class CancelableTaskManager;
class CompilerDispatcherJob;
class CompilerDispatcherTracer;
class Isolate;
@ -29,6 +33,30 @@ class Handle;
// The CompilerDispatcher uses a combination of idle tasks and background tasks
// to parse and compile lazily parsed functions.
//
// As both parsing and compilation currently requires a preparation and
// finalization step that happens on the main thread, every task has to be
// advanced during idle time first. Depending on the properties of the task, it
// can then be parsed or compiled on either background threads, or during idle
// time. Last, it has to be finalized during idle time again.
//
// CompilerDispatcher::jobs_ maintains the list of all CompilerDispatcherJobs
// the CompilerDispatcher knows about.
//
// CompilerDispatcher::pending_background_jobs_ contains the set of
// CompilerDispatcherJobs that can be processed on a background thread.
//
// CompilerDispatcher::running_background_jobs_ contains the set of
// CompilerDispatcherJobs that are currently being processed on a background
// thread.
//
// CompilerDispatcher::DoIdleWork tries to advance as many jobs out of jobs_ as
// possible during idle time. If a job can't be advanced, but is suitable for
// background processing, it fires off background threads.
//
// CompilerDispatcher::DoBackgroundWork advances one of the pending jobs, and
// then spins of another idle task to potentially do the final step on the main
// thread.
class V8_EXPORT_PRIVATE CompilerDispatcher {
public:
enum class BlockingBehavior { kBlock, kDontBlock };
@ -55,15 +83,23 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
private:
FRIEND_TEST(CompilerDispatcherTest, IdleTaskSmallIdleTime);
FRIEND_TEST(IgnitionCompilerDispatcherTest, CompileOnBackgroundThread);
FRIEND_TEST(IgnitionCompilerDispatcherTest, FinishNowWithBackgroundTask);
typedef std::multimap<std::pair<int, int>,
std::unique_ptr<CompilerDispatcherJob>>
JobMap;
class BackgroundTask;
class IdleTask;
void WaitForJobIfRunningOnBackground(CompilerDispatcherJob* job);
bool IsEnabled() const;
JobMap::const_iterator GetJobFor(Handle<SharedFunctionInfo> shared) const;
void ConsiderJobForBackgroundProcessing(CompilerDispatcherJob* job);
void ScheduleMoreBackgroundTasksIfNeeded();
void ScheduleIdleTaskFromAnyThread();
void ScheduleIdleTaskIfNeeded();
void DoBackgroundWork();
void DoIdleWork(double deadline_in_seconds);
Isolate* isolate_;
@ -71,12 +107,33 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
size_t max_stack_size_;
std::unique_ptr<CompilerDispatcherTracer> tracer_;
bool idle_task_scheduled_;
std::unique_ptr<CancelableTaskManager> task_manager_;
// Mapping from (script id, function literal id) to job. We use a multimap,
// as script id is not necessarily unique.
JobMap jobs_;
// The following members can be accessed from any thread. Methods need to hold
// the mutex |mutex_| while accessing them.
base::Mutex mutex_;
bool idle_task_scheduled_;
// Number of currently scheduled BackgroundTask objects.
size_t num_scheduled_background_tasks_;
// The set of CompilerDispatcherJobs that can be advanced on any thread.
std::unordered_set<CompilerDispatcherJob*> pending_background_jobs_;
// The set of CompilerDispatcherJobs currently processed on background
// threads.
std::unordered_set<CompilerDispatcherJob*> running_background_jobs_;
// If not nullptr, then the main thread waits for the task processing
// this job, and blocks on the ConditionVariable main_thread_blocking_signal_.
CompilerDispatcherJob* main_thread_blocking_on_job_;
base::ConditionVariable main_thread_blocking_signal_;
DISALLOW_COPY_AND_ASSIGN(CompilerDispatcher);
};

View File

@ -5,10 +5,13 @@
#include "src/compiler-dispatcher/compiler-dispatcher.h"
#include "include/v8-platform.h"
#include "src/base/platform/semaphore.h"
#include "src/compiler-dispatcher/compiler-dispatcher-job.h"
#include "src/compiler-dispatcher/compiler-dispatcher-tracer.h"
#include "src/flags.h"
#include "src/handles.h"
#include "src/objects-inl.h"
#include "src/v8.h"
#include "test/unittests/compiler-dispatcher/compiler-dispatcher-helper.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
@ -40,16 +43,44 @@ class CompilerDispatcherTest : public TestWithContext {
bool CompilerDispatcherTest::old_flag_;
class IgnitionCompilerDispatcherTest : public CompilerDispatcherTest {
public:
IgnitionCompilerDispatcherTest() = default;
~IgnitionCompilerDispatcherTest() override = default;
static void SetUpTestCase() {
old_flag_ = i::FLAG_ignition;
i::FLAG_ignition = true;
CompilerDispatcherTest::SetUpTestCase();
}
static void TearDownTestCase() {
CompilerDispatcherTest::TearDownTestCase();
i::FLAG_ignition = old_flag_;
}
private:
static bool old_flag_;
DISALLOW_COPY_AND_ASSIGN(IgnitionCompilerDispatcherTest);
};
bool IgnitionCompilerDispatcherTest::old_flag_;
namespace {
class MockPlatform : public v8::Platform {
public:
MockPlatform() : task_(nullptr), time_(0.0), time_step_(0.0) {}
~MockPlatform() override = default;
MockPlatform() : idle_task_(nullptr), time_(0.0), time_step_(0.0), sem_(0) {}
~MockPlatform() override {
EXPECT_TRUE(tasks_.empty());
EXPECT_TRUE(idle_task_ == nullptr);
}
size_t NumberOfAvailableBackgroundThreads() override { return 1; }
void CallOnBackgroundThread(Task* task,
ExpectedRuntime expected_runtime) override {
UNREACHABLE();
tasks_.push_back(task);
}
void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override {
@ -63,7 +94,8 @@ class MockPlatform : public v8::Platform {
void CallIdleOnForegroundThread(v8::Isolate* isolate,
IdleTask* task) override {
task_ = task;
ASSERT_TRUE(idle_task_ == nullptr);
idle_task_ = task;
}
bool IdleTasksEnabled(v8::Isolate* isolate) override { return true; }
@ -74,21 +106,78 @@ class MockPlatform : public v8::Platform {
}
void RunIdleTask(double deadline_in_seconds, double time_step) {
ASSERT_TRUE(task_ != nullptr);
ASSERT_TRUE(idle_task_ != nullptr);
time_step_ = time_step;
IdleTask* task = task_;
task_ = nullptr;
IdleTask* task = idle_task_;
idle_task_ = nullptr;
task->Run(deadline_in_seconds);
delete task;
}
bool IdleTaskPending() const { return !!task_; }
bool IdleTaskPending() const { return idle_task_; }
bool BackgroundTasksPending() const { return !tasks_.empty(); }
void RunBackgroundTasksAndBlock(Platform* platform) {
std::vector<Task*> tasks;
tasks.swap(tasks_);
platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, true),
kShortRunningTask);
sem_.Wait();
}
void RunBackgroundTasks(Platform* platform) {
std::vector<Task*> tasks;
tasks.swap(tasks_);
platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, false),
kShortRunningTask);
}
void ClearBackgroundTasks() {
std::vector<Task*> tasks;
tasks.swap(tasks_);
for (auto& task : tasks) {
delete task;
}
}
void ClearIdleTask() {
ASSERT_TRUE(idle_task_ != nullptr);
delete idle_task_;
idle_task_ = nullptr;
}
private:
IdleTask* task_;
class TaskWrapper : public Task {
public:
TaskWrapper(MockPlatform* platform, const std::vector<Task*>& tasks,
bool signal)
: platform_(platform), tasks_(tasks), signal_(signal) {}
~TaskWrapper() = default;
void Run() override {
for (auto& task : tasks_) {
task->Run();
delete task;
}
if (signal_) platform_->sem_.Signal();
}
private:
MockPlatform* platform_;
std::vector<Task*> tasks_;
bool signal_;
DISALLOW_COPY_AND_ASSIGN(TaskWrapper);
};
IdleTask* idle_task_;
double time_;
double time_step_;
std::vector<Task*> tasks_;
base::Semaphore sem_;
DISALLOW_COPY_AND_ASSIGN(MockPlatform);
};
@ -112,8 +201,10 @@ TEST_F(CompilerDispatcherTest, IsEnqueued) {
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
dispatcher.Abort(shared, CompilerDispatcher::BlockingBehavior::kBlock);
dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kBlock);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, FinishNow) {
@ -132,6 +223,8 @@ TEST_F(CompilerDispatcherTest, FinishNow) {
// Finishing removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, IdleTask) {
@ -188,7 +281,7 @@ TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) {
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
CompileJobStatus::kReadyToParse);
// Only grant a lot of idle time and freeze time.
// Now grant a lot of idle time and freeze time.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
@ -225,5 +318,91 @@ TEST_F(CompilerDispatcherTest, IdleTaskException) {
ASSERT_FALSE(try_catch.HasCaught());
}
TEST_F(IgnitionCompilerDispatcherTest, CompileOnBackgroundThread) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] =
"function g() { var y = 1; function f6(x) { return x * y }; return f6; } "
"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());
platform.RunBackgroundTasksAndBlock(V8::GetCurrentPlatform());
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(platform.BackgroundTasksPending());
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
CompileJobStatus::kCompiled);
// Now grant a lot of idle time and freeze time.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
}
TEST_F(IgnitionCompilerDispatcherTest, FinishNowWithBackgroundTask) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] =
"function g() { var y = 1; function f7(x) { return x * y }; return f7; } "
"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());
// This does not block, but races with the FinishNow() call below.
platform.RunBackgroundTasks(V8::GetCurrentPlatform());
ASSERT_TRUE(dispatcher.FinishNow(shared));
// Finishing removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
if (platform.IdleTaskPending()) platform.ClearIdleTask();
ASSERT_FALSE(platform.BackgroundTasksPending());
}
} // namespace internal
} // namespace v8