普通文本  |  715行  |  22.89 KB

// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/compiler-dispatcher/compiler-dispatcher.h"

#include "include/v8-platform.h"
#include "include/v8.h"
#include "src/base/platform/time.h"
#include "src/cancelable-task.h"
#include "src/compilation-info.h"
#include "src/compiler-dispatcher/compiler-dispatcher-job.h"
#include "src/compiler-dispatcher/compiler-dispatcher-tracer.h"
#include "src/flags.h"
#include "src/objects-inl.h"

namespace v8 {
namespace internal {

namespace {

enum class ExceptionHandling { kSwallow, kThrow };

bool DoNextStepOnMainThread(Isolate* isolate, CompilerDispatcherJob* job,
                            ExceptionHandling exception_handling) {
  DCHECK(ThreadId::Current().Equals(isolate->thread_id()));
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.CompilerDispatcherForgroundStep");

  // Ensure we are in the correct context for the job.
  SaveContext save(isolate);
  isolate->set_context(job->context());

  switch (job->status()) {
    case CompileJobStatus::kInitial:
      job->PrepareToParseOnMainThread();
      break;

    case CompileJobStatus::kReadyToParse:
      job->Parse();
      break;

    case CompileJobStatus::kParsed:
      job->FinalizeParsingOnMainThread();
      break;

    case CompileJobStatus::kReadyToAnalyze:
      job->AnalyzeOnMainThread();
      break;

    case CompileJobStatus::kAnalyzed:
      job->PrepareToCompileOnMainThread();
      break;

    case CompileJobStatus::kReadyToCompile:
      job->Compile();
      break;

    case CompileJobStatus::kCompiled:
      job->FinalizeCompilingOnMainThread();
      break;

    case CompileJobStatus::kFailed:
    case CompileJobStatus::kDone:
      break;
  }

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

bool IsFinished(CompilerDispatcherJob* job) {
  return job->status() == CompileJobStatus::kDone ||
         job->status() == CompileJobStatus::kFailed;
}

bool CanRunOnAnyThread(CompilerDispatcherJob* job) {
  return job->status() == CompileJobStatus::kReadyToParse ||
         job->status() == CompileJobStatus::kReadyToCompile;
}

void DoNextStepOnBackgroundThread(CompilerDispatcherJob* job) {
  DCHECK(CanRunOnAnyThread(job));
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.CompilerDispatcherBackgroundStep");

  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;

class MemoryPressureTask : public CancelableTask {
 public:
  MemoryPressureTask(Isolate* isolate, CancelableTaskManager* task_manager,
                     CompilerDispatcher* dispatcher);
  ~MemoryPressureTask() override;

  // CancelableTask implementation.
  void RunInternal() override;

 private:
  CompilerDispatcher* dispatcher_;

  DISALLOW_COPY_AND_ASSIGN(MemoryPressureTask);
};

MemoryPressureTask::MemoryPressureTask(Isolate* isolate,
                                       CancelableTaskManager* task_manager,
                                       CompilerDispatcher* dispatcher)
    : CancelableTask(isolate, task_manager), dispatcher_(dispatcher) {}

MemoryPressureTask::~MemoryPressureTask() {}

void MemoryPressureTask::RunInternal() {
  dispatcher_->AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock);
}

}  // 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,
                 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, CancelableTaskManager* task_manager,
           CompilerDispatcher* dispatcher);
  ~IdleTask() override;

  // CancelableIdleTask implementation.
  void RunInternal(double deadline_in_seconds) override;

 private:
  CompilerDispatcher* dispatcher_;

