• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/compiler-dispatcher/lazy-compile-dispatcher.h"
6 
7 #include <atomic>
8 
9 #include "include/v8-platform.h"
10 #include "src/ast/ast.h"
11 #include "src/base/platform/mutex.h"
12 #include "src/base/platform/time.h"
13 #include "src/codegen/compiler.h"
14 #include "src/common/globals.h"
15 #include "src/execution/isolate.h"
16 #include "src/flags/flags.h"
17 #include "src/handles/global-handles-inl.h"
18 #include "src/heap/parked-scope.h"
19 #include "src/logging/counters.h"
20 #include "src/logging/runtime-call-stats-scope.h"
21 #include "src/numbers/hash-seed-inl.h"
22 #include "src/objects/instance-type.h"
23 #include "src/objects/objects-inl.h"
24 #include "src/parsing/parse-info.h"
25 #include "src/parsing/parser.h"
26 #include "src/roots/roots.h"
27 #include "src/sandbox/external-pointer.h"
28 #include "src/tasks/cancelable-task.h"
29 #include "src/tasks/task-utils.h"
30 #include "src/zone/zone-list-inl.h"  // crbug.com/v8/8816
31 
32 namespace v8 {
33 namespace internal {
34 
35 // The maximum amount of time we should allow a single function's FinishNow to
36 // spend opportunistically finalizing other finalizable jobs.
37 static constexpr int kMaxOpportunisticFinalizeTimeMs = 1;
38 
39 class LazyCompileDispatcher::JobTask : public v8::JobTask {
40  public:
JobTask(LazyCompileDispatcher * lazy_compile_dispatcher)41   explicit JobTask(LazyCompileDispatcher* lazy_compile_dispatcher)
42       : lazy_compile_dispatcher_(lazy_compile_dispatcher) {}
43 
Run(JobDelegate * delegate)44   void Run(JobDelegate* delegate) final {
45     lazy_compile_dispatcher_->DoBackgroundWork(delegate);
46   }
47 
GetMaxConcurrency(size_t worker_count) const48   size_t GetMaxConcurrency(size_t worker_count) const final {
49     size_t n = lazy_compile_dispatcher_->num_jobs_for_background_.load(
50         std::memory_order_relaxed);
51     if (FLAG_lazy_compile_dispatcher_max_threads == 0) return n;
52     return std::min(
53         n, static_cast<size_t>(FLAG_lazy_compile_dispatcher_max_threads));
54   }
55 
56  private:
57   LazyCompileDispatcher* lazy_compile_dispatcher_;
58 };
59 
Job(std::unique_ptr<BackgroundCompileTask> task)60 LazyCompileDispatcher::Job::Job(std::unique_ptr<BackgroundCompileTask> task)
61     : task(std::move(task)), state(Job::State::kPending) {}
62 
63 LazyCompileDispatcher::Job::~Job() = default;
64 
LazyCompileDispatcher(Isolate * isolate,Platform * platform,size_t max_stack_size)65 LazyCompileDispatcher::LazyCompileDispatcher(Isolate* isolate,
66                                              Platform* platform,
67                                              size_t max_stack_size)
68     : isolate_(isolate),
69       worker_thread_runtime_call_stats_(
70           isolate->counters()->worker_thread_runtime_call_stats()),
71       background_compile_timer_(
72           isolate->counters()->compile_function_on_background()),
73       taskrunner_(platform->GetForegroundTaskRunner(
74           reinterpret_cast<v8::Isolate*>(isolate))),
75       platform_(platform),
76       max_stack_size_(max_stack_size),
77       trace_compiler_dispatcher_(FLAG_trace_compiler_dispatcher),
78       idle_task_manager_(new CancelableTaskManager()),
79       idle_task_scheduled_(false),
80       num_jobs_for_background_(0),
81       main_thread_blocking_on_job_(nullptr),
82       block_for_testing_(false),
83       semaphore_for_testing_(0) {
84   job_handle_ = platform_->PostJob(TaskPriority::kUserVisible,
85                                    std::make_unique<JobTask>(this));
86 }
87 
~LazyCompileDispatcher()88 LazyCompileDispatcher::~LazyCompileDispatcher() {
89   // AbortAll must be called before LazyCompileDispatcher is destroyed.
90   CHECK(!job_handle_->IsValid());
91 }
92 
93 namespace {
94 
95 // If the SharedFunctionInfo's UncompiledData has a job slot, then write into
96 // it. Otherwise, allocate a new UncompiledData with a job slot, and then write
97 // into that. Since we have two optional slots (preparse data and job), this
98 // gets a little messy.
SetUncompiledDataJobPointer(LocalIsolate * isolate,Handle<SharedFunctionInfo> shared_info,Address job_address)99 void SetUncompiledDataJobPointer(LocalIsolate* isolate,
100                                  Handle<SharedFunctionInfo> shared_info,
101                                  Address job_address) {
102   UncompiledData uncompiled_data = shared_info->uncompiled_data();
103   switch (uncompiled_data.map(isolate).instance_type()) {
104     // The easy cases -- we already have a job slot, so can write into it and
105     // return.
106     case UNCOMPILED_DATA_WITH_PREPARSE_DATA_AND_JOB_TYPE:
107       UncompiledDataWithPreparseDataAndJob::cast(uncompiled_data)
108           .set_job(job_address);
109       break;
110     case UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_WITH_JOB_TYPE:
111       UncompiledDataWithoutPreparseDataWithJob::cast(uncompiled_data)
112           .set_job(job_address);
113       break;
114 
115     // Otherwise, we'll have to allocate a new UncompiledData (with or without
116     // preparse data as appropriate), set the job pointer on that, and update
117     // the SharedFunctionInfo to use the new UncompiledData
118     case UNCOMPILED_DATA_WITH_PREPARSE_DATA_TYPE: {
119       Handle<String> inferred_name(uncompiled_data.inferred_name(), isolate);
120       Handle<PreparseData> preparse_data(
121           UncompiledDataWithPreparseData::cast(uncompiled_data).preparse_data(),
122           isolate);
123       Handle<UncompiledDataWithPreparseDataAndJob> new_uncompiled_data =
124           isolate->factory()->NewUncompiledDataWithPreparseDataAndJob(
125               inferred_name, uncompiled_data.start_position(),
126               uncompiled_data.end_position(), preparse_data);
127 
128       new_uncompiled_data->set_job(job_address);
129       shared_info->set_uncompiled_data(*new_uncompiled_data);
130       break;
131     }
132     case UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_TYPE: {
133       DCHECK(uncompiled_data.IsUncompiledDataWithoutPreparseData());
134       Handle<String> inferred_name(uncompiled_data.inferred_name(), isolate);
135       Handle<UncompiledDataWithoutPreparseDataWithJob> new_uncompiled_data =
136           isolate->factory()->NewUncompiledDataWithoutPreparseDataWithJob(
137               inferred_name, uncompiled_data.start_position(),
138               uncompiled_data.end_position());
139 
140       new_uncompiled_data->set_job(job_address);
141       shared_info->set_uncompiled_data(*new_uncompiled_data);
142       break;
143     }
144 
145     default:
146       UNREACHABLE();
147   }
148 }
149 
150 }  // namespace
151 
Enqueue(LocalIsolate * isolate,Handle<SharedFunctionInfo> shared_info,std::unique_ptr<Utf16CharacterStream> character_stream)152 void LazyCompileDispatcher::Enqueue(
153     LocalIsolate* isolate, Handle<SharedFunctionInfo> shared_info,
154     std::unique_ptr<Utf16CharacterStream> character_stream) {
155   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
156                "V8.LazyCompilerDispatcherEnqueue");
157   RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileEnqueueOnDispatcher);
158 
159   Job* job = new Job(std::make_unique<BackgroundCompileTask>(
160       isolate_, shared_info, std::move(character_stream),
161       worker_thread_runtime_call_stats_, background_compile_timer_,
162       static_cast<int>(max_stack_size_)));
163 
164   SetUncompiledDataJobPointer(isolate, shared_info,
165                               reinterpret_cast<Address>(job));
166 
167   // Post a a background worker task to perform the compilation on the worker
168   // thread.
169   {
170     base::MutexGuard lock(&mutex_);
171     if (trace_compiler_dispatcher_) {
172       PrintF("LazyCompileDispatcher: enqueued job for ");
173       shared_info->ShortPrint();
174       PrintF("\n");
175     }
176 
177 #ifdef DEBUG
178     all_jobs_.insert(job);
179 #endif
180     pending_background_jobs_.push_back(job);
181     NotifyAddedBackgroundJob(lock);
182   }
183   // This is not in NotifyAddedBackgroundJob to avoid being inside the mutex.
184   job_handle_->NotifyConcurrencyIncrease();
185 }
186 
IsEnqueued(Handle<SharedFunctionInfo> function) const187 bool LazyCompileDispatcher::IsEnqueued(
188     Handle<SharedFunctionInfo> function) const {
189   Job* job = nullptr;
190   Object function_data = function->function_data(kAcquireLoad);
191   if (function_data.IsUncompiledDataWithPreparseDataAndJob()) {
192     job = reinterpret_cast<Job*>(
193         UncompiledDataWithPreparseDataAndJob::cast(function_data).job());
194   } else if (function_data.IsUncompiledDataWithoutPreparseDataWithJob()) {
195     job = reinterpret_cast<Job*>(
196         UncompiledDataWithoutPreparseDataWithJob::cast(function_data).job());
197   }
198   return job != nullptr;
199 }
200 
WaitForJobIfRunningOnBackground(Job * job,const base::MutexGuard & lock)201 void LazyCompileDispatcher::WaitForJobIfRunningOnBackground(
202     Job* job, const base::MutexGuard& lock) {
203   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
204                "V8.LazyCompilerDispatcherWaitForBackgroundJob");
205   RCS_SCOPE(isolate_, RuntimeCallCounterId::kCompileWaitForDispatcher);
206 
207   if (!job->is_running_on_background()) {
208     if (job->state == Job::State::kPending) {
209       DCHECK_EQ(std::count(pending_background_jobs_.begin(),
210                            pending_background_jobs_.end(), job),
211                 1);
212 
213       // TODO(leszeks): Remove from pending jobs without walking the whole
214       // vector.
215       pending_background_jobs_.erase(
216           std::remove(pending_background_jobs_.begin(),
217                       pending_background_jobs_.end(), job));
218       job->state = Job::State::kPendingToRunOnForeground;
219       NotifyRemovedBackgroundJob(lock);
220     } else {
221       DCHECK_EQ(job->state, Job::State::kReadyToFinalize);
222       DCHECK_EQ(
223           std::count(finalizable_jobs_.begin(), finalizable_jobs_.end(), job),
224           1);
225 
226       // TODO(leszeks): Remove from finalizable jobs without walking the whole
227       // vector.
228       finalizable_jobs_.erase(
229           std::remove(finalizable_jobs_.begin(), finalizable_jobs_.end(), job));
230       job->state = Job::State::kFinalizingNow;
231     }
232     return;
233   }
234   DCHECK_NULL(main_thread_blocking_on_job_);
235   main_thread_blocking_on_job_ = job;
236   while (main_thread_blocking_on_job_ != nullptr) {
237     main_thread_blocking_signal_.Wait(&mutex_);
238   }
239 
240   DCHECK_EQ(job->state, Job::State::kReadyToFinalize);
241   DCHECK_EQ(std::count(finalizable_jobs_.begin(), finalizable_jobs_.end(), job),
242             1);
243 
244   // TODO(leszeks): Remove from finalizable jobs without walking the whole
245   // vector.
246   finalizable_jobs_.erase(
247       std::remove(finalizable_jobs_.begin(), finalizable_jobs_.end(), job));
248   job->state = Job::State::kFinalizingNow;
249 }
250 
FinishNow(Handle<SharedFunctionInfo> function)251 bool LazyCompileDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
252   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
253                "V8.LazyCompilerDispatcherFinishNow");
254   RCS_SCOPE(isolate_, RuntimeCallCounterId::kCompileFinishNowOnDispatcher);
255   if (trace_compiler_dispatcher_) {
256     PrintF("LazyCompileDispatcher: finishing ");
257     function->ShortPrint();
258     PrintF(" now\n");
259   }
260 
261   Job* job;
262 
263   {
264     base::MutexGuard lock(&mutex_);
265     job = GetJobFor(function, lock);
266     WaitForJobIfRunningOnBackground(job, lock);
267   }
268 
269   if (job->state == Job::State::kPendingToRunOnForeground) {
270     job->task->RunOnMainThread(isolate_);
271     job->state = Job::State::kFinalizingNow;
272   }
273 
274   if (DEBUG_BOOL) {
275     base::MutexGuard lock(&mutex_);
276     DCHECK_EQ(std::count(pending_background_jobs_.begin(),
277                          pending_background_jobs_.end(), job),
278               0);
279     DCHECK_EQ(
280         std::count(finalizable_jobs_.begin(), finalizable_jobs_.end(), job), 0);
281     DCHECK_EQ(job->state, Job::State::kFinalizingNow);
282   }
283 
284   bool success = Compiler::FinalizeBackgroundCompileTask(
285       job->task.get(), isolate_, Compiler::KEEP_EXCEPTION);
286   job->state = Job::State::kFinalized;
287 
288   DCHECK_NE(success, isolate_->has_pending_exception());
289   DeleteJob(job);
290 
291   // Opportunistically finalize all other jobs for a maximum time of
292   // kMaxOpportunisticFinalizeTimeMs.
293   double deadline_in_seconds = platform_->MonotonicallyIncreasingTime() +
294                                kMaxOpportunisticFinalizeTimeMs / 1000.0;
295   while (deadline_in_seconds > platform_->MonotonicallyIncreasingTime()) {
296     if (!FinalizeSingleJob()) break;
297   }
298 
299   return success;
300 }
301 
AbortJob(Handle<SharedFunctionInfo> shared_info)302 void LazyCompileDispatcher::AbortJob(Handle<SharedFunctionInfo> shared_info) {
303   if (trace_compiler_dispatcher_) {
304     PrintF("LazyCompileDispatcher: aborting job for ");
305     shared_info->ShortPrint();
306     PrintF("\n");
307   }
308   base::LockGuard<base::Mutex> lock(&mutex_);
309 
310   Job* job = GetJobFor(shared_info, lock);
311   if (job->is_running_on_background()) {
312     // Job is currently running on the background thread, wait until it's done
313     // and remove job then.
314     job->state = Job::State::kAbortRequested;
315   } else {
316     if (job->state == Job::State::kPending) {
317       DCHECK_EQ(std::count(pending_background_jobs_.begin(),
318                            pending_background_jobs_.end(), job),
319                 1);
320 
321       pending_background_jobs_.erase(
322           std::remove(pending_background_jobs_.begin(),
323                       pending_background_jobs_.end(), job));
324       job->state = Job::State::kAbortingNow;
325       NotifyRemovedBackgroundJob(lock);
326     } else if (job->state == Job::State::kReadyToFinalize) {
327       DCHECK_EQ(
328           std::count(finalizable_jobs_.begin(), finalizable_jobs_.end(), job),
329           1);
330 
331       finalizable_jobs_.erase(
332           std::remove(finalizable_jobs_.begin(), finalizable_jobs_.end(), job));
333       job->state = Job::State::kAbortingNow;
334     } else {
335       UNREACHABLE();
336     }
337     job->task->AbortFunction();
338     job->state = Job::State::kFinalized;
339     DeleteJob(job, lock);
340   }
341 }
342 
AbortAll()343 void LazyCompileDispatcher::AbortAll() {
344   idle_task_manager_->TryAbortAll();
345   job_handle_->Cancel();
346 
347   {
348     base::MutexGuard lock(&mutex_);
349     for (Job* job : pending_background_jobs_) {
350       job->task->AbortFunction();
351       job->state = Job::State::kFinalized;
352       DeleteJob(job, lock);
353     }
354     pending_background_jobs_.clear();
355     for (Job* job : finalizable_jobs_) {
356       job->task->AbortFunction();
357       job->state = Job::State::kFinalized;
358       DeleteJob(job, lock);
359     }
360     finalizable_jobs_.clear();
361     for (Job* job : jobs_to_dispose_) {
362       delete job;
363     }
364     jobs_to_dispose_.clear();
365 
366     DCHECK_EQ(all_jobs_.size(), 0);
367     num_jobs_for_background_ = 0;
368     VerifyBackgroundTaskCount(lock);
369   }
370 
371   idle_task_manager_->CancelAndWait();
372 }
373 
GetJobFor(Handle<SharedFunctionInfo> shared,const base::MutexGuard &) const374 LazyCompileDispatcher::Job* LazyCompileDispatcher::GetJobFor(
375     Handle<SharedFunctionInfo> shared, const base::MutexGuard&) const {
376   Object function_data = shared->function_data(kAcquireLoad);
377   if (function_data.IsUncompiledDataWithPreparseDataAndJob()) {
378     return reinterpret_cast<Job*>(
379         UncompiledDataWithPreparseDataAndJob::cast(function_data).job());
380   } else if (function_data.IsUncompiledDataWithoutPreparseDataWithJob()) {
381     return reinterpret_cast<Job*>(
382         UncompiledDataWithoutPreparseDataWithJob::cast(function_data).job());
383   }
384   return nullptr;
385 }
386 
ScheduleIdleTaskFromAnyThread(const base::MutexGuard &)387 void LazyCompileDispatcher::ScheduleIdleTaskFromAnyThread(
388     const base::MutexGuard&) {
389   if (!taskrunner_->IdleTasksEnabled()) return;
390   if (idle_task_scheduled_) return;
391 
392   idle_task_scheduled_ = true;
393   // TODO(leszeks): Using a full task manager for a single cancellable task is
394   // overkill, we could probably do the cancelling ourselves.
395   taskrunner_->PostIdleTask(MakeCancelableIdleTask(
396       idle_task_manager_.get(),
397       [this](double deadline_in_seconds) { DoIdleWork(deadline_in_seconds); }));
398 }
399 
DoBackgroundWork(JobDelegate * delegate)400 void LazyCompileDispatcher::DoBackgroundWork(JobDelegate* delegate) {
401   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
402                "V8.LazyCompileDispatcherDoBackgroundWork");
403 
404   LocalIsolate isolate(isolate_, ThreadKind::kBackground);
405   UnparkedScope unparked_scope(&isolate);
406   LocalHandleScope handle_scope(&isolate);
407 
408   ReusableUnoptimizedCompileState reusable_state(&isolate);
409 
410   while (!delegate->ShouldYield()) {
411     Job* job = nullptr;
412     {
413       base::MutexGuard lock(&mutex_);
414 
415       if (pending_background_jobs_.empty()) break;
416       job = pending_background_jobs_.back();
417       pending_background_jobs_.pop_back();
418       DCHECK_EQ(job->state, Job::State::kPending);
419 
420       job->state = Job::State::kRunning;
421     }
422 
423     if (V8_UNLIKELY(block_for_testing_.Value())) {
424       block_for_testing_.SetValue(false);
425       semaphore_for_testing_.Wait();
426     }
427 
428     if (trace_compiler_dispatcher_) {
429       PrintF("LazyCompileDispatcher: doing background work\n");
430     }
431 
432     job->task->Run(&isolate, &reusable_state);
433 
434     {
435       base::MutexGuard lock(&mutex_);
436       if (job->state == Job::State::kRunning) {
437         job->state = Job::State::kReadyToFinalize;
438         // Schedule an idle task to finalize the compilation on the main thread
439         // if the job has a shared function info registered.
440       } else {
441         DCHECK_EQ(job->state, Job::State::kAbortRequested);
442         job->state = Job::State::kAborted;
443       }
444       finalizable_jobs_.push_back(job);
445       NotifyRemovedBackgroundJob(lock);
446 
447       if (main_thread_blocking_on_job_ == job) {
448         main_thread_blocking_on_job_ = nullptr;
449         main_thread_blocking_signal_.NotifyOne();
450       } else {
451         ScheduleIdleTaskFromAnyThread(lock);
452       }
453     }
454   }
455 
456   while (!delegate->ShouldYield()) {
457     Job* job = nullptr;
458     {
459       base::MutexGuard lock(&mutex_);
460       if (jobs_to_dispose_.empty()) break;
461       job = jobs_to_dispose_.back();
462       jobs_to_dispose_.pop_back();
463       if (jobs_to_dispose_.empty()) {
464         num_jobs_for_background_--;
465       }
466     }
467     delete job;
468   }
469 
470   // Don't touch |this| anymore after this point, as it might have been
471   // deleted.
472 }
473 
PopSingleFinalizeJob()474 LazyCompileDispatcher::Job* LazyCompileDispatcher::PopSingleFinalizeJob() {
475   base::MutexGuard lock(&mutex_);
476 
477   if (finalizable_jobs_.empty()) return nullptr;
478 
479   Job* job = finalizable_jobs_.back();
480   finalizable_jobs_.pop_back();
481   DCHECK(job->state == Job::State::kReadyToFinalize ||
482          job->state == Job::State::kAborted);
483   if (job->state == Job::State::kReadyToFinalize) {
484     job->state = Job::State::kFinalizingNow;
485   } else {
486     DCHECK_EQ(job->state, Job::State::kAborted);
487     job->state = Job::State::kAbortingNow;
488   }
489   return job;
490 }
491 
FinalizeSingleJob()492 bool LazyCompileDispatcher::FinalizeSingleJob() {
493   Job* job = PopSingleFinalizeJob();
494   if (job == nullptr) return false;
495 
496   if (trace_compiler_dispatcher_) {
497     PrintF("LazyCompileDispatcher: idle finalizing job\n");
498   }
499 
500   if (job->state == Job::State::kFinalizingNow) {
501     HandleScope scope(isolate_);
502     Compiler::FinalizeBackgroundCompileTask(job->task.get(), isolate_,
503                                             Compiler::CLEAR_EXCEPTION);
504   } else {
505     DCHECK_EQ(job->state, Job::State::kAbortingNow);
506     job->task->AbortFunction();
507   }
508   job->state = Job::State::kFinalized;
509   DeleteJob(job);
510   return true;
511 }
512 
DoIdleWork(double deadline_in_seconds)513 void LazyCompileDispatcher::DoIdleWork(double deadline_in_seconds) {
514   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
515                "V8.LazyCompilerDispatcherDoIdleWork");
516   {
517     base::MutexGuard lock(&mutex_);
518     idle_task_scheduled_ = false;
519   }
520 
521   if (trace_compiler_dispatcher_) {
522     PrintF("LazyCompileDispatcher: received %0.1lfms of idle time\n",
523            (deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) *
524                static_cast<double>(base::Time::kMillisecondsPerSecond));
525   }
526   while (deadline_in_seconds > platform_->MonotonicallyIncreasingTime()) {
527     // Find a job which is pending finalization and has a shared function info
528     auto there_was_a_job = FinalizeSingleJob();
529     if (!there_was_a_job) return;
530   }
531 
532   // We didn't return above so there still might be jobs to finalize.
533   {
534     base::MutexGuard lock(&mutex_);
535     ScheduleIdleTaskFromAnyThread(lock);
536   }
537 }
538 
DeleteJob(Job * job)539 void LazyCompileDispatcher::DeleteJob(Job* job) {
540   DCHECK(job->state == Job::State::kFinalized);
541   base::MutexGuard lock(&mutex_);
542   DeleteJob(job, lock);
543 }
544 
DeleteJob(Job * job,const base::MutexGuard &)545 void LazyCompileDispatcher::DeleteJob(Job* job, const base::MutexGuard&) {
546   DCHECK(job->state == Job::State::kFinalized);
547 #ifdef DEBUG
548   all_jobs_.erase(job);
549 #endif
550   jobs_to_dispose_.push_back(job);
551   if (jobs_to_dispose_.size() == 1) {
552     num_jobs_for_background_++;
553   }
554 }
555 
556 #ifdef DEBUG
VerifyBackgroundTaskCount(const base::MutexGuard &)557 void LazyCompileDispatcher::VerifyBackgroundTaskCount(const base::MutexGuard&) {
558   size_t pending_jobs = 0;
559   size_t running_jobs = 0;
560   size_t finalizable_jobs = 0;
561 
562   for (Job* job : all_jobs_) {
563     switch (job->state) {
564       case Job::State::kPending:
565         pending_jobs++;
566         break;
567       case Job::State::kRunning:
568       case Job::State::kAbortRequested:
569         running_jobs++;
570         break;
571       case Job::State::kReadyToFinalize:
572       case Job::State::kAborted:
573         finalizable_jobs++;
574         break;
575       case Job::State::kPendingToRunOnForeground:
576       case Job::State::kFinalizingNow:
577       case Job::State::kAbortingNow:
578       case Job::State::kFinalized:
579         // Ignore.
580         break;
581     }
582   }
583 
584   CHECK_EQ(pending_background_jobs_.size(), pending_jobs);
585   CHECK_EQ(finalizable_jobs_.size(), finalizable_jobs);
586   CHECK_EQ(num_jobs_for_background_.load(),
587            pending_jobs + running_jobs + (jobs_to_dispose_.empty() ? 0 : 1));
588 }
589 #endif
590 
591 }  // namespace internal
592 }  // namespace v8
593