diff --git a/src/cancelable-task.cc b/src/cancelable-task.cc index b712d98877..b0387f4dc0 100644 --- a/src/cancelable-task.cc +++ b/src/cancelable-task.cc @@ -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 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()) {} diff --git a/src/cancelable-task.h b/src/cancelable-task.h index 578db6f6b9..04f775cbe8 100644 --- a/src/cancelable-task.h +++ b/src/cancelable-task.h @@ -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. diff --git a/src/compiler-dispatcher/compiler-dispatcher.cc b/src/compiler-dispatcher/compiler-dispatcher.cc index 782a6ed855..ba5154e011 100644 --- a/src/compiler-dispatcher/compiler-dispatcher.cc +++ b/src/compiler-dispatcher/compiler-dispatcher.cc @@ -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 function) { if (!IsEnabled()) return false; + { + base::LockGuard 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 function) { bool result = job->second->status() != CompileJobStatus::kFailed; job->second->ResetOnMainThread(); jobs_.erase(job); + if (jobs_.empty()) { + base::LockGuard 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 lock(&mutex_); + DCHECK(pending_background_jobs_.empty()); + DCHECK(running_background_jobs_.empty()); + abort_ = false; + } + return; + } + + { + base::LockGuard 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 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 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 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(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 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 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 diff --git a/src/compiler-dispatcher/compiler-dispatcher.h b/src/compiler-dispatcher/compiler-dispatcher.h index 99c15db925..f78c12f32a 100644 --- a/src/compiler-dispatcher/compiler-dispatcher.h +++ b/src/compiler-dispatcher/compiler-dispatcher.h @@ -10,9 +10,11 @@ #include #include +#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::unique_ptr> JobMap; + class AbortTask; class BackgroundTask; class IdleTask; void WaitForJobIfRunningOnBackground(CompilerDispatcherJob* job); bool IsEnabled() const; + void AbortInactiveJobs(); JobMap::const_iterator GetJobFor(Handle 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 block_for_testing_; + base::Semaphore semaphore_for_testing_; + DISALLOW_COPY_AND_ASSIGN(CompilerDispatcher); }; diff --git a/test/unittests/cancelable-tasks-unittest.cc b/test/unittests/cancelable-tasks-unittest.cc index 37690aaf80..eb5dd91589 100644 --- a/test/unittests/cancelable-tasks-unittest.cc +++ b/test/unittests/cancelable-tasks-unittest.cc @@ -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 diff --git a/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc b/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc index bef225efaf..d9d09b4fb6 100644 --- a/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc +++ b/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc @@ -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 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 lock(&mutex_); + background_tasks_.push_back(task); } void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override { - UNREACHABLE(); + base::LockGuard 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 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 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 lock(&mutex_); + return idle_task_; + } - bool BackgroundTasksPending() const { return !tasks_.empty(); } + bool BackgroundTasksPending() { + base::LockGuard lock(&mutex_); + return !background_tasks_.empty(); + } + + bool ForegroundTasksPending() { + base::LockGuard lock(&mutex_); + return !foreground_tasks_.empty(); + } void RunBackgroundTasksAndBlock(Platform* platform) { std::vector tasks; - tasks.swap(tasks_); + { + base::LockGuard 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 tasks; - tasks.swap(tasks_); + { + base::LockGuard lock(&mutex_); + tasks.swap(background_tasks_); + } platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, false), kShortRunningTask); } + void RunForegroundTasks() { + std::vector tasks; + { + base::LockGuard lock(&mutex_); + tasks.swap(foreground_tasks_); + } + for (auto& task : tasks) { + task->Run(); + delete task; + } + } + void ClearBackgroundTasks() { std::vector tasks; - tasks.swap(tasks_); + { + base::LockGuard lock(&mutex_); + tasks.swap(background_tasks_); + } + for (auto& task : tasks) { + delete task; + } + } + + void ClearForegroundTasks() { + std::vector tasks; + { + base::LockGuard lock(&mutex_); + tasks.swap(foreground_tasks_); + } for (auto& task : tasks) { delete task; } } void ClearIdleTask() { + base::LockGuard 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 tasks_; + // Protects all *_tasks_. + base::Mutex mutex_; + + IdleTask* idle_task_; + std::vector background_tasks_; + std::vector 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 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()); + + // 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 f1 = Handle::cast(RunJS(isolate(), script1)); + Handle shared1(f1->shared(), i_isolate()); + + const char script2[] = + "function g() { var y = 1; function f12(x) { return x * y }; return f12; " + "} g();"; + Handle f2 = Handle::cast(RunJS(isolate(), script2)); + Handle 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 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 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 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()); + + // 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 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 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