blob: 79e9b91430cab8d73d0deff83b9b36f671f63c91 [file] [log] [blame]
// Copyright 2012 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/run_loop.h"
#include "base/cancelable_callback.h"
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/observer_list.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/base_tracing.h"
#include "build/build_config.h"
#include "third_party/abseil-cpp/absl/base/attributes.h"
#if defined(STARBOARD)
#include <pthread.h>
#include "starboard/thread.h"
#endif
namespace base {
namespace {
#if defined(STARBOARD)
ABSL_CONST_INIT pthread_once_t s_once_delegate_flag = PTHREAD_ONCE_INIT;
ABSL_CONST_INIT pthread_key_t s_thread_local_delegate_key = 0;
void InitThreadLocalDelegateKey() {
int res = pthread_key_create(&s_thread_local_delegate_key , NULL);
DCHECK(res == 0);
}
void EnsureThreadLocalDelegateKeyInited() {
pthread_once(&s_once_delegate_flag, InitThreadLocalDelegateKey);
}
RunLoop::Delegate* GetDelegate() {
EnsureThreadLocalDelegateKeyInited();
return static_cast<RunLoop::Delegate*>(
pthread_getspecific(s_thread_local_delegate_key));
}
ABSL_CONST_INIT pthread_once_t s_once_timeout_flag = PTHREAD_ONCE_INIT;
ABSL_CONST_INIT pthread_key_t s_thread_local_timeout_key = 0;
void InitThreadLocalTimeoutKey() {
int res = pthread_key_create(&s_thread_local_timeout_key, NULL);
DCHECK(res == 0);
}
void EnsureThreadLocalTimeoutKeyInited() {
pthread_once(&s_once_timeout_flag, InitThreadLocalTimeoutKey);
}
const RunLoop::RunLoopTimeout* GetRunLoopTimeout() {
EnsureThreadLocalTimeoutKeyInited();
return static_cast<const RunLoop::RunLoopTimeout*>(
pthread_getspecific(s_thread_local_timeout_key));
}
#else
ABSL_CONST_INIT thread_local RunLoop::Delegate* delegate = nullptr;
ABSL_CONST_INIT thread_local const RunLoop::RunLoopTimeout* run_loop_timeout =
nullptr;
#endif
// Runs |closure| immediately if this is called on |task_runner|, otherwise
// forwards |closure| to it.
void ProxyToTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner,
OnceClosure closure) {
if (task_runner->RunsTasksInCurrentSequence()) {
std::move(closure).Run();
return;
}
task_runner->PostTask(FROM_HERE, std::move(closure));
}
void OnRunLoopTimeout(RunLoop* run_loop,
const Location& location,
OnceCallback<void(const Location&)> on_timeout) {
run_loop->Quit();
std::move(on_timeout).Run(location);
}
} // namespace
RunLoop::Delegate::Delegate() {
// The Delegate can be created on another thread. It is only bound in
// RegisterDelegateForCurrentThread().
DETACH_FROM_THREAD(bound_thread_checker_);
}
RunLoop::Delegate::~Delegate() {
DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
DCHECK(active_run_loops_.empty());
// A RunLoop::Delegate may be destroyed before it is bound, if so it may still
// be on its creation thread (e.g. a Thread that fails to start) and
// shouldn't disrupt that thread's state.
if (bound_) {
#if defined(STARBOARD)
DCHECK_EQ(this, GetDelegate());
EnsureThreadLocalDelegateKeyInited();
pthread_setspecific(s_thread_local_delegate_key, nullptr);
#else
DCHECK_EQ(this, delegate);
delegate = nullptr;
#endif
}
}
bool RunLoop::Delegate::ShouldQuitWhenIdle() {
const auto* top_loop = active_run_loops_.top();
if (top_loop->quit_when_idle_) {
TRACE_EVENT_WITH_FLOW0("toplevel.flow", "RunLoop_ExitedOnIdle",
TRACE_ID_LOCAL(top_loop), TRACE_EVENT_FLAG_FLOW_IN);
return true;
}
return false;
}
// static
void RunLoop::RegisterDelegateForCurrentThread(Delegate* new_delegate) {
// Bind |delegate| to this thread.
DCHECK(!new_delegate->bound_);
DCHECK_CALLED_ON_VALID_THREAD(new_delegate->bound_thread_checker_);
// There can only be one RunLoop::Delegate per thread.
#if defined(STARBOARD)
DCHECK(!GetDelegate())
#else
DCHECK(!delegate)
#endif
<< "Error: Multiple RunLoop::Delegates registered on the same thread.\n\n"
"Hint: You perhaps instantiated a second "
"MessageLoop/TaskEnvironment on a thread that already had one?";
#if defined(STARBOARD)
EnsureThreadLocalDelegateKeyInited();
pthread_setspecific(s_thread_local_delegate_key, new_delegate);
new_delegate->bound_ = true;
#else
delegate = new_delegate;
delegate->bound_ = true;
#endif
}
RunLoop::RunLoop(Type type)
#if defined(STARBOARD)
: delegate_(GetDelegate()),
#else
: delegate_(delegate),
#endif
type_(type),
origin_task_runner_(SingleThreadTaskRunner::GetCurrentDefault()) {
DCHECK(delegate_) << "A RunLoop::Delegate must be bound to this thread prior "
"to using RunLoop.";
DCHECK(origin_task_runner_);
}
RunLoop::~RunLoop() {
// ~RunLoop() must happen-after the RunLoop is done running but it doesn't
// have to be on |sequence_checker_| (it usually is but sometimes it can be a
// member of a RefCountedThreadSafe object and be destroyed on another thread
// after being quit).
DCHECK(!running_);
}
void RunLoop::Run(const Location& location) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// "test" tracing category is used here because in regular scenarios RunLoop
// trace events are not useful (each process normally has one RunLoop covering
// its entire lifetime) and might be confusing (they make idle processes look
// non-idle). In tests, however, creating a RunLoop is a frequent and an
// explicit action making this trace event very useful.
TRACE_EVENT("test", "RunLoop::Run", "location", location);
if (!BeforeRun())
return;
// If there is a RunLoopTimeout active then set the timeout.
// TODO(crbug.com/905412): Use real-time for Run() timeouts so that they
// can be applied even in tests which mock TimeTicks::Now().
CancelableOnceClosure cancelable_timeout;
const RunLoopTimeout* run_timeout = GetTimeoutForCurrentThread();
if (run_timeout) {
cancelable_timeout.Reset(BindOnce(&OnRunLoopTimeout, Unretained(this),
location, run_timeout->on_timeout));
origin_task_runner_->PostDelayedTask(
FROM_HERE, cancelable_timeout.callback(), run_timeout->timeout);
}
DCHECK_EQ(this, delegate_->active_run_loops_.top());
const bool application_tasks_allowed =
delegate_->active_run_loops_.size() == 1U ||
type_ == Type::kNestableTasksAllowed;
delegate_->Run(application_tasks_allowed, TimeDelta::Max());
AfterRun();
}
void RunLoop::RunUntilIdle() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
quit_when_idle_ = true;
Run();
if (!AnyQuitCalled()) {
quit_when_idle_ = false;
#if DCHECK_IS_ON()
run_allowed_ = true;
#endif
}
}
void RunLoop::Quit() {
// Thread-safe.
// This can only be hit if RunLoop::Quit() is called directly (QuitClosure()
// proxies through ProxyToTaskRunner() as it can only deref its WeakPtr on
// |origin_task_runner_|).
if (!origin_task_runner_->RunsTasksInCurrentSequence()) {
origin_task_runner_->PostTask(FROM_HERE,
BindOnce(&RunLoop::Quit, Unretained(this)));
return;
}
// While Quit() is an "OUT" call to reach one of the quit-states ("IN"),
// OUT|IN is used to visually link multiple Quit*() together which can help
// when debugging flaky tests.
TRACE_EVENT_WITH_FLOW0("toplevel.flow", "RunLoop::Quit", TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_OUT | TRACE_EVENT_FLAG_FLOW_IN);
quit_called_ = true;
if (running_ && delegate_->active_run_loops_.top() == this) {
// This is the inner-most RunLoop, so quit now.
delegate_->Quit();
}
}
void RunLoop::QuitWhenIdle() {
// Thread-safe.
// This can only be hit if RunLoop::QuitWhenIdle() is called directly
// (QuitWhenIdleClosure() proxies through ProxyToTaskRunner() as it can only
// deref its WeakPtr on |origin_task_runner_|).
if (!origin_task_runner_->RunsTasksInCurrentSequence()) {
origin_task_runner_->PostTask(
FROM_HERE, BindOnce(&RunLoop::QuitWhenIdle, Unretained(this)));
return;
}
// OUT|IN as in Quit() to link all Quit*() together should there be multiple.
TRACE_EVENT_WITH_FLOW0("toplevel.flow", "RunLoop::QuitWhenIdle",
TRACE_ID_LOCAL(this),
TRACE_EVENT_FLAG_FLOW_OUT | TRACE_EVENT_FLAG_FLOW_IN);
quit_when_idle_ = true;
quit_when_idle_called_ = true;
}
RepeatingClosure RunLoop::QuitClosure() {
// Obtaining the QuitClosure() is not thread-safe; either obtain the
// QuitClosure() from the owning thread before Run() or invoke Quit() directly
// (which is thread-safe).
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
allow_quit_current_deprecated_ = false;
return BindRepeating(
&ProxyToTaskRunner, origin_task_runner_,
BindRepeating(&RunLoop::Quit, weak_factory_.GetWeakPtr()));
}
RepeatingClosure RunLoop::QuitWhenIdleClosure() {
// Obtaining the QuitWhenIdleClosure() is not thread-safe; either obtain the
// QuitWhenIdleClosure() from the owning thread before Run() or invoke
// QuitWhenIdle() directly (which is thread-safe).
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
allow_quit_current_deprecated_ = false;
return BindRepeating(
&ProxyToTaskRunner, origin_task_runner_,
BindRepeating(&RunLoop::QuitWhenIdle, weak_factory_.GetWeakPtr()));
}
bool RunLoop::AnyQuitCalled() {
return quit_called_ || quit_when_idle_called_;
}
// static
bool RunLoop::IsRunningOnCurrentThread() {
#if defined(STARBOARD)
auto delegate = GetDelegate();
#endif
return delegate && !delegate->active_run_loops_.empty();
}
// static
bool RunLoop::IsNestedOnCurrentThread() {
#if defined(STARBOARD)
auto delegate = GetDelegate();
#endif
return delegate && delegate->active_run_loops_.size() > 1;
}
// static
void RunLoop::AddNestingObserverOnCurrentThread(NestingObserver* observer) {
#if defined(STARBOARD)
auto delegate = GetDelegate();
#endif
DCHECK(delegate);
delegate->nesting_observers_.AddObserver(observer);
}
// static
void RunLoop::RemoveNestingObserverOnCurrentThread(NestingObserver* observer) {
#if defined(STARBOARD)
auto delegate = GetDelegate();
#endif
DCHECK(delegate);
delegate->nesting_observers_.RemoveObserver(observer);
}
// static
void RunLoop::QuitCurrentDeprecated() {
DCHECK(IsRunningOnCurrentThread());
#if defined(STARBOARD)
auto delegate = GetDelegate();
#endif
DCHECK(delegate->active_run_loops_.top()->allow_quit_current_deprecated_)
<< "Please migrate off QuitCurrentDeprecated(), e.g. to QuitClosure().";
delegate->active_run_loops_.top()->Quit();
}
// static
void RunLoop::QuitCurrentWhenIdleDeprecated() {
DCHECK(IsRunningOnCurrentThread());
#if defined(STARBOARD)
auto delegate = GetDelegate();
#endif
DCHECK(delegate->active_run_loops_.top()->allow_quit_current_deprecated_)
<< "Please migrate off QuitCurrentWhenIdleDeprecated(), e.g. to "
"QuitWhenIdleClosure().";
delegate->active_run_loops_.top()->QuitWhenIdle();
}
// static
RepeatingClosure RunLoop::QuitCurrentWhenIdleClosureDeprecated() {
// TODO(844016): Fix callsites and enable this check, or remove the API.
// DCHECK(delegate->active_run_loops_.top()->allow_quit_current_deprecated_)
// << "Please migrate off QuitCurrentWhenIdleClosureDeprecated(), e.g to "
// "QuitWhenIdleClosure().";
return BindRepeating(&RunLoop::QuitCurrentWhenIdleDeprecated);
}
#if DCHECK_IS_ON()
ScopedDisallowRunningRunLoop::ScopedDisallowRunningRunLoop()
#if defined(STARBOARD)
: current_delegate_(GetDelegate()),
#else
: current_delegate_(delegate),
#endif
previous_run_allowance_(current_delegate_ &&
current_delegate_->allow_running_for_testing_) {
if (current_delegate_)
current_delegate_->allow_running_for_testing_ = false;
}
ScopedDisallowRunningRunLoop::~ScopedDisallowRunningRunLoop() {
#if defined(STARBOARD)
DCHECK_EQ(current_delegate_, GetDelegate());
#else
DCHECK_EQ(current_delegate_, delegate);
#endif
if (current_delegate_)
current_delegate_->allow_running_for_testing_ = previous_run_allowance_;
}
#else // DCHECK_IS_ON()
// Defined out of line so that the compiler doesn't inline these and realize
// the scope has no effect and then throws an "unused variable" warning in
// non-dcheck builds.
ScopedDisallowRunningRunLoop::ScopedDisallowRunningRunLoop() = default;
ScopedDisallowRunningRunLoop::~ScopedDisallowRunningRunLoop() = default;
#endif // DCHECK_IS_ON()
RunLoop::RunLoopTimeout::RunLoopTimeout() = default;
RunLoop::RunLoopTimeout::~RunLoopTimeout() = default;
// static
void RunLoop::SetTimeoutForCurrentThread(const RunLoopTimeout* timeout) {
#if defined(STARBOARD)
EnsureThreadLocalTimeoutKeyInited();
pthread_setspecific(s_thread_local_timeout_key, const_cast<RunLoopTimeout*>(timeout));
#else
run_loop_timeout = timeout;
#endif
}
// static
const RunLoop::RunLoopTimeout* RunLoop::GetTimeoutForCurrentThread() {
#if defined(STARBOARD)
return GetRunLoopTimeout();
#else
// Workaround false-positive MSAN use-of-uninitialized-value on
// thread_local storage for loaded libraries:
// https://github.com/google/sanitizers/issues/1265
MSAN_UNPOISON(&run_loop_timeout, sizeof(RunLoopTimeout*));
return run_loop_timeout;
#endif
}
bool RunLoop::BeforeRun() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
DCHECK(delegate_->allow_running_for_testing_)
<< "RunLoop::Run() isn't allowed in the scope of a "
"ScopedDisallowRunningRunLoop. Hint: if mixing "
"TestMockTimeTaskRunners on same thread, use TestMockTimeTaskRunner's "
"API instead of RunLoop to drive individual task runners.";
DCHECK(run_allowed_);
run_allowed_ = false;
#endif // DCHECK_IS_ON()
// Allow Quit to be called before Run.
if (quit_called_) {
TRACE_EVENT_WITH_FLOW0("toplevel.flow", "RunLoop_ExitedEarly",
TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN);
return false;
}
auto& active_run_loops = delegate_->active_run_loops_;
active_run_loops.push(this);
const bool is_nested = active_run_loops.size() > 1;
if (is_nested) {
for (auto& observer : delegate_->nesting_observers_)
observer.OnBeginNestedRunLoop();
if (type_ == Type::kNestableTasksAllowed)
delegate_->EnsureWorkScheduled();
}
running_ = true;
return true;
}
void RunLoop::AfterRun() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
running_ = false;
TRACE_EVENT_WITH_FLOW0("toplevel.flow", "RunLoop_Exited",
TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN);
auto& active_run_loops = delegate_->active_run_loops_;
DCHECK_EQ(active_run_loops.top(), this);
active_run_loops.pop();
// Exiting a nested RunLoop?
if (!active_run_loops.empty()) {
for (auto& observer : delegate_->nesting_observers_)
observer.OnExitNestedRunLoop();
// Execute deferred Quit, if any:
if (active_run_loops.top()->quit_called_)
delegate_->Quit();
}
}
} // namespace base