diff --git a/src/cancelable-task.cc b/src/cancelable-task.cc index ea351f8908..b712d98877 100644 --- a/src/cancelable-task.cc +++ b/src/cancelable-task.cc @@ -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 diff --git a/src/cancelable-task.h b/src/cancelable-task.h index 65f98e7662..578db6f6b9 100644 --- a/src/cancelable-task.h +++ b/src/cancelable-task.h @@ -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 { diff --git a/src/compiler-dispatcher/compiler-dispatcher.cc b/src/compiler-dispatcher/compiler-dispatcher.cc index 5cf10aa3eb..47c04b8b44 100644 --- a/src/compiler-dispatcher/compiler-dispatcher.cc +++ b/src/compiler-dispatcher/compiler-dispatcher.cc @@ -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 function) { @@ -138,12 +193,27 @@ bool CompilerDispatcher::IsEnqueued(Handle function) const { return GetJobFor(function) != jobs_.end(); } +void CompilerDispatcher::WaitForJobIfRunningOnBackground( + CompilerDispatcherJob* job) { + base::LockGuard 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 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 function) { return result; } -void CompilerDispatcher::Abort(Handle 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(isolate_); DCHECK(platform_->IdleTasksEnabled(v8_isolate)); - if (idle_task_scheduled_) return; + { + base::LockGuard lock(&mutex_); + if (idle_task_scheduled_) return; + idle_task_scheduled_ = true; + } + platform_->CallIdleOnForegroundThread( + v8_isolate, new IdleTask(isolate_, task_manager_.get(), this)); +} + +void CompilerDispatcher::ScheduleIdleTaskIfNeeded() { if (jobs_.empty()) return; - idle_task_scheduled_ = true; - platform_->CallIdleOnForegroundThread(v8_isolate, - new IdleTask(isolate_, this)); + ScheduleIdleTaskFromAnyThread(); +} + +void CompilerDispatcher::ConsiderJobForBackgroundProcessing( + CompilerDispatcherJob* job) { + if (!CanRunOnAnyThread(job)) return; + { + base::LockGuard lock(&mutex_); + pending_background_jobs_.insert(job); + } + ScheduleMoreBackgroundTasksIfNeeded(); +} + +void CompilerDispatcher::ScheduleMoreBackgroundTasksIfNeeded() { + if (FLAG_single_threaded) return; + { + base::LockGuard 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 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 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) { - idle_task_scheduled_ = false; + { + base::LockGuard 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> lock( + new base::LockGuard(&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); } diff --git a/src/compiler-dispatcher/compiler-dispatcher.h b/src/compiler-dispatcher/compiler-dispatcher.h index fea141e22f..99c15db925 100644 --- a/src/compiler-dispatcher/compiler-dispatcher.h +++ b/src/compiler-dispatcher/compiler-dispatcher.h @@ -7,9 +7,12 @@ #include #include +#include #include #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::unique_ptr> JobMap; + class BackgroundTask; class IdleTask; + void WaitForJobIfRunningOnBackground(CompilerDispatcherJob* job); bool IsEnabled() const; JobMap::const_iterator GetJobFor(Handle 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 tracer_; - bool idle_task_scheduled_; + std::unique_ptr 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 pending_background_jobs_; + + // The set of CompilerDispatcherJobs currently processed on background + // threads. + std::unordered_set 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); }; diff --git a/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc b/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc index ae176408fd..338041ed8a 100644 --- a/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc +++ b/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc @@ -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 tasks; + tasks.swap(tasks_); + platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, true), + kShortRunningTask); + sem_.Wait(); + } + + void RunBackgroundTasks(Platform* platform) { + std::vector tasks; + tasks.swap(tasks_); + platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, false), + kShortRunningTask); + } + + void ClearBackgroundTasks() { + std::vector 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& 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 tasks_; + bool signal_; + + DISALLOW_COPY_AND_ASSIGN(TaskWrapper); + }; + + IdleTask* idle_task_; double time_; double time_step_; + std::vector 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 f = Handle::cast(RunJS(isolate(), script)); + Handle 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 f = Handle::cast(RunJS(isolate(), script)); + Handle 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