| // 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_config.h" | |
| namespace base { | |
| namespace { | |
| LazyInstance<ThreadLocalPointer<RunLoop::Delegate>>::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<SequencedTaskRunner> 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 |