blob: 504b0dbc06fb876b94cfe45a9deee832d2d7127a [file] [log] [blame]
// Copyright 2016 The Chromium 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 "base/task/task_scheduler/scheduler_worker.h"
#include <utility>
#include "base/compiler_specific.h"
#include "base/debug/alias.h"
#include "base/logging.h"
#include "base/task/task_scheduler/environment_config.h"
#include "base/task/task_scheduler/scheduler_worker_observer.h"
#include "base/task/task_scheduler/task_tracker.h"
#include "base/trace_event/trace_event.h"
#if defined(OS_MACOSX)
#include "base/mac/scoped_nsautorelease_pool.h"
#elif defined(OS_WIN)
#include "base/win/com_init_check_hook.h"
#include "base/win/scoped_com_initializer.h"
#include "starboard/types.h"
#endif
namespace base {
namespace internal {
void SchedulerWorker::Delegate::WaitForWork(WaitableEvent* wake_up_event) {
DCHECK(wake_up_event);
const TimeDelta sleep_time = GetSleepTimeout();
if (sleep_time.is_max()) {
// Calling TimedWait with TimeDelta::Max is not recommended per
// http://crbug.com/465948.
wake_up_event->Wait();
} else {
wake_up_event->TimedWait(sleep_time);
}
}
SchedulerWorker::SchedulerWorker(
ThreadPriority priority_hint,
std::unique_ptr<Delegate> delegate,
TrackedRef<TaskTracker> task_tracker,
const SchedulerLock* predecessor_lock,
SchedulerBackwardCompatibility backward_compatibility)
: thread_lock_(predecessor_lock),
delegate_(std::move(delegate)),
task_tracker_(std::move(task_tracker)),
priority_hint_(priority_hint),
current_thread_priority_(GetDesiredThreadPriority())
#if defined(OS_WIN) && !defined(COM_INIT_CHECK_HOOK_ENABLED)
,
backward_compatibility_(backward_compatibility)
#endif
{
DCHECK(delegate_);
DCHECK(task_tracker_);
DCHECK(CanUseBackgroundPriorityForSchedulerWorker() ||
priority_hint_ != ThreadPriority::BACKGROUND);
}
bool SchedulerWorker::Start(
SchedulerWorkerObserver* scheduler_worker_observer) {
AutoSchedulerLock auto_lock(thread_lock_);
DCHECK(thread_handle_.is_null());
if (should_exit_.IsSet())
return true;
DCHECK(!scheduler_worker_observer_);
scheduler_worker_observer_ = scheduler_worker_observer;
self_ = this;
constexpr size_t kDefaultStackSize = 0;
PlatformThread::CreateWithPriority(kDefaultStackSize, this, &thread_handle_,
current_thread_priority_);
if (thread_handle_.is_null()) {
self_ = nullptr;
return false;
}
return true;
}
void SchedulerWorker::WakeUp() {
// Calling WakeUp() after Cleanup() or Join() is wrong because the
// SchedulerWorker cannot run more tasks.
DCHECK(!join_called_for_testing_.IsSet());
DCHECK(!should_exit_.IsSet());
wake_up_event_.Signal();
}
void SchedulerWorker::JoinForTesting() {
DCHECK(!join_called_for_testing_.IsSet());
join_called_for_testing_.Set();
wake_up_event_.Signal();
PlatformThreadHandle thread_handle;
{
AutoSchedulerLock auto_lock(thread_lock_);
DCHECK(!thread_handle_.is_null());
thread_handle = thread_handle_;
// Reset |thread_handle_| so it isn't joined by the destructor.
thread_handle_ = PlatformThreadHandle();
}
PlatformThread::Join(thread_handle);
}
bool SchedulerWorker::ThreadAliveForTesting() const {
AutoSchedulerLock auto_lock(thread_lock_);
return !thread_handle_.is_null();
}
SchedulerWorker::~SchedulerWorker() {
AutoSchedulerLock 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 SchedulerWorker::Cleanup() {
DCHECK(!should_exit_.IsSet());
should_exit_.Set();
wake_up_event_.Signal();
}
void SchedulerWorker::BeginUnusedPeriod() {
AutoSchedulerLock auto_lock(thread_lock_);
DCHECK(last_used_time_.is_null());
last_used_time_ = TimeTicks::Now();
}
void SchedulerWorker::EndUnusedPeriod() {
AutoSchedulerLock auto_lock(thread_lock_);
DCHECK(!last_used_time_.is_null());
last_used_time_ = TimeTicks();
}
TimeTicks SchedulerWorker::GetLastUsedTime() const {
AutoSchedulerLock auto_lock(thread_lock_);
return last_used_time_;
}
bool SchedulerWorker::ShouldExit() const {
// The ordering of the checks is important below. This SchedulerWorker may be
// released and outlive |task_tracker_| in unit tests. However, when the
// SchedulerWorker is released, |should_exit_| will be set, so check that
// first.
return should_exit_.IsSet() || join_called_for_testing_.IsSet() ||
task_tracker_->IsShutdownComplete();
}
ThreadPriority SchedulerWorker::GetDesiredThreadPriority() const {
// To avoid shutdown hangs, disallow a priority below NORMAL during shutdown
if (task_tracker_->HasShutdownStarted())
return ThreadPriority::NORMAL;
return priority_hint_;
}
void SchedulerWorker::UpdateThreadPriority(
ThreadPriority desired_thread_priority) {
if (desired_thread_priority == current_thread_priority_)
return;
PlatformThread::SetCurrentThreadPriority(desired_thread_priority);
current_thread_priority_ = desired_thread_priority;
}
void SchedulerWorker::ThreadMain() {
if (priority_hint_ == ThreadPriority::BACKGROUND) {
switch (delegate_->GetThreadLabel()) {
case ThreadLabel::POOLED:
RunBackgroundPooledWorker();
return;
case ThreadLabel::SHARED:
RunBackgroundSharedWorker();
return;
case ThreadLabel::DEDICATED:
RunBackgroundDedicatedWorker();
return;
#if defined(OS_WIN)
case ThreadLabel::SHARED_COM:
RunBackgroundSharedCOMWorker();
return;
case ThreadLabel::DEDICATED_COM:
RunBackgroundDedicatedCOMWorker();
return;
#endif // defined(OS_WIN)
}
}
switch (delegate_->GetThreadLabel()) {
case ThreadLabel::POOLED:
RunPooledWorker();
return;
case ThreadLabel::SHARED:
RunSharedWorker();
return;
case ThreadLabel::DEDICATED:
RunDedicatedWorker();
return;
#if defined(OS_WIN)
case ThreadLabel::SHARED_COM:
RunSharedCOMWorker();
return;
case ThreadLabel::DEDICATED_COM:
RunDedicatedCOMWorker();
return;
#endif // defined(OS_WIN)
}
}
NOINLINE void SchedulerWorker::RunPooledWorker() {
const int line_number = __LINE__;
RunWorker();
base::debug::Alias(&line_number);
}
NOINLINE void SchedulerWorker::RunBackgroundPooledWorker() {
const int line_number = __LINE__;
RunWorker();
base::debug::Alias(&line_number);
}
NOINLINE void SchedulerWorker::RunSharedWorker() {
const int line_number = __LINE__;
RunWorker();
base::debug::Alias(&line_number);
}
NOINLINE void SchedulerWorker::RunBackgroundSharedWorker() {
const int line_number = __LINE__;
RunWorker();
base::debug::Alias(&line_number);
}
NOINLINE void SchedulerWorker::RunDedicatedWorker() {
const int line_number = __LINE__;
RunWorker();
base::debug::Alias(&line_number);
}
NOINLINE void SchedulerWorker::RunBackgroundDedicatedWorker() {
const int line_number = __LINE__;
RunWorker();
base::debug::Alias(&line_number);
}
#if defined(OS_WIN)
NOINLINE void SchedulerWorker::RunSharedCOMWorker() {
const int line_number = __LINE__;
RunWorker();
base::debug::Alias(&line_number);
}
NOINLINE void SchedulerWorker::RunBackgroundSharedCOMWorker() {
const int line_number = __LINE__;
RunWorker();
base::debug::Alias(&line_number);
}
NOINLINE void SchedulerWorker::RunDedicatedCOMWorker() {
const int line_number = __LINE__;
RunWorker();
base::debug::Alias(&line_number);
}
NOINLINE void SchedulerWorker::RunBackgroundDedicatedCOMWorker() {
const int line_number = __LINE__;
RunWorker();
base::debug::Alias(&line_number);
}
#endif // defined(OS_WIN)
void SchedulerWorker::RunWorker() {
DCHECK_EQ(self_.get(), this);
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("task_scheduler_diagnostics"),
"SchedulerWorkerThread alive");
TRACE_EVENT_BEGIN0("task_scheduler", "SchedulerWorkerThread active");
if (scheduler_worker_observer_)
scheduler_worker_observer_->OnSchedulerWorkerMainEntry();
delegate_->OnMainEntry(this);
// A SchedulerWorker starts out waiting for work.
{
TRACE_EVENT_END0("task_scheduler", "SchedulerWorkerThread active");
delegate_->WaitForWork(&wake_up_event_);
TRACE_EVENT_BEGIN0("task_scheduler", "SchedulerWorkerThread active");
}
// When defined(COM_INIT_CHECK_HOOK_ENABLED), ignore
// SchedulerBackwardCompatibility::INIT_COM_STA to find incorrect uses of
// COM that should be running in a COM STA Task Runner.
#if defined(OS_WIN) && !defined(COM_INIT_CHECK_HOOK_ENABLED)
std::unique_ptr<win::ScopedCOMInitializer> com_initializer;
if (backward_compatibility_ == SchedulerBackwardCompatibility::INIT_COM_STA)
com_initializer = std::make_unique<win::ScopedCOMInitializer>();
#endif
while (!ShouldExit()) {
#if defined(OS_MACOSX)
mac::ScopedNSAutoreleasePool autorelease_pool;
#endif
UpdateThreadPriority(GetDesiredThreadPriority());
// Get the sequence containing the next task to execute.
scoped_refptr<Sequence> sequence = delegate_->GetWork(this);
if (!sequence) {
// Exit immediately if GetWork() resulted in detaching this worker.
if (ShouldExit())
break;
TRACE_EVENT_END0("task_scheduler", "SchedulerWorkerThread active");
delegate_->WaitForWork(&wake_up_event_);
TRACE_EVENT_BEGIN0("task_scheduler", "SchedulerWorkerThread active");
continue;
}
sequence =
task_tracker_->RunAndPopNextTask(std::move(sequence), delegate_.get());
delegate_->DidRunTask();
// Re-enqueue |sequence| if allowed by RunNextTask().
if (sequence)
delegate_->ReEnqueueSequence(std::move(sequence));
// Calling WakeUp() guarantees that this SchedulerWorker will run Tasks from
// Sequences 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 SchedulerWorker 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 (scheduler_worker_observer_)
scheduler_worker_observer_->OnSchedulerWorkerMainExit();
// 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("task_scheduler", "SchedulerWorkerThread active");
TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("task_scheduler_diagnostics"),
"SchedulerWorkerThread alive");
}
} // namespace internal
} // namespace base