  DISALLOW_COPY_AND_ASSIGN(IdleTask);
};

CompilerDispatcher::IdleTask::IdleTask(Isolate* isolate,
                                       CancelableTaskManager* task_manager,
                                       CompilerDispatcher* dispatcher)
    : CancelableIdleTask(isolate, task_manager), dispatcher_(dispatcher) {}

CompilerDispatcher::IdleTask::~IdleTask() {}

void CompilerDispatcher::IdleTask::RunInternal(double deadline_in_seconds) {
  dispatcher_->DoIdleWork(deadline_in_seconds);
}

CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform,
                                       size_t max_stack_size)
    : isolate_(isolate),
      platform_(platform),
      max_stack_size_(max_stack_size),
      trace_compiler_dispatcher_(FLAG_trace_compiler_dispatcher),
      tracer_(new CompilerDispatcherTracer(isolate_)),
      task_manager_(new CancelableTaskManager()),
      memory_pressure_level_(MemoryPressureLevel::kNone),
      abort_(false),
      idle_task_scheduled_(false),
      num_scheduled_background_tasks_(0),
      main_thread_blocking_on_job_(nullptr),
      block_for_testing_(false),
      semaphore_for_testing_(0) {
  if (trace_compiler_dispatcher_ && !IsEnabled()) {
    PrintF("CompilerDispatcher: dispatcher is disabled\n");
  }
}

CompilerDispatcher::~CompilerDispatcher() {
  // To avoid crashing in unit tests due to unfished jobs.
  AbortAll(BlockingBehavior::kBlock);
  task_manager_->CancelAndWait();
}

bool CompilerDispatcher::CanEnqueue(Handle<SharedFunctionInfo> function) {
  if (!IsEnabled()) return false;

  DCHECK(FLAG_ignition);

  if (memory_pressure_level_.Value() != MemoryPressureLevel::kNone) {
    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_toplevel() ||
      function->asm_function() || function->native()) {
    return false;
  }

  return true;
}

bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.CompilerDispatcherEnqueue");
  if (!CanEnqueue(function)) return false;
  if (IsEnqueued(function)) return true;

  if (trace_compiler_dispatcher_) {
    PrintF("CompilerDispatcher: enqueuing ");
    function->ShortPrint();
    PrintF(" for parse and compile\n");
  }

  std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob(
      isolate_, tracer_.get(), function, max_stack_size_));
  std::pair<int, int> key(Script::cast(function->script())->id(),
                          function->function_literal_id());
  jobs_.insert(std::make_pair(key, std::move(job)));
  ScheduleIdleTaskIfNeeded();
  return true;
}

bool CompilerDispatcher::EnqueueAndStep(Handle<SharedFunctionInfo> function) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.CompilerDispatcherEnqueueAndStep");
  if (IsEnqueued(function)) return true;
  if (!Enqueue(function)) return false;

  if (trace_compiler_dispatcher_) {
    PrintF("CompilerDispatcher: stepping ");
    function->ShortPrint();
    PrintF("\n");
  }
  JobMap::const_iterator job = GetJobFor(function);
  DoNextStepOnMainThread(isolate_, job->second.get(),
                         ExceptionHandling::kSwallow);
  ConsiderJobForBackgroundProcessing(job->second.get());
  return true;
}

bool CompilerDispatcher::Enqueue(
    Handle<Script> script, Handle<SharedFunctionInfo> function,
    FunctionLiteral* literal, std::shared_ptr<Zone> parse_zone,
    std::shared_ptr<DeferredHandles> parse_handles,
    std::shared_ptr<DeferredHandles> compile_handles) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.CompilerDispatcherEnqueue");
  if (!CanEnqueue(function)) return false;
  if (IsEnqueued(function)) return true;

  if (trace_compiler_dispatcher_) {
    PrintF("CompilerDispatcher: enqueuing ");
    function->ShortPrint();
    PrintF(" for compile\n");
  }

  std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob(
      isolate_, tracer_.get(), script, function, literal, parse_zone,
      parse_handles, compile_handles, max_stack_size_));
  std::pair<int, int> key(Script::cast(function->script())->id(),
                          function->function_literal_id());
  jobs_.insert(std::make_pair(key, std::move(job)));
  ScheduleIdleTaskIfNeeded();
  return true;
}

