// Copyright (c) 2012 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/run_loop.h" #include "base/bind.h" #include "base/callback.h" #include "base/lazy_instance.h" #include "base/message_loop/message_loop.h" #include "base/single_thread_task_runner.h" #include "base/threading/thread_local.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" namespace base { namespace { LazyInstance>::Leaky tls_delegate = LAZY_INSTANCE_INITIALIZER; // Runs |closure| immediately if this is called on |task_runner|, otherwise // forwards |closure| to it. void ProxyToTaskRunner(scoped_refptr task_runner, OnceClosure closure) { if (task_runner->RunsTasksInCurrentSequence()) { std::move(closure).Run(); return; } task_runner->PostTask(FROM_HERE, std::move(closure)); } } // 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_); // 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_) tls_delegate.Get().Set(nullptr); } bool RunLoop::Delegate::ShouldQuitWhenIdle() { return active_run_loops_.top()->quit_when_idle_received_; } // static void RunLoop::RegisterDelegateForCurrentThread(Delegate* delegate) { // Bind |delegate| to this thread. DCHECK(!delegate->bound_); DCHECK_CALLED_ON_VALID_THREAD(delegate->bound_thread_checker_); // There can only be one RunLoop::Delegate per thread. DCHECK(!tls_delegate.Get().Get()) << "Error: Multiple RunLoop::Delegates registered on the same thread.\n\n" "Hint: You perhaps instantiated a second " "MessageLoop/ScopedTaskEnvironment on a thread that already had one?"; tls_delegate.Get().Set(delegate); delegate->bound_ = true; } RunLoop::RunLoop(Type type) : delegate_(tls_delegate.Get().Get()), type_(type), origin_task_runner_(ThreadTaskRunnerHandle::Get()), weak_factory_(this) { DCHECK(delegate_) << "A RunLoop::Delegate must be bound to this thread prior " "to using RunLoop."; DCHECK(origin_task_runner_); } RunLoop::~RunLoop() { // TODO(gab): Fix bad usage and enable this check, http://crbug.com/715235. // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); } void RunLoop::Run() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!BeforeRun()) return; // It is okay to access this RunLoop from another sequence while Run() is // active as this RunLoop won't touch its state until after that returns (if // the RunLoop's state is accessed while processing Run(), it will be re-bound // to the accessing sequence for the remainder of that Run() -- accessing from // multiple sequences is still disallowed). DETACH_FROM_SEQUENCE(sequence_checker_); 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); // Rebind this RunLoop to the current thread after Run(). DETACH_FROM_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AfterRun(); } void RunLoop::RunUntilIdle() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); quit_when_idle_received_ = true; Run(); } void RunLoop::Quit() { // Thread-safe. // This can only be hit if run_loop->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, base::BindOnce(&RunLoop::Quit, Unretained(this))); return; } 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 run_loop->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, base::BindOnce(&RunLoop::QuitWhenIdle, Unretained(this))); return; } quit_when_idle_received_ = true; } base::Closure RunLoop::QuitClosure() { // TODO(gab): Fix bad usage and enable this check, http://crbug.com/715235. // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Need to use ProxyToTaskRunner() as WeakPtrs vended from // |weak_factory_| may only be accessed on |origin_task_runner_|. // TODO(gab): It feels wrong that QuitClosure() is bound to a WeakPtr. return base::Bind(&ProxyToTaskRunner, origin_task_runner_, base::Bind(&RunLoop::Quit, weak_factory_.GetWeakPtr())); } base::Closure RunLoop::QuitWhenIdleClosure() { // TODO(gab): Fix bad usage and enable this check, http://crbug.com/715235. // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Need to use ProxyToTaskRunner() as WeakPtrs vended from // |weak_factory_| may only be accessed on |origin_task_runner_|. // TODO(gab): It feels wrong that QuitWhenIdleClosure() is bound to a WeakPtr. return base::Bind( &ProxyToTaskRunner, origin_task_runner_, base::Bind(&RunLoop::QuitWhenIdle, weak_factory_.GetWeakPtr())); } // static bool RunLoop::IsRunningOnCurrentThread() { Delegate* delegate = tls_delegate.Get().Get(); return delegate && !delegate->active_run_loops_.empty(); } // static bool RunLoop::IsNestedOnCurrentThread() { Delegate* delegate = tls_delegate.Get().Get(); return delegate && delegate->active_run_loops_.size() > 1; } // static void RunLoop::AddNestingObserverOnCurrentThread(NestingObserver* observer) { Delegate* delegate = tls_delegate.Get().Get(); DCHECK(delegate); delegate->nesting_observers_.AddObserver(observer); } // static void RunLoop::RemoveNestingObserverOnCurrentThread(NestingObserver* observer) { Delegate* delegate = tls_delegate.Get().Get(); DCHECK(delegate); delegate->nesting_observers_.RemoveObserver(observer); } // static void RunLoop::QuitCurrentDeprecated() { DCHECK(IsRunningOnCurrentThread()); tls_delegate.Get().Get()->active_run_loops_.top()->Quit(); } // static void RunLoop::QuitCurrentWhenIdleDeprecated() { DCHECK(IsRunningOnCurrentThread()); tls_delegate.Get().Get()->active_run_loops_.top()->QuitWhenIdle(); } // static Closure RunLoop::QuitCurrentWhenIdleClosureDeprecated() { return Bind(&RunLoop::QuitCurrentWhenIdleDeprecated); } #if DCHECK_IS_ON() RunLoop::ScopedDisallowRunningForTesting::ScopedDisallowRunningForTesting() : current_delegate_(tls_delegate.Get().Get()), previous_run_allowance_( current_delegate_ ? current_delegate_->allow_running_for_testing_ : false) { if (current_delegate_) current_delegate_->allow_running_for_testing_ = false; } RunLoop::ScopedDisallowRunningForTesting::~ScopedDisallowRunningForTesting() { DCHECK_EQ(current_delegate_, tls_delegate.Get().Get()); 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. RunLoop::ScopedDisallowRunningForTesting::ScopedDisallowRunningForTesting() = default; RunLoop::ScopedDisallowRunningForTesting::~ScopedDisallowRunningForTesting() = default; #endif // DCHECK_IS_ON() 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 " "ScopedDisallowRunningForTesting. Hint: if mixing " "TestMockTimeTaskRunners on same thread, use TestMockTimeTaskRunner's " "API instead of RunLoop to drive individual task runners."; DCHECK(!run_called_); run_called_ = true; #endif // DCHECK_IS_ON() // Allow Quit to be called before Run. if (quit_called_) 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; auto& active_run_loops_ = delegate_->active_run_loops_; DCHECK_EQ(active_run_loops_.top(), this); active_run_loops_.pop(); RunLoop* previous_run_loop = active_run_loops_.empty() ? nullptr : active_run_loops_.top(); if (previous_run_loop) { for (auto& observer : delegate_->nesting_observers_) observer.OnExitNestedRunLoop(); } // Execute deferred Quit, if any: if (previous_run_loop && previous_run_loop->quit_called_) delegate_->Quit(); } } // namespace base