// 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/worker_thread.h" #include #include #include #include #include "base/allocator/partition_allocator/partition_alloc_buildflags.h" #include "base/allocator/partition_allocator/partition_alloc_config.h" #include "base/check_op.h" #include "base/compiler_specific.h" #include "base/debug/alias.h" #include "base/feature_list.h" #include "base/functional/callback_helpers.h" #include "base/synchronization/waitable_event.h" #include "base/task/task_features.h" #include "base/task/thread_pool/environment_config.h" #include "base/task/thread_pool/task_tracker.h" #include "base/task/thread_pool/worker_thread_observer.h" #include "base/threading/hang_watcher.h" #include "base/time/time.h" #include "base/time/time_override.h" #include "base/trace_event/base_tracing.h" #include "build/build_config.h" #include "third_party/abseil-cpp/absl/types/optional.h" #if (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)) || BUILDFLAG(IS_FUCHSIA) #include "base/files/file_descriptor_watcher_posix.h" #endif #if BUILDFLAG(IS_APPLE) #include "base/mac/scoped_nsautorelease_pool.h" #endif #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \ PA_CONFIG(THREAD_CACHE_SUPPORTED) #include "base/allocator/partition_allocator/thread_cache.h" #endif namespace base::internal { namespace { #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \ PA_CONFIG(THREAD_CACHE_SUPPORTED) // Returns the desired sleep time before the worker has to wake up to purge // the cache thread or reclaim itself. |min_sleep_time| contains the minimal // acceptable amount of time to sleep. TimeDelta GetSleepTimeBeforePurge(TimeDelta min_sleep_time) { const TimeTicks now = TimeTicks::Now(); // Do not wake up to purge within the first minute of process lifetime. In // short lived processes this will avoid waking up to try and save memory // for a heap that will be going away soon. For longer lived processes this // should allow for better performance at process startup since even if a // worker goes to sleep for kPurgeThreadCacheIdleDelay it's very likely it // will be needed soon after because of heavy startup workloads. constexpr TimeDelta kFirstSleepLength = Minutes(1); // Use the first time a worker goes to sleep in this process as an // approximation of the process creation time. static const TimeTicks first_scheduled_wake = now + kFirstSleepLength; // Align wakeups for purges to reduce the chances of taking the CPU out of // sleep multiple times for these operations. constexpr TimeDelta kPurgeThreadCacheIdleDelay = Seconds(1); const TimeTicks snapped_wake = (now + min_sleep_time) .SnappedToNextTick(TimeTicks(), kPurgeThreadCacheIdleDelay); // Avoid scheduling at |first_scheduled_wake| if it would result in a sleep // that's too short. return std::max(snapped_wake - now, first_scheduled_wake - now); } #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && // PA_CONFIG(THREAD_CACHE_SUPPORTED) bool IsDelayFirstWorkerSleepEnabled() { static bool state = FeatureList::IsEnabled(kDelayFirstWorkerWake); return state; } } // namespace constexpr TimeDelta WorkerThread::Delegate::kPurgeThreadCacheIdleDelay; void WorkerThread::Delegate::WaitForWork(WaitableEvent* wake_up_event) { DCHECK(wake_up_event); const TimeDelta sleep_time = GetSleepTimeout(); // When a thread goes to sleep, the memory retained by its thread cache is // trapped there for as long as the thread sleeps. To prevent that, we can // either purge the thread cache right before going to sleep, or after some // delay. // // Purging the thread cache incurs a cost on the next task, since its thread // cache will be empty and allocation performance initially lower. As a lot of // sleeps are very short, do not purge all the time (this would also make // sleep / wakeups cycles more costly). // // Instead, sleep for min(timeout, 1s). If the wait times out then purge at // that point, and go to sleep for the remaining of the time. This ensures // that we do no work for short sleeps, and that threads do not get awaken // many times. #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \ PA_CONFIG(THREAD_CACHE_SUPPORTED) TimeDelta min_sleep_time = std::min(sleep_time, kPurgeThreadCacheIdleDelay); if (IsDelayFirstWorkerSleepEnabled()) min_sleep_time = GetSleepTimeBeforePurge(min_sleep_time); const bool was_signaled = wake_up_event->TimedWait(min_sleep_time); // Timed out. if (!was_signaled) { partition_alloc::ThreadCache::PurgeCurrentThread(); // The thread woke up to purge before its standard reclaim time. Sleep for // what's remaining until then. if (sleep_time > min_sleep_time) { wake_up_event->TimedWait(sleep_time.is_max() ? base::TimeDelta::Max() : sleep_time - min_sleep_time); } } #else wake_up_event->TimedWait(sleep_time); #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && // PA_CONFIG(THREAD_CACHE_SUPPORTED) } WorkerThread::WorkerThread(ThreadType thread_type_hint, std::unique_ptr delegate, TrackedRef task_tracker, size_t sequence_num, const CheckedLock* predecessor_lock) : thread_lock_(predecessor_lock), delegate_(std::move(delegate)), task_tracker_(std::move(task_tracker)), thread_type_hint_(thread_type_hint), current_thread_type_(GetDesiredThreadType()), sequence_num_(sequence_num) { DCHECK(delegate_); DCHECK(task_tracker_); DCHECK(CanUseBackgroundThreadTypeForWorkerThread() || thread_type_hint_ != ThreadType::kBackground); DCHECK(CanUseUtilityThreadTypeForWorkerThread() || thread_type_hint != ThreadType::kUtility); wake_up_event_.declare_only_used_while_idle(); } bool WorkerThread::Start( scoped_refptr io_thread_task_runner, WorkerThreadObserver* worker_thread_observer) { CheckedLock::AssertNoLockHeldOnCurrentThread(); // Prime kDelayFirstWorkerWake's feature state right away on thread creation // instead of looking it up for the first time later on thread as this avoids // a data race in tests that may ~FeatureList while the first worker thread // is still initializing (the first WorkerThread will be started on the main // thread as part of ThreadPoolImpl::Start() so doing it then avoids this // race), crbug.com/1344573. // Note 1: the feature state is always available at this point as // ThreadPoolInstance::Start() contractually happens-after FeatureList // initialization. // Note 2: This is done on Start instead of in the constructor as construction // happens under a ThreadGroupImpl lock which precludes calling into // FeatureList (as that can also use a lock). IsDelayFirstWorkerSleepEnabled(); CheckedAutoLock auto_lock(thread_lock_); DCHECK(thread_handle_.is_null()); #if (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)) || BUILDFLAG(IS_FUCHSIA) DCHECK(io_thread_task_runner); io_thread_task_runner_ = std::move(io_thread_task_runner); #endif if (should_exit_.IsSet() || join_called_for_testing_.IsSet()) return true; DCHECK(!worker_thread_observer_); worker_thread_observer_ = worker_thread_observer; self_ = this; constexpr size_t kDefaultStackSize = 0; PlatformThread::CreateWithType(kDefaultStackSize, this, &thread_handle_, current_thread_type_); if (thread_handle_.is_null()) { self_ = nullptr; return false; } return true; } void WorkerThread::WakeUp() { // Signalling an event can deschedule the current thread. Since being // descheduled while holding a lock is undesirable (https://crbug.com/890978), // assert that no lock is held by the current thread. CheckedLock::AssertNoLockHeldOnCurrentThread(); // Calling WakeUp() after Cleanup() or Join() is wrong because the // WorkerThread cannot run more tasks. DCHECK(!join_called_for_testing_.IsSet()); DCHECK(!should_exit_.IsSet()); TRACE_EVENT_INSTANT("wakeup.flow", "WorkerThread::WakeUp", perfetto::Flow::FromPointer(this)); wake_up_event_.Signal(); } void WorkerThread::JoinForTesting() { DCHECK(!join_called_for_testing_.IsSet()); join_called_for_testing_.Set(); wake_up_event_.Signal(); PlatformThreadHandle thread_handle; { CheckedAutoLock auto_lock(thread_lock_); if (thread_handle_.is_null()) return; thread_handle = thread_handle_; // Reset |thread_handle_| so it isn't joined by the destructor. thread_handle_ = PlatformThreadHandle(); } PlatformThread::Join(thread_handle); } bool WorkerThread::ThreadAliveForTesting() const { CheckedAutoLock auto_lock(thread_lock_); return !thread_handle_.is_null(); } WorkerThread::~WorkerThread() { CheckedAutoLock auto_lock(thread_lock_); // If |thread_handle_| wasn't joined, detach it. if (!thread_handle_.is_null()) { DCHECK(!join_called_for_testing_.IsSet()); PlatformThread::Detach(thread_handle_); } } void WorkerThread::Cleanup() { DCHECK(!should_exit_.IsSet()); should_exit_.Set(); wake_up_event_.Signal(); } void WorkerThread::MaybeUpdateThreadType() { UpdateThreadType(GetDesiredThreadType()); } void WorkerThread::BeginUnusedPeriod() { CheckedAutoLock auto_lock(thread_lock_); DCHECK(last_used_time_.is_null()); last_used_time_ = subtle::TimeTicksNowIgnoringOverride(); } void WorkerThread::EndUnusedPeriod() { CheckedAutoLock auto_lock(thread_lock_); DCHECK(!last_used_time_.is_null()); last_used_time_ = TimeTicks(); } TimeTicks WorkerThread::GetLastUsedTime() const { CheckedAutoLock auto_lock(thread_lock_); return last_used_time_; } bool WorkerThread::ShouldExit() const { // The ordering of the checks is important below. This WorkerThread may be // released and outlive |task_tracker_| in unit tests. However, when the // WorkerThread is released, |should_exit_| will be set, so check that // first. return should_exit_.IsSet() || join_called_for_testing_.IsSet() || task_tracker_->IsShutdownComplete(); } ThreadType WorkerThread::GetDesiredThreadType() const { // To avoid shutdown hangs, disallow a type below kNormal during shutdown if (task_tracker_->HasShutdownStarted()) return ThreadType::kDefault; return thread_type_hint_; } void WorkerThread::UpdateThreadType(ThreadType desired_thread_type) { if (desired_thread_type == current_thread_type_) return; PlatformThread::SetCurrentThreadType(desired_thread_type); current_thread_type_ = desired_thread_type; } void WorkerThread::ThreadMain() { #if (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)) || BUILDFLAG(IS_FUCHSIA) DCHECK(io_thread_task_runner_); FileDescriptorWatcher file_descriptor_watcher(io_thread_task_runner_); #endif if (thread_type_hint_ == ThreadType::kBackground) { switch (delegate_->GetThreadLabel()) { case ThreadLabel::POOLED: RunBackgroundPooledWorker(); return; case ThreadLabel::SHARED: RunBackgroundSharedWorker(); return; case ThreadLabel::DEDICATED: RunBackgroundDedicatedWorker(); return; #if BUILDFLAG(IS_WIN) case ThreadLabel::SHARED_COM: RunBackgroundSharedCOMWorker(); return; case ThreadLabel::DEDICATED_COM: RunBackgroundDedicatedCOMWorker(); return; #endif // BUILDFLAG(IS_WIN) } } switch (delegate_->GetThreadLabel()) { case ThreadLabel::POOLED: RunPooledWorker(); return; case ThreadLabel::SHARED: RunSharedWorker(); return; case ThreadLabel::DEDICATED: RunDedicatedWorker(); return; #if BUILDFLAG(IS_WIN) case ThreadLabel::SHARED_COM: RunSharedCOMWorker(); return; case ThreadLabel::DEDICATED_COM: RunDedicatedCOMWorker(); return; #endif // BUILDFLAG(IS_WIN) } } NOINLINE void WorkerThread::RunPooledWorker() { RunWorker(); NO_CODE_FOLDING(); } NOINLINE void WorkerThread::RunBackgroundPooledWorker() { RunWorker(); NO_CODE_FOLDING(); } NOINLINE void WorkerThread::RunSharedWorker() { RunWorker(); NO_CODE_FOLDING(); } NOINLINE void WorkerThread::RunBackgroundSharedWorker() { RunWorker(); NO_CODE_FOLDING(); } NOINLINE void WorkerThread::RunDedicatedWorker() { RunWorker(); NO_CODE_FOLDING(); } NOINLINE void WorkerThread::RunBackgroundDedicatedWorker() { RunWorker(); NO_CODE_FOLDING(); } #if BUILDFLAG(IS_WIN) NOINLINE void WorkerThread::RunSharedCOMWorker() { RunWorker(); NO_CODE_FOLDING(); } NOINLINE void WorkerThread::RunBackgroundSharedCOMWorker() { RunWorker(); NO_CODE_FOLDING(); } NOINLINE void WorkerThread::RunDedicatedCOMWorker() { RunWorker(); NO_CODE_FOLDING(); } NOINLINE void WorkerThread::RunBackgroundDedicatedCOMWorker() { RunWorker(); NO_CODE_FOLDING(); } #endif // BUILDFLAG(IS_WIN) void WorkerThread::RunWorker() { DCHECK_EQ(self_, this); TRACE_EVENT_INSTANT0("base", "WorkerThread born", TRACE_EVENT_SCOPE_THREAD); TRACE_EVENT_BEGIN0("base", "WorkerThread active"); if (worker_thread_observer_) worker_thread_observer_->OnWorkerThreadMainEntry(); delegate_->OnMainEntry(this); // Background threads can take an arbitrary amount of time to complete, do not // watch them for hangs. Ignore priority boosting for now. const bool watch_for_hangs = base::HangWatcher::IsThreadPoolHangWatchingEnabled() && GetDesiredThreadType() != ThreadType::kBackground; // If this process has a HangWatcher register this thread for watching. base::ScopedClosureRunner unregister_for_hang_watching; if (watch_for_hangs) { unregister_for_hang_watching = base::HangWatcher::RegisterThread( base::HangWatcher::ThreadType::kThreadPoolThread); } // A WorkerThread starts out waiting for work. { TRACE_EVENT_END0("base", "WorkerThread active"); // TODO(crbug.com/1021571): Remove this once fixed. PERFETTO_INTERNAL_ADD_EMPTY_EVENT(); delegate_->WaitForWork(&wake_up_event_); TRACE_EVENT_BEGIN("base", "WorkerThread active", perfetto::TerminatingFlow::FromPointer(this)); } bool got_work_this_wakeup = false; while (!ShouldExit()) { #if BUILDFLAG(IS_APPLE) mac::ScopedNSAutoreleasePool autorelease_pool; #endif absl::optional hang_watch_scope; if (watch_for_hangs) hang_watch_scope.emplace(); UpdateThreadType(GetDesiredThreadType()); // Get the task source containing the next task to execute. RegisteredTaskSource task_source = delegate_->GetWork(this); if (!task_source) { // Exit immediately if GetWork() resulted in detaching this worker. if (ShouldExit()) break; // If this is the first time we called GetWork and the worker's still // alive, record that this is an unnecessary wakeup. if (!got_work_this_wakeup) delegate_->RecordUnnecessaryWakeup(); TRACE_EVENT_END0("base", "WorkerThread active"); // TODO(crbug.com/1021571): Remove this once fixed. PERFETTO_INTERNAL_ADD_EMPTY_EVENT(); hang_watch_scope.reset(); delegate_->WaitForWork(&wake_up_event_); got_work_this_wakeup = false; TRACE_EVENT_BEGIN("base", "WorkerThread active", perfetto::TerminatingFlow::FromPointer(this)); continue; } got_work_this_wakeup = true; // Alias pointer for investigation of memory corruption. crbug.com/1218384 TaskSource* task_source_before_run = task_source.get(); base::debug::Alias(&task_source_before_run); task_source = task_tracker_->RunAndPopNextTask(std::move(task_source)); // Alias pointer for investigation of memory corruption. crbug.com/1218384 TaskSource* task_source_before_move = task_source.get(); base::debug::Alias(&task_source_before_move); delegate_->DidProcessTask(std::move(task_source)); // Check that task_source is always cleared, to help investigation of memory // corruption where task_source is non-null after being moved. // crbug.com/1218384 CHECK(!task_source); // Calling WakeUp() guarantees that this WorkerThread will run Tasks from // TaskSources returned by the GetWork() method of |delegate_| until it // returns nullptr. Resetting |wake_up_event_| here doesn't break this // invariant and avoids a useless loop iteration before going to sleep if // WakeUp() is called while this WorkerThread is awake. wake_up_event_.Reset(); } // Important: It is unsafe to access unowned state (e.g. |task_tracker_|) // after invoking OnMainExit(). delegate_->OnMainExit(this); if (worker_thread_observer_) worker_thread_observer_->OnWorkerThreadMainExit(); // Release the self-reference to |this|. This can result in deleting |this| // and as such no more member accesses should be made after this point. self_ = nullptr; TRACE_EVENT_END0("base", "WorkerThread active"); TRACE_EVENT_INSTANT0("base", "WorkerThread dead", TRACE_EVENT_SCOPE_THREAD); // TODO(crbug.com/1021571): Remove this once fixed. PERFETTO_INTERNAL_ADD_EMPTY_EVENT(); } } // namespace base::internal