| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/task/thread_pool/thread_pool_impl.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| |
| #include "base/base_switches.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/debug/alias.h" |
| #include "base/debug/leak_annotations.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/scoped_set_task_priority_for_current_thread.h" |
| #include "base/task/task_features.h" |
| #include "base/task/thread_pool/pooled_parallel_task_runner.h" |
| #include "base/task/thread_pool/pooled_sequenced_task_runner.h" |
| #include "base/task/thread_pool/task.h" |
| #include "base/task/thread_pool/task_source.h" |
| #include "base/task/thread_pool/task_source_sort_key.h" |
| #include "base/task/thread_pool/thread_group_impl.h" |
| #include "base/task/thread_pool/worker_thread.h" |
| #include "base/thread_annotations.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace base { |
| namespace internal { |
| |
| namespace { |
| |
| constexpr EnvironmentParams kForegroundPoolEnvironmentParams{ |
| "Foreground", base::ThreadType::kDefault}; |
| |
| constexpr EnvironmentParams kUtilityPoolEnvironmentParams{ |
| "Utility", base::ThreadType::kUtility}; |
| |
| constexpr EnvironmentParams kBackgroundPoolEnvironmentParams{ |
| "Background", base::ThreadType::kBackground}; |
| |
| constexpr size_t kMaxBestEffortTasks = 2; |
| |
| // Indicates whether BEST_EFFORT tasks are disabled by a command line switch. |
| bool HasDisableBestEffortTasksSwitch() { |
| // The CommandLine might not be initialized if ThreadPool is initialized in a |
| // dynamic library which doesn't have access to argc/argv. |
| return CommandLine::InitializedForCurrentProcess() && |
| CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableBestEffortTasks); |
| } |
| |
| // A global variable that can be set from test fixtures while no |
| // ThreadPoolInstance is active. Global instead of being a member variable to |
| // avoid having to add a public API to ThreadPoolInstance::InitParams for this |
| // internal edge case. |
| bool g_synchronous_thread_start_for_testing = false; |
| |
| } // namespace |
| |
| ThreadPoolImpl::ThreadPoolImpl(StringPiece histogram_label) |
| : ThreadPoolImpl(histogram_label, std::make_unique<TaskTrackerImpl>()) {} |
| |
| ThreadPoolImpl::ThreadPoolImpl(StringPiece histogram_label, |
| std::unique_ptr<TaskTrackerImpl> task_tracker, |
| bool use_background_threads) |
| : histogram_label_(histogram_label), |
| task_tracker_(std::move(task_tracker)), |
| single_thread_task_runner_manager_(task_tracker_->GetTrackedRef(), |
| &delayed_task_manager_), |
| has_disable_best_effort_switch_(HasDisableBestEffortTasksSwitch()), |
| tracked_ref_factory_(this) { |
| foreground_thread_group_ = std::make_unique<ThreadGroupImpl>( |
| histogram_label.empty() |
| ? std::string() |
| : JoinString( |
| {histogram_label, kForegroundPoolEnvironmentParams.name_suffix}, |
| "."), |
| kForegroundPoolEnvironmentParams.name_suffix, |
| kForegroundPoolEnvironmentParams.thread_type_hint, |
| task_tracker_->GetTrackedRef(), tracked_ref_factory_.GetTrackedRef()); |
| |
| if (CanUseBackgroundThreadTypeForWorkerThread()) { |
| background_thread_group_ = std::make_unique<ThreadGroupImpl>( |
| histogram_label.empty() |
| ? std::string() |
| : JoinString({histogram_label, |
| kBackgroundPoolEnvironmentParams.name_suffix}, |
| "."), |
| kBackgroundPoolEnvironmentParams.name_suffix, |
| use_background_threads |
| ? kBackgroundPoolEnvironmentParams.thread_type_hint |
| : kForegroundPoolEnvironmentParams.thread_type_hint, |
| task_tracker_->GetTrackedRef(), tracked_ref_factory_.GetTrackedRef()); |
| } |
| } |
| |
| ThreadPoolImpl::~ThreadPoolImpl() { |
| #if DCHECK_IS_ON() |
| DCHECK(join_for_testing_returned_.IsSet()); |
| #endif |
| |
| // Reset thread groups to release held TrackedRefs, which block teardown. |
| foreground_thread_group_.reset(); |
| utility_thread_group_.reset(); |
| background_thread_group_.reset(); |
| } |
| |
| void ThreadPoolImpl::Start(const ThreadPoolInstance::InitParams& init_params, |
| WorkerThreadObserver* worker_thread_observer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!started_); |
| |
| // The max number of concurrent BEST_EFFORT tasks is |kMaxBestEffortTasks|, |
| // unless the max number of foreground threads is lower. |
| const size_t max_best_effort_tasks = |
| std::min(kMaxBestEffortTasks, init_params.max_num_foreground_threads); |
| |
| // Start the service thread. On platforms that support it (POSIX except NaCL |
| // SFI), the service thread runs a MessageLoopForIO which is used to support |
| // FileDescriptorWatcher in the scope in which tasks run. |
| ServiceThread::Options service_thread_options; |
| service_thread_options.message_pump_type = |
| #if (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)) || BUILDFLAG(IS_FUCHSIA) |
| MessagePumpType::IO; |
| #else |
| MessagePumpType::DEFAULT; |
| #endif |
| service_thread_options.timer_slack = TIMER_SLACK_MAXIMUM; |
| CHECK(service_thread_.StartWithOptions(std::move(service_thread_options))); |
| if (g_synchronous_thread_start_for_testing) |
| service_thread_.WaitUntilThreadStarted(); |
| |
| if (FeatureList::IsEnabled(kUseUtilityThreadGroup) && |
| CanUseUtilityThreadTypeForWorkerThread()) { |
| utility_thread_group_ = std::make_unique<ThreadGroupImpl>( |
| histogram_label_.empty() |
| ? std::string() |
| : JoinString( |
| {histogram_label_, kUtilityPoolEnvironmentParams.name_suffix}, |
| "."), |
| kUtilityPoolEnvironmentParams.name_suffix, |
| kUtilityPoolEnvironmentParams.thread_type_hint, |
| task_tracker_->GetTrackedRef(), tracked_ref_factory_.GetTrackedRef(), |
| foreground_thread_group_.get()); |
| foreground_thread_group_ |
| ->HandoffNonUserBlockingTaskSourcesToOtherThreadGroup( |
| utility_thread_group_.get()); |
| } |
| |
| // Update the CanRunPolicy based on |has_disable_best_effort_switch_|. |
| UpdateCanRunPolicy(); |
| |
| // Needs to happen after starting the service thread to get its task_runner(). |
| auto service_thread_task_runner = service_thread_.task_runner(); |
| delayed_task_manager_.Start(service_thread_task_runner); |
| |
| single_thread_task_runner_manager_.Start(service_thread_task_runner, |
| worker_thread_observer); |
| |
| ThreadGroup::WorkerEnvironment worker_environment; |
| switch (init_params.common_thread_pool_environment) { |
| case InitParams::CommonThreadPoolEnvironment::DEFAULT: |
| worker_environment = ThreadGroup::WorkerEnvironment::NONE; |
| break; |
| #if BUILDFLAG(IS_WIN) |
| case InitParams::CommonThreadPoolEnvironment::COM_MTA: |
| worker_environment = ThreadGroup::WorkerEnvironment::COM_MTA; |
| break; |
| #endif |
| } |
| |
| // On platforms that can't use the background thread priority, best-effort |
| // tasks run in foreground pools. A cap is set on the number of best-effort |
| // tasks that can run in foreground pools to ensure that there is always |
| // room for incoming foreground tasks and to minimize the performance impact |
| // of best-effort tasks. |
| static_cast<ThreadGroupImpl*>(foreground_thread_group_.get()) |
| ->Start(init_params.max_num_foreground_threads, max_best_effort_tasks, |
| init_params.suggested_reclaim_time, service_thread_task_runner, |
| worker_thread_observer, worker_environment, |
| g_synchronous_thread_start_for_testing); |
| |
| if (utility_thread_group_) { |
| static_cast<ThreadGroupImpl*>(utility_thread_group_.get()) |
| ->Start(init_params.max_num_utility_threads, max_best_effort_tasks, |
| init_params.suggested_reclaim_time, service_thread_task_runner, |
| worker_thread_observer, worker_environment, |
| g_synchronous_thread_start_for_testing); |
| } |
| |
| if (background_thread_group_) { |
| static_cast<ThreadGroupImpl*>(background_thread_group_.get()) |
| ->Start(max_best_effort_tasks, max_best_effort_tasks, |
| init_params.suggested_reclaim_time, service_thread_task_runner, |
| worker_thread_observer, worker_environment, |
| g_synchronous_thread_start_for_testing); |
| } |
| |
| started_ = true; |
| } |
| |
| bool ThreadPoolImpl::WasStarted() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return started_; |
| } |
| |
| bool ThreadPoolImpl::WasStartedUnsafe() const { |
| return TS_UNCHECKED_READ(started_); |
| } |
| |
| bool ThreadPoolImpl::PostDelayedTask(const Location& from_here, |
| const TaskTraits& traits, |
| OnceClosure task, |
| TimeDelta delay) { |
| // Post |task| as part of a one-off single-task Sequence. |
| return PostTaskWithSequence( |
| Task(from_here, std::move(task), TimeTicks::Now(), delay, |
| GetDefaultTaskLeeway()), |
| MakeRefCounted<Sequence>(traits, nullptr, |
| TaskSourceExecutionMode::kParallel)); |
| } |
| |
| scoped_refptr<TaskRunner> ThreadPoolImpl::CreateTaskRunner( |
| const TaskTraits& traits) { |
| return MakeRefCounted<PooledParallelTaskRunner>(traits, this); |
| } |
| |
| scoped_refptr<SequencedTaskRunner> ThreadPoolImpl::CreateSequencedTaskRunner( |
| const TaskTraits& traits) { |
| return MakeRefCounted<PooledSequencedTaskRunner>(traits, this); |
| } |
| |
| scoped_refptr<SingleThreadTaskRunner> |
| ThreadPoolImpl::CreateSingleThreadTaskRunner( |
| const TaskTraits& traits, |
| SingleThreadTaskRunnerThreadMode thread_mode) { |
| return single_thread_task_runner_manager_.CreateSingleThreadTaskRunner( |
| traits, thread_mode); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| scoped_refptr<SingleThreadTaskRunner> ThreadPoolImpl::CreateCOMSTATaskRunner( |
| const TaskTraits& traits, |
| SingleThreadTaskRunnerThreadMode thread_mode) { |
| return single_thread_task_runner_manager_.CreateCOMSTATaskRunner(traits, |
| thread_mode); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| scoped_refptr<UpdateableSequencedTaskRunner> |
| ThreadPoolImpl::CreateUpdateableSequencedTaskRunner(const TaskTraits& traits) { |
| return MakeRefCounted<PooledSequencedTaskRunner>(traits, this); |
| } |
| |
| absl::optional<TimeTicks> ThreadPoolImpl::NextScheduledRunTimeForTesting() |
| const { |
| if (task_tracker_->HasIncompleteTaskSourcesForTesting()) |
| return TimeTicks::Now(); |
| return delayed_task_manager_.NextScheduledRunTime(); |
| } |
| |
| void ThreadPoolImpl::ProcessRipeDelayedTasksForTesting() { |
| delayed_task_manager_.ProcessRipeTasks(); |
| } |
| |
| // static |
| void ThreadPoolImpl::SetSynchronousThreadStartForTesting(bool enabled) { |
| DCHECK(!ThreadPoolInstance::Get()); |
| g_synchronous_thread_start_for_testing = enabled; |
| } |
| |
| size_t ThreadPoolImpl::GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated( |
| const TaskTraits& traits) const { |
| // This method does not support getting the maximum number of BEST_EFFORT |
| // tasks that can run concurrently in a pool. |
| DCHECK_NE(traits.priority(), TaskPriority::BEST_EFFORT); |
| return GetThreadGroupForTraits(traits) |
| ->GetMaxConcurrentNonBlockedTasksDeprecated(); |
| } |
| |
| void ThreadPoolImpl::Shutdown() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Cancels an internal service thread task. This must be done before stopping |
| // the service thread. |
| delayed_task_manager_.Shutdown(); |
| |
| // Stop() the ServiceThread before triggering shutdown. This ensures that no |
| // more delayed tasks or file descriptor watches will trigger during shutdown |
| // (preventing http://crbug.com/698140). None of these asynchronous tasks |
| // being guaranteed to happen anyways, stopping right away is valid behavior |
| // and avoids the more complex alternative of shutting down the service thread |
| // atomically during TaskTracker shutdown. |
| service_thread_.Stop(); |
| |
| task_tracker_->StartShutdown(); |
| |
| // Allow all tasks to run. Done after initiating shutdown to ensure that non- |
| // BLOCK_SHUTDOWN tasks don't get a chance to run and that BLOCK_SHUTDOWN |
| // tasks run with a normal thread priority. |
| UpdateCanRunPolicy(); |
| |
| // Ensures that there are enough background worker to run BLOCK_SHUTDOWN |
| // tasks. |
| foreground_thread_group_->OnShutdownStarted(); |
| if (utility_thread_group_) |
| utility_thread_group_->OnShutdownStarted(); |
| if (background_thread_group_) |
| background_thread_group_->OnShutdownStarted(); |
| |
| task_tracker_->CompleteShutdown(); |
| } |
| |
| void ThreadPoolImpl::FlushForTesting() { |
| task_tracker_->FlushForTesting(); |
| } |
| |
| void ThreadPoolImpl::FlushAsyncForTesting(OnceClosure flush_callback) { |
| task_tracker_->FlushAsyncForTesting(std::move(flush_callback)); |
| } |
| |
| void ThreadPoolImpl::JoinForTesting() { |
| #if DCHECK_IS_ON() |
| DCHECK(!join_for_testing_returned_.IsSet()); |
| #endif |
| // Cancels an internal service thread task. This must be done before stopping |
| // the service thread. |
| delayed_task_manager_.Shutdown(); |
| // The service thread must be stopped before the workers are joined, otherwise |
| // tasks scheduled by the DelayedTaskManager might be posted between joining |
| // those workers and stopping the service thread which will cause a CHECK. See |
| // https://crbug.com/771701. |
| service_thread_.Stop(); |
| single_thread_task_runner_manager_.JoinForTesting(); |
| foreground_thread_group_->JoinForTesting(); |
| if (utility_thread_group_) |
| utility_thread_group_->JoinForTesting(); // IN-TEST |
| if (background_thread_group_) |
| background_thread_group_->JoinForTesting(); |
| #if DCHECK_IS_ON() |
| join_for_testing_returned_.Set(); |
| #endif |
| } |
| |
| void ThreadPoolImpl::BeginFence() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| ++num_fences_; |
| UpdateCanRunPolicy(); |
| } |
| |
| void ThreadPoolImpl::EndFence() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_GT(num_fences_, 0); |
| --num_fences_; |
| UpdateCanRunPolicy(); |
| } |
| |
| void ThreadPoolImpl::BeginBestEffortFence() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| ++num_best_effort_fences_; |
| UpdateCanRunPolicy(); |
| } |
| |
| void ThreadPoolImpl::EndBestEffortFence() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_GT(num_best_effort_fences_, 0); |
| --num_best_effort_fences_; |
| UpdateCanRunPolicy(); |
| } |
| |
| void ThreadPoolImpl::BeginFizzlingBlockShutdownTasks() { |
| task_tracker_->BeginFizzlingBlockShutdownTasks(); |
| } |
| |
| void ThreadPoolImpl::EndFizzlingBlockShutdownTasks() { |
| task_tracker_->EndFizzlingBlockShutdownTasks(); |
| } |
| |
| bool ThreadPoolImpl::PostTaskWithSequenceNow(Task task, |
| scoped_refptr<Sequence> sequence) { |
| auto transaction = sequence->BeginTransaction(); |
| const bool sequence_should_be_queued = transaction.WillPushImmediateTask(); |
| RegisteredTaskSource task_source; |
| if (sequence_should_be_queued) { |
| task_source = task_tracker_->RegisterTaskSource(sequence); |
| // We shouldn't push |task| if we're not allowed to queue |task_source|. |
| if (!task_source) |
| return false; |
| } |
| if (!task_tracker_->WillPostTaskNow(task, transaction.traits().priority())) |
| return false; |
| transaction.PushImmediateTask(std::move(task)); |
| if (task_source) { |
| const TaskTraits traits = transaction.traits(); |
| GetThreadGroupForTraits(traits)->PushTaskSourceAndWakeUpWorkers( |
| {std::move(task_source), std::move(transaction)}); |
| } |
| return true; |
| } |
| |
| bool ThreadPoolImpl::PostTaskWithSequence(Task task, |
| scoped_refptr<Sequence> sequence) { |
| // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167 |
| // for details. |
| CHECK(task.task); |
| DCHECK(sequence); |
| |
| #if BUILDFLAG(IS_WIN) |
| // Force reading |task.posted_from.file_name()| to produce a useful crash |
| // report if the address is invalid. A crash report generated later when the |
| // task is executed would not contain the PostTask stack. |
| // |
| // TODO(crbug.com/1224432): Remove after resolving the crash. |
| DEBUG_ALIAS_FOR_CSTR(task_posted_from, task.posted_from.file_name(), 32); |
| #endif |
| |
| if (!task_tracker_->WillPostTask(&task, sequence->shutdown_behavior())) { |
| // `task`'s destructor may run sequence-affine code, so it must be leaked |
| // when `WillPostTask` returns false. |
| auto leak = std::make_unique<Task>(std::move(task)); |
| ANNOTATE_LEAKING_OBJECT_PTR(leak.get()); |
| leak.release(); |
| return false; |
| } |
| |
| if (task.delayed_run_time.is_null()) { |
| return PostTaskWithSequenceNow(std::move(task), std::move(sequence)); |
| } else { |
| // It's safe to take a ref on this pointer since the caller must have a ref |
| // to the TaskRunner in order to post. |
| scoped_refptr<TaskRunner> task_runner = sequence->task_runner(); |
| delayed_task_manager_.AddDelayedTask( |
| std::move(task), |
| BindOnce( |
| [](scoped_refptr<Sequence> sequence, |
| ThreadPoolImpl* thread_pool_impl, Task task) { |
| thread_pool_impl->PostTaskWithSequenceNow(std::move(task), |
| std::move(sequence)); |
| }, |
| std::move(sequence), Unretained(this)), |
| std::move(task_runner)); |
| } |
| |
| return true; |
| } |
| |
| bool ThreadPoolImpl::ShouldYield(const TaskSource* task_source) { |
| const TaskPriority priority = task_source->priority_racy(); |
| auto* const thread_group = |
| GetThreadGroupForTraits({priority, task_source->thread_policy()}); |
| // A task whose priority changed and is now running in the wrong thread group |
| // should yield so it's rescheduled in the right one. |
| if (!thread_group->IsBoundToCurrentThread()) |
| return true; |
| return GetThreadGroupForTraits({priority, task_source->thread_policy()}) |
| ->ShouldYield(task_source->GetSortKey()); |
| } |
| |
| bool ThreadPoolImpl::EnqueueJobTaskSource( |
| scoped_refptr<JobTaskSource> task_source) { |
| auto registered_task_source = |
| task_tracker_->RegisterTaskSource(std::move(task_source)); |
| if (!registered_task_source) |
| return false; |
| auto transaction = registered_task_source->BeginTransaction(); |
| const TaskTraits traits = transaction.traits(); |
| GetThreadGroupForTraits(traits)->PushTaskSourceAndWakeUpWorkers( |
| {std::move(registered_task_source), std::move(transaction)}); |
| return true; |
| } |
| |
| void ThreadPoolImpl::RemoveJobTaskSource( |
| scoped_refptr<JobTaskSource> task_source) { |
| auto transaction = task_source->BeginTransaction(); |
| ThreadGroup* const current_thread_group = |
| GetThreadGroupForTraits(transaction.traits()); |
| current_thread_group->RemoveTaskSource(*task_source); |
| } |
| |
| void ThreadPoolImpl::UpdatePriority(scoped_refptr<TaskSource> task_source, |
| TaskPriority priority) { |
| auto transaction = task_source->BeginTransaction(); |
| |
| if (transaction.traits().priority() == priority) |
| return; |
| |
| if (transaction.traits().priority() == TaskPriority::BEST_EFFORT) { |
| DCHECK(transaction.traits().thread_policy_set_explicitly()) |
| << "A ThreadPolicy must be specified in the TaskTraits of an " |
| "UpdateableSequencedTaskRunner whose priority is increased from " |
| "BEST_EFFORT. See ThreadPolicy documentation."; |
| } |
| |
| ThreadGroup* const current_thread_group = |
| GetThreadGroupForTraits(transaction.traits()); |
| transaction.UpdatePriority(priority); |
| ThreadGroup* const new_thread_group = |
| GetThreadGroupForTraits(transaction.traits()); |
| |
| if (new_thread_group == current_thread_group) { |
| // |task_source|'s position needs to be updated within its current thread |
| // group. |
| current_thread_group->UpdateSortKey(std::move(transaction)); |
| } else { |
| // |task_source| is changing thread groups; remove it from its current |
| // thread group and reenqueue it. |
| auto registered_task_source = |
| current_thread_group->RemoveTaskSource(*task_source); |
| if (registered_task_source) { |
| DCHECK(task_source); |
| new_thread_group->PushTaskSourceAndWakeUpWorkers( |
| {std::move(registered_task_source), std::move(transaction)}); |
| } |
| } |
| } |
| |
| void ThreadPoolImpl::UpdateJobPriority(scoped_refptr<TaskSource> task_source, |
| TaskPriority priority) { |
| UpdatePriority(std::move(task_source), priority); |
| } |
| |
| const ThreadGroup* ThreadPoolImpl::GetThreadGroupForTraits( |
| const TaskTraits& traits) const { |
| return const_cast<ThreadPoolImpl*>(this)->GetThreadGroupForTraits(traits); |
| } |
| |
| ThreadGroup* ThreadPoolImpl::GetThreadGroupForTraits(const TaskTraits& traits) { |
| if (traits.priority() == TaskPriority::BEST_EFFORT && |
| traits.thread_policy() == ThreadPolicy::PREFER_BACKGROUND && |
| background_thread_group_) { |
| return background_thread_group_.get(); |
| } |
| |
| if (traits.priority() <= TaskPriority::USER_VISIBLE && |
| traits.thread_policy() == ThreadPolicy::PREFER_BACKGROUND && |
| utility_thread_group_) { |
| return utility_thread_group_.get(); |
| } |
| |
| return foreground_thread_group_.get(); |
| } |
| |
| void ThreadPoolImpl::UpdateCanRunPolicy() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CanRunPolicy can_run_policy; |
| if ((num_fences_ == 0 && num_best_effort_fences_ == 0 && |
| !has_disable_best_effort_switch_) || |
| task_tracker_->HasShutdownStarted()) { |
| can_run_policy = CanRunPolicy::kAll; |
| } else if (num_fences_ != 0) { |
| can_run_policy = CanRunPolicy::kNone; |
| } else { |
| DCHECK(num_best_effort_fences_ > 0 || has_disable_best_effort_switch_); |
| can_run_policy = CanRunPolicy::kForegroundOnly; |
| } |
| |
| task_tracker_->SetCanRunPolicy(can_run_policy); |
| foreground_thread_group_->DidUpdateCanRunPolicy(); |
| if (utility_thread_group_) |
| utility_thread_group_->DidUpdateCanRunPolicy(); |
| if (background_thread_group_) |
| background_thread_group_->DidUpdateCanRunPolicy(); |
| single_thread_task_runner_manager_.DidUpdateCanRunPolicy(); |
| } |
| |
| } // namespace internal |
| } // namespace base |