bool CompilerDispatcher::EnqueueAndStep(
    Handle<Script> script, Handle<SharedFunctionInfo> function,
    FunctionLiteral* literal, std::shared_ptr<Zone> parse_zone,
    std::shared_ptr<DeferredHandles> parse_handles,
    std::shared_ptr<DeferredHandles> compile_handles) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.CompilerDispatcherEnqueueAndStep");
  if (IsEnqueued(function)) return true;
  if (!Enqueue(script, function, literal, parse_zone, parse_handles,
               compile_handles)) {
    return false;
  }

  if (trace_compiler_dispatcher_) {
    PrintF("CompilerDispatcher: stepping ");
    function->ShortPrint();
    PrintF("\n");
  }
  JobMap::const_iterator job = GetJobFor(function);
  DoNextStepOnMainThread(isolate_, job->second.get(),
                         ExceptionHandling::kSwallow);
  ConsiderJobForBackgroundProcessing(job->second.get());
  return true;
}

bool CompilerDispatcher::IsEnabled() const { return FLAG_compiler_dispatcher; }

bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const {
  if (jobs_.empty()) return false;
  return GetJobFor(function) != jobs_.end();
}

void CompilerDispatcher::WaitForJobIfRunningOnBackground(
    CompilerDispatcherJob* job) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.CompilerDispatcherWaitForBackgroundJob");
  RuntimeCallTimerScope runtimeTimer(
      isolate_, &RuntimeCallStats::CompileWaitForDispatcher);

  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) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.CompilerDispatcherFinishNow");
  JobMap::const_iterator job = GetJobFor(function);
  CHECK(job != jobs_.end());

  if (trace_compiler_dispatcher_) {
    PrintF("CompilerDispatcher: finishing ");
    function->ShortPrint();
    PrintF(" now\n");
  }

  WaitForJobIfRunningOnBackground(job->second.get());
  while (!IsFinished(job->second.get())) {
    DoNextStepOnMainThread(isolate_, job->second.get(),
                           ExceptionHandling::kThrow);
  }
  bool result = job->second->status() != CompileJobStatus::kFailed;

  if (trace_compiler_dispatcher_) {
    PrintF("CompilerDispatcher: finished working on ");
    function->ShortPrint();
    PrintF(": %s\n", result ? "success" : "failure");
    tracer_->DumpStatistics();
  }

  job->second->ResetOnMainThread();
  jobs_.erase(job);
  if (jobs_.empty()) {
    base::LockGuard<base::Mutex> lock(&mutex_);
    abort_ = false;
  }
  return result;
}

void CompilerDispatcher::AbortAll(BlockingBehavior blocking) {
  bool background_tasks_running =
      task_manager_->TryAbortAll() == CancelableTaskManager::kTaskRunning;
  if (!background_tasks_running || blocking == BlockingBehavior::kBlock) {
    for (auto& it : jobs_) {
      WaitForJobIfRunningOnBackground(it.second.get());
      if (trace_compiler_dispatcher_) {
        PrintF("CompilerDispatcher: aborted ");
        it.second->ShortPrint();
        PrintF("\n");
      }
      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;
      }
    }
    if (trace_compiler_dispatcher_) {
      PrintF("CompilerDispatcher: aborted ");
      job->second->ShortPrint();
      PrintF("\n");
    }
    job->second->ResetOnMainThread();
    jobs_.erase(job);
  }
  if (jobs_.empty()) {
    base::LockGuard<base::Mutex> lock(&mutex_);
    abort_ = false;
  }
}

