blob: b24a7dc681a68ed160cb457f1d36d45e834c8ae2 [file] [log] [blame]
// Copyright 2018 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/sequence_manager/thread_controller_with_message_pump_impl.h"
#include "base/auto_reset.h"
#include "base/message_loop/message_pump.h"
#include "base/time/tick_clock.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#if defined(OS_IOS)
#include "base/message_loop/message_pump_mac.h"
#elif defined(OS_ANDROID)
#include "base/message_loop/message_pump_android.h"
#endif
namespace base {
namespace sequence_manager {
namespace internal {
namespace {
// Returns |next_run_time| capped at 1 day from |lazy_now|. This is used to
// mitigate https://crbug.com/850450 where some platforms are unhappy with
// delays > 100,000,000 seconds. In practice, a diagnosis metric showed that no
// sleep > 1 hour ever completes (always interrupted by an earlier MessageLoop
// event) and 99% of completed sleeps are the ones scheduled for <= 1 second.
// Details @ https://crrev.com/c/1142589.
TimeTicks CapAtOneDay(TimeTicks next_run_time, LazyNow* lazy_now) {
return std::min(next_run_time, lazy_now->Now() + TimeDelta::FromDays(1));
}
} // namespace
ThreadControllerWithMessagePumpImpl::ThreadControllerWithMessagePumpImpl(
const SequenceManager::Settings& settings)
: associated_thread_(AssociatedThreadId::CreateUnbound()),
work_deduplicator_(associated_thread_),
#if DCHECK_IS_ON()
log_runloop_quit_and_quit_when_idle_(
settings.log_runloop_quit_and_quit_when_idle),
#endif
time_source_(settings.clock) {
}
ThreadControllerWithMessagePumpImpl::ThreadControllerWithMessagePumpImpl(
std::unique_ptr<MessagePump> message_pump,
const SequenceManager::Settings& settings)
: ThreadControllerWithMessagePumpImpl(settings) {
BindToCurrentThread(std::move(message_pump));
}
ThreadControllerWithMessagePumpImpl::~ThreadControllerWithMessagePumpImpl() {
// Destructors of MessagePump::Delegate and ThreadTaskRunnerHandle
// will do all the clean-up.
// ScopedSetSequenceLocalStorageMapForCurrentThread destructor will
// de-register the current thread as a sequence.
}
// static
std::unique_ptr<ThreadControllerWithMessagePumpImpl>
ThreadControllerWithMessagePumpImpl::CreateUnbound(
const SequenceManager::Settings& settings) {
return base::WrapUnique(new ThreadControllerWithMessagePumpImpl(settings));
}
ThreadControllerWithMessagePumpImpl::MainThreadOnly::MainThreadOnly() = default;
ThreadControllerWithMessagePumpImpl::MainThreadOnly::~MainThreadOnly() =
default;
void ThreadControllerWithMessagePumpImpl::SetSequencedTaskSource(
SequencedTaskSource* task_source) {
DCHECK(task_source);
DCHECK(!main_thread_only().task_source);
main_thread_only().task_source = task_source;
}
void ThreadControllerWithMessagePumpImpl::BindToCurrentThread(
std::unique_ptr<MessagePump> message_pump) {
associated_thread_->BindToCurrentThread();
pump_ = std::move(message_pump);
work_id_provider_ = WorkIdProvider::GetForCurrentThread();
RunLoop::RegisterDelegateForCurrentThread(this);
scoped_set_sequence_local_storage_map_for_current_thread_ = std::make_unique<
base::internal::ScopedSetSequenceLocalStorageMapForCurrentThread>(
&sequence_local_storage_map_);
{
base::internal::CheckedAutoLock task_runner_lock(task_runner_lock_);
if (task_runner_)
InitializeThreadTaskRunnerHandle();
}
if (work_deduplicator_.BindToCurrentThread() ==
ShouldScheduleWork::kScheduleImmediate) {
pump_->ScheduleWork();
}
}
void ThreadControllerWithMessagePumpImpl::SetWorkBatchSize(
int work_batch_size) {
DCHECK_GE(work_batch_size, 1);
main_thread_only().work_batch_size = work_batch_size;
}
void ThreadControllerWithMessagePumpImpl::SetTimerSlack(
TimerSlack timer_slack) {
DCHECK(RunsTasksInCurrentSequence());
pump_->SetTimerSlack(timer_slack);
}
void ThreadControllerWithMessagePumpImpl::WillQueueTask(
PendingTask* pending_task,
const char* task_queue_name) {
task_annotator_.WillQueueTask("SequenceManager PostTask", pending_task,
task_queue_name);
}
void ThreadControllerWithMessagePumpImpl::ScheduleWork() {
base::internal::CheckedLock::AssertNoLockHeldOnCurrentThread();
if (work_deduplicator_.OnWorkRequested() ==
ShouldScheduleWork::kScheduleImmediate) {
pump_->ScheduleWork();
}
}
void ThreadControllerWithMessagePumpImpl::SetNextDelayedDoWork(
LazyNow* lazy_now,
TimeTicks run_time) {
DCHECK_LT(lazy_now->Now(), run_time);
if (main_thread_only().next_delayed_do_work == run_time)
return;
// Cap at one day but remember the exact time for the above equality check on
// the next round.
main_thread_only().next_delayed_do_work = run_time;
run_time = CapAtOneDay(run_time, lazy_now);
// It's very rare for PostDelayedTask to be called outside of a Do(Some)Work
// in production, so most of the time this does nothing.
if (work_deduplicator_.OnDelayedWorkRequested() ==
ShouldScheduleWork::kScheduleImmediate) {
// |pump_| can't be null as all postTasks are cross-thread before binding,
// and delayed cross-thread postTasks do the thread hop through an immediate
// task.
pump_->ScheduleDelayedWork(run_time);
}
}
const TickClock* ThreadControllerWithMessagePumpImpl::GetClock() {
return time_source_;
}
bool ThreadControllerWithMessagePumpImpl::RunsTasksInCurrentSequence() {
return associated_thread_->IsBoundToCurrentThread();
}
void ThreadControllerWithMessagePumpImpl::SetDefaultTaskRunner(
scoped_refptr<SingleThreadTaskRunner> task_runner) {
base::internal::CheckedAutoLock lock(task_runner_lock_);
task_runner_ = task_runner;
if (associated_thread_->IsBound()) {
DCHECK(associated_thread_->IsBoundToCurrentThread());
// Thread task runner handle will be created in BindToCurrentThread().
InitializeThreadTaskRunnerHandle();
}
}
void ThreadControllerWithMessagePumpImpl::InitializeThreadTaskRunnerHandle() {
// Only one ThreadTaskRunnerHandle can exist at any time,
// so reset the old one.
main_thread_only().thread_task_runner_handle.reset();
main_thread_only().thread_task_runner_handle =
std::make_unique<ThreadTaskRunnerHandle>(task_runner_);
}
scoped_refptr<SingleThreadTaskRunner>
ThreadControllerWithMessagePumpImpl::GetDefaultTaskRunner() {
base::internal::CheckedAutoLock lock(task_runner_lock_);
return task_runner_;
}
void ThreadControllerWithMessagePumpImpl::RestoreDefaultTaskRunner() {
// There's no default task runner unlike with the MessageLoop.
main_thread_only().thread_task_runner_handle.reset();
}
void ThreadControllerWithMessagePumpImpl::AddNestingObserver(
RunLoop::NestingObserver* observer) {
DCHECK(!main_thread_only().nesting_observer);
DCHECK(observer);
main_thread_only().nesting_observer = observer;
RunLoop::AddNestingObserverOnCurrentThread(this);
}
void ThreadControllerWithMessagePumpImpl::RemoveNestingObserver(
RunLoop::NestingObserver* observer) {
DCHECK_EQ(main_thread_only().nesting_observer, observer);
main_thread_only().nesting_observer = nullptr;
RunLoop::RemoveNestingObserverOnCurrentThread(this);
}
const scoped_refptr<AssociatedThreadId>&
ThreadControllerWithMessagePumpImpl::GetAssociatedThread() const {
return associated_thread_;
}
void ThreadControllerWithMessagePumpImpl::BeforeDoInternalWork() {
work_id_provider_->IncrementWorkId();
}
MessagePump::Delegate::NextWorkInfo
ThreadControllerWithMessagePumpImpl::DoSomeWork() {
work_deduplicator_.OnWorkStarted();
bool ran_task = false; // Unused.
LazyNow continuation_lazy_now(time_source_);
TimeDelta delay_till_next_task =
DoWorkImpl(&continuation_lazy_now, &ran_task);
// Schedule a continuation.
WorkDeduplicator::NextTask next_task =
delay_till_next_task.is_zero() ? WorkDeduplicator::NextTask::kIsImmediate
: WorkDeduplicator::NextTask::kIsDelayed;
if (work_deduplicator_.DidCheckForMoreWork(next_task) ==
ShouldScheduleWork::kScheduleImmediate) {
// Need to run new work immediately, but due to the contract of DoSomeWork
// we only need to return a null TimeTicks to ensure that happens.
return MessagePump::Delegate::NextWorkInfo();
}
// While the math below would saturate when |delay_till_next_task.is_max()|;
// special-casing here avoids unnecessarily sampling Now() when out of work.
if (delay_till_next_task.is_max()) {
main_thread_only().next_delayed_do_work = TimeTicks::Max();
return {TimeTicks::Max()};
}
// The MessagePump will schedule the delay on our behalf, so we need to update
// |main_thread_only().next_delayed_do_work|.
// TODO(gab, alexclarke): Replace DelayTillNextTask() with NextTaskTime() to
// avoid converting back-and-forth between TimeTicks and TimeDelta.
main_thread_only().next_delayed_do_work =
continuation_lazy_now.Now() + delay_till_next_task;
// Don't request a run time past |main_thread_only().quit_runloop_after|.
if (main_thread_only().next_delayed_do_work >
main_thread_only().quit_runloop_after) {
main_thread_only().next_delayed_do_work =
main_thread_only().quit_runloop_after;
// If we've passed |quit_runloop_after| there's no more work to do.
if (continuation_lazy_now.Now() >= main_thread_only().quit_runloop_after)
return {TimeTicks::Max()};
}
return {CapAtOneDay(main_thread_only().next_delayed_do_work,
&continuation_lazy_now),
continuation_lazy_now.Now()};
}
bool ThreadControllerWithMessagePumpImpl::DoWork() {
work_deduplicator_.OnWorkStarted();
bool ran_task = false;
LazyNow continuation_lazy_now(time_source_);
TimeDelta delay_till_next_task =
DoWorkImpl(&continuation_lazy_now, &ran_task);
// Schedule a continuation.
// TODO(altimin, gab): Make this more efficient by merging DoWork
// and DoDelayedWork and allowing returning base::TimeTicks() when we have
// immediate work.
if (delay_till_next_task.is_zero()) {
// Need to run new work immediately, but due to the contract of DoWork we
// only need to return true to ensure that happens.
ran_task = true;
}
// DoDelayedWork always follows DoWork, (although the inverse is not true) so
// we don't need to schedule a delayed wakeup here.
WorkDeduplicator::NextTask next_task =
ran_task ? WorkDeduplicator::NextTask::kIsImmediate
: WorkDeduplicator::NextTask::kIsDelayed;
return work_deduplicator_.DidCheckForMoreWork(next_task) ==
ShouldScheduleWork::kScheduleImmediate;
}
bool ThreadControllerWithMessagePumpImpl::DoDelayedWork(
TimeTicks* next_run_time) {
work_deduplicator_.OnDelayedWorkStarted();
LazyNow continuation_lazy_now(time_source_);
bool ran_task = false;
WorkDeduplicator::NextTask next_task = WorkDeduplicator::NextTask::kIsDelayed;
TimeDelta delay_till_next_task =
DoWorkImpl(&continuation_lazy_now, &ran_task);
// Schedule a continuation.
// TODO(altimin, gab): Make this more efficient by merging DoWork
// and DoDelayedWork and allowing returning base::TimeTicks() when we have
// immediate work.
if (delay_till_next_task.is_zero()) {
*next_run_time = TimeTicks();
next_task = WorkDeduplicator::NextTask::kIsImmediate;
} else if (delay_till_next_task != TimeDelta::Max()) {
// Cancels any previously scheduled delayed wake-ups.
*next_run_time =
CapAtOneDay(delay_till_next_task + continuation_lazy_now.Now(),
&continuation_lazy_now);
// Don't request a run time past |main_thread_only().quit_runloop_after|.
if (*next_run_time > main_thread_only().quit_runloop_after) {
*next_run_time = main_thread_only().quit_runloop_after;
// If we've passed |quit_runloop_after| there's no more work to do.
if (continuation_lazy_now.Now() >= main_thread_only().quit_runloop_after)
*next_run_time = TimeTicks();
}
// The MessagePump will call ScheduleDelayedWork on our behalf, so we need
// to update |main_thread_only().next_delayed_do_work|.
main_thread_only().next_delayed_do_work = *next_run_time;
} else {
// There's no more work to do.
*next_run_time = TimeTicks();
}
// Figure out if we need to post an immediate continuation.
if (work_deduplicator_.OnDelayedWorkEnded(next_task) ==
ShouldScheduleWork::kScheduleImmediate) {
pump_->ScheduleWork();
}
return ran_task;
}
TimeDelta ThreadControllerWithMessagePumpImpl::DoWorkImpl(
LazyNow* continuation_lazy_now,
bool* ran_task) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
"ThreadControllerImpl::DoWork");
if (!main_thread_only().task_execution_allowed) {
if (main_thread_only().quit_runloop_after == TimeTicks::Max())
return TimeDelta::Max();
return main_thread_only().quit_runloop_after - continuation_lazy_now->Now();
}
DCHECK(main_thread_only().task_source);
for (int i = 0; i < main_thread_only().work_batch_size; i++) {
Task* task = main_thread_only().task_source->SelectNextTask();
if (!task)
break;
// Execute the task and assume the worst: it is probably not reentrant.
main_thread_only().task_execution_allowed = false;
work_id_provider_->IncrementWorkId();
// Trace-parsing tools (DevTools, Lighthouse, etc) consume this event
// to determine long tasks.
// The event scope must span across DidRunTask call below to make sure
// it covers RunMicrotasks event.
// See https://crbug.com/681863 and https://crbug.com/874982
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "RunTask");
{
// Trace events should finish before we call DidRunTask to ensure that
// SequenceManager trace events do not interfere with them.
TRACE_TASK_EXECUTION("ThreadControllerImpl::RunTask", *task);
task_annotator_.RunTask("SequenceManager RunTask", task);
}
#if DCHECK_IS_ON()
if (log_runloop_quit_and_quit_when_idle_ && !quit_when_idle_requested_ &&
ShouldQuitWhenIdle()) {
DVLOG(1) << "ThreadControllerWithMessagePumpImpl::QuitWhenIdle";
quit_when_idle_requested_ = true;
}
#endif
*ran_task = true;
main_thread_only().task_execution_allowed = true;
main_thread_only().task_source->DidRunTask();
// When Quit() is called we must stop running the batch because the caller
// expects per-task granularity.
if (main_thread_only().quit_pending)
break;
}
if (main_thread_only().quit_pending)
return TimeDelta::Max();
work_deduplicator_.WillCheckForMoreWork();
TimeDelta do_work_delay =
main_thread_only().task_source->DelayTillNextTask(continuation_lazy_now);
DCHECK_GE(do_work_delay, TimeDelta());
return do_work_delay;
}
bool ThreadControllerWithMessagePumpImpl::DoIdleWork() {
TRACE_EVENT0("sequence_manager", "SequenceManager::DoIdleWork");
work_id_provider_->IncrementWorkId();
#if defined(OS_WIN)
bool need_high_res_mode =
main_thread_only().task_source->HasPendingHighResolutionTasks();
if (main_thread_only().in_high_res_mode != need_high_res_mode) {
// On Windows we activate the high resolution timer so that the wait
// _if_ triggered by the timer happens with good resolution. If we don't
// do this the default resolution is 15ms which might not be acceptable
// for some tasks.
main_thread_only().in_high_res_mode = need_high_res_mode;
Time::ActivateHighResolutionTimer(need_high_res_mode);
}
#endif // defined(OS_WIN)
if (main_thread_only().task_source->OnSystemIdle()) {
// The OnSystemIdle() callback resulted in more immediate work, so schedule
// a DoWork callback. For some message pumps returning true from here is
// sufficient to do that but not on mac.
pump_->ScheduleWork();
return false;
}
// Check if any runloop timeout has expired.
if (main_thread_only().quit_runloop_after != TimeTicks::Max() &&
main_thread_only().quit_runloop_after <= time_source_->NowTicks()) {
Quit();
return false;
}
// RunLoop::Delegate knows whether we called Run() or RunUntilIdle().
if (ShouldQuitWhenIdle())
Quit();
return false;
}
void ThreadControllerWithMessagePumpImpl::Run(bool application_tasks_allowed,
TimeDelta timeout) {
DCHECK(RunsTasksInCurrentSequence());
// RunLoops can be nested so we need to restore the previous value of
// |quit_runloop_after| upon exit. NB we could use saturated arithmetic here
// but don't because we have some tests which assert the number of calls to
// Now.
AutoReset<TimeTicks> quit_runloop_after(
&main_thread_only().quit_runloop_after,
(timeout == TimeDelta::Max()) ? TimeTicks::Max()
: time_source_->NowTicks() + timeout);
#if DCHECK_IS_ON()
AutoReset<bool> quit_when_idle_requested(&quit_when_idle_requested_, false);
#endif
// Quit may have been called outside of a Run(), so |quit_pending| might be
// true here. We can't use InTopLevelDoWork() in Quit() as this call may be
// outside top-level DoWork but still in Run().
main_thread_only().quit_pending = false;
main_thread_only().runloop_count++;
if (application_tasks_allowed && !main_thread_only().task_execution_allowed) {
// Allow nested task execution as explicitly requested.
DCHECK(RunLoop::IsNestedOnCurrentThread());
main_thread_only().task_execution_allowed = true;
pump_->Run(this);
main_thread_only().task_execution_allowed = false;
} else {
pump_->Run(this);
}
#if DCHECK_IS_ON()
if (log_runloop_quit_and_quit_when_idle_)
DVLOG(1) << "ThreadControllerWithMessagePumpImpl::Quit";
#endif
main_thread_only().runloop_count--;
main_thread_only().quit_pending = false;
}
void ThreadControllerWithMessagePumpImpl::OnBeginNestedRunLoop() {
// We don't need to ScheduleWork here! That's because the call to pump_->Run()
// above, which is always called for RunLoop().Run(), guarantees a call to
// Do(Some)Work on all platforms.
if (main_thread_only().nesting_observer)
main_thread_only().nesting_observer->OnBeginNestedRunLoop();
}
void ThreadControllerWithMessagePumpImpl::OnExitNestedRunLoop() {
if (main_thread_only().nesting_observer)
main_thread_only().nesting_observer->OnExitNestedRunLoop();
}
void ThreadControllerWithMessagePumpImpl::Quit() {
DCHECK(RunsTasksInCurrentSequence());
// Interrupt a batch of work.
main_thread_only().quit_pending = true;
// If we're in a nested RunLoop, continuation will be posted if necessary.
pump_->Quit();
}
void ThreadControllerWithMessagePumpImpl::EnsureWorkScheduled() {
if (work_deduplicator_.OnWorkRequested() ==
ShouldScheduleWork::kScheduleImmediate)
pump_->ScheduleWork();
}
void ThreadControllerWithMessagePumpImpl::SetTaskExecutionAllowed(
bool allowed) {
if (allowed) {
// We need to schedule work unconditionally because we might be about to
// enter an OS level nested message loop. Unlike a RunLoop().Run() we don't
// get a call to Do(Some)Work on entering for free.
work_deduplicator_.OnWorkRequested(); // Set the pending DoWork flag.
pump_->ScheduleWork();
} else {
// We've (probably) just left an OS level nested message loop. Make sure a
// subsequent PostTask within the same Task doesn't ScheduleWork with the
// pump (this will be done anyway when the task exits).
work_deduplicator_.OnWorkStarted();
}
main_thread_only().task_execution_allowed = allowed;
}
bool ThreadControllerWithMessagePumpImpl::IsTaskExecutionAllowed() const {
return main_thread_only().task_execution_allowed;
}
MessagePump* ThreadControllerWithMessagePumpImpl::GetBoundMessagePump() const {
return pump_.get();
}
#if defined(OS_IOS)
void ThreadControllerWithMessagePumpImpl::AttachToMessagePump() {
static_cast<MessagePumpCFRunLoopBase*>(pump_.get())->Attach(this);
}
void ThreadControllerWithMessagePumpImpl::DetachFromMessagePump() {
static_cast<MessagePumpCFRunLoopBase*>(pump_.get())->Detach();
}
#elif defined(OS_ANDROID)
void ThreadControllerWithMessagePumpImpl::AttachToMessagePump() {
static_cast<MessagePumpForUI*>(pump_.get())->Attach(this);
}
#endif
bool ThreadControllerWithMessagePumpImpl::ShouldQuitRunLoopWhenIdle() {
if (main_thread_only().runloop_count == 0)
return false;
// It's only safe to call ShouldQuitWhenIdle() when in a RunLoop.
return ShouldQuitWhenIdle();
}
} // namespace internal
} // namespace sequence_manager
} // namespace base