void CompilerDispatcher::MemoryPressureNotification(
    v8::MemoryPressureLevel level, bool is_isolate_locked) {
  MemoryPressureLevel previous = memory_pressure_level_.Value();
  memory_pressure_level_.SetValue(level);
  // If we're already under pressure, we haven't accepted new tasks meanwhile
  // and can just return. If we're no longer under pressure, we're also done.
  if (previous != MemoryPressureLevel::kNone ||
      level == MemoryPressureLevel::kNone) {
    return;
  }
  if (trace_compiler_dispatcher_) {
    PrintF("CompilerDispatcher: received memory pressure notification\n");
  }
  if (is_isolate_locked) {
    AbortAll(BlockingBehavior::kDontBlock);
  } else {
    {
      base::LockGuard<base::Mutex> lock(&mutex_);
      if (abort_) return;
      // By going into abort mode here, and clearing the
      // pending_background_jobs_, we at keep existing background jobs from
      // picking up more work before the MemoryPressureTask gets executed.
      abort_ = true;
      pending_background_jobs_.clear();
    }
    platform_->CallOnForegroundThread(
        reinterpret_cast<v8::Isolate*>(isolate_),
        new MemoryPressureTask(isolate_, task_manager_.get(), this));
  }
}

CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor(
    Handle<SharedFunctionInfo> shared) const {
  if (!shared->script()->IsScript()) return jobs_.end();
  std::pair<int, int> key(Script::cast(shared->script())->id(),
                          shared->function_literal_id());
  auto range = jobs_.equal_range(key);
  for (auto job = range.first; job != range.second; ++job) {
    if (job->second->IsAssociatedWith(shared)) return job;
  }
  return jobs_.end();
}

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;
    idle_task_scheduled_ = true;
  }
  platform_->CallIdleOnForegroundThread(
      v8_isolate, new IdleTask(isolate_, task_manager_.get(), this));
}

void CompilerDispatcher::ScheduleIdleTaskIfNeeded() {
  if (jobs_.empty()) return;
  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;
  {
    base::LockGuard<base::Mutex> lock(&mutex_);
    pending_background_jobs_.insert(job);
  }
  ScheduleMoreBackgroundTasksIfNeeded();
}

void CompilerDispatcher::ScheduleMoreBackgroundTasksIfNeeded() {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.CompilerDispatcherScheduleMoreBackgroundTasksIfNeeded");
  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;

  if (V8_UNLIKELY(block_for_testing_.Value())) {
    block_for_testing_.SetValue(false);
    semaphore_for_testing_.Wait();
  }

  if (trace_compiler_dispatcher_) {
    PrintF("CompilerDispatcher: doing background work\n");
  }

  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 (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();
    }
  }
  // Don't touch |this| anymore after this point, as it might have been
  // deleted.
}

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
  // due to their estimated duration.
  size_t too_long_jobs = 0;

  // Iterate over all available jobs & remaining time. For each job, decide
  // whether to 1) skip it (if it would take too long), 2) erase it (if it's
  // finished), or 3) make progress on it.
  double idle_time_in_seconds =
      deadline_in_seconds - platform_->MonotonicallyIncreasingTime();

  if (trace_compiler_dispatcher_) {
    PrintF("CompilerDispatcher: received %0.1lfms of idle time\n",
           idle_time_in_seconds *
               static_cast<double>(base::Time::kMillisecondsPerSecond));
  }
  for (auto job = jobs_.begin();
       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 /
         static_cast<double>(base::Time::kMillisecondsPerSecond))) {
      // If there's not enough time left, try to estimate whether we would
      // 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());
      if (trace_compiler_dispatcher_) {
        PrintF("CompilerDispatcher: finished working on ");
        job->second->ShortPrint();
        PrintF(": %s\n", job->second->status() == CompileJobStatus::kDone
                             ? "success"
                             : "failure");
        tracer_->DumpStatistics();
      }
      job->second->ResetOnMainThread();
      job = jobs_.erase(job);
      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);
    }
  }
  if (jobs_.size() > too_long_jobs) ScheduleIdleTaskIfNeeded();
}

}  // namespace internal
}  // namespace v8