| // Copyright 2014 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 "mojo/public/cpp/utility/run_loop.h" |
| |
| #include <assert.h> |
| #include <mojo/macros.h> |
| #include <pthread.h> |
| |
| #include <algorithm> |
| #include <limits> |
| #include <utility> |
| #include <vector> |
| |
| #include "mojo/public/cpp/system/time.h" |
| #include "mojo/public/cpp/system/wait.h" |
| #include "mojo/public/cpp/utility/run_loop_handler.h" |
| |
| namespace mojo { |
| namespace { |
| |
| // The initial and maximum number of results that we'll accept from |
| // |WaitSetWait()|. TODO(vtl): I just made up these numbers. |
| constexpr uint32_t kInitialWaitSetNumResults = 16u; |
| constexpr uint32_t kMaximumWaitSetNumResults = 256u; |
| |
| pthread_key_t g_current_run_loop_key; |
| |
| // Ensures that the "current run loop" functionality is available (i.e., that we |
| // have a TLS slot). |
| void InitializeCurrentRunLoopIfNecessary() { |
| static pthread_once_t current_run_loop_key_once = PTHREAD_ONCE_INIT; |
| int error = pthread_once(¤t_run_loop_key_once, []() { |
| int error = pthread_key_create(&g_current_run_loop_key, nullptr); |
| MOJO_ALLOW_UNUSED_LOCAL(error); |
| assert(!error); |
| }); |
| MOJO_ALLOW_UNUSED_LOCAL(error); |
| assert(!error); |
| } |
| |
| void SetCurrentRunLoop(RunLoop* run_loop) { |
| InitializeCurrentRunLoopIfNecessary(); |
| |
| int error = pthread_setspecific(g_current_run_loop_key, run_loop); |
| MOJO_ALLOW_UNUSED_LOCAL(error); |
| assert(!error); |
| } |
| |
| } // namespace |
| |
| RunLoop::DelayedTaskInfo::DelayedTaskInfo(RunLoopHandler::Id id, |
| const Closure& task, |
| MojoTimeTicks absolute_run_time) |
| : id(id), task(task), absolute_run_time(absolute_run_time) {} |
| |
| RunLoop::DelayedTaskInfo::~DelayedTaskInfo() {} |
| |
| struct RunLoop::RunState { |
| bool should_quit = false; |
| uint32_t results_size = kInitialWaitSetNumResults; |
| std::vector<MojoWaitSetResult> results; |
| }; |
| |
| // static |
| constexpr MojoTimeTicks RunLoop::kInvalidTimeTicks; |
| |
| RunLoop::RunLoop() { |
| MojoResult result = CreateWaitSet(nullptr, &wait_set_); |
| MOJO_ALLOW_UNUSED_LOCAL(result); |
| assert(result == MOJO_RESULT_OK); |
| assert(wait_set_.is_valid()); |
| |
| assert(!current()); |
| SetCurrentRunLoop(this); |
| } |
| |
| RunLoop::~RunLoop() { |
| assert(current() == this); |
| |
| // Notify all handlers that they've been aborted. Note that handlers could |
| // conceivably call |RemoveHandler()| (which would be a bit shady, admittedly, |
| // even if we handle it correctly). (They could also call |AddHandler()|, |
| // which would be even shadier; we handle this "correctly", but we may still |
| // end up looping infinitely in that case.) |
| while (!handlers_.empty()) { |
| auto it = handlers_.begin(); |
| auto handler = it->second.handler; |
| auto id = it->first; |
| handlers_.erase(it); |
| handler->OnHandleError(id, MOJO_RESULT_ABORTED); |
| } |
| |
| SetCurrentRunLoop(nullptr); |
| } |
| |
| // static |
| RunLoop* RunLoop::current() { |
| InitializeCurrentRunLoopIfNecessary(); |
| return static_cast<RunLoop*>(pthread_getspecific(g_current_run_loop_key)); |
| } |
| |
| RunLoopHandler::Id RunLoop::AddHandler(RunLoopHandler* handler, |
| const Handle& handle, |
| MojoHandleSignals handle_signals, |
| MojoDeadline deadline) { |
| assert(current() == this); |
| assert(handler); |
| assert(handle.is_valid()); |
| |
| // Generate a |RunLoopHandler::Id|. |
| auto id = next_id_++; |
| |
| // Calculate the absolute deadline. |
| auto absolute_deadline = kInvalidTimeTicks; // Default to "forever". |
| static constexpr auto kMaxMojoTimeTicks = |
| std::numeric_limits<MojoTimeTicks>::max(); |
| if (deadline <= static_cast<MojoDeadline>(kMaxMojoTimeTicks)) { |
| auto now = GetTimeTicksNow(); |
| if (deadline <= static_cast<MojoDeadline>(kMaxMojoTimeTicks - now)) { |
| absolute_deadline = now + static_cast<MojoTimeTicks>(deadline); |
| handler_deadlines_.push(HandlerDeadlineInfo(id, absolute_deadline)); |
| } |
| // Else either |deadline| or |now| is so large (hopefully the former) that |
| // |now + deadline| would overflow. We'll take that to mean forever. |
| } |
| // Else |deadline| is either very large (which we may as well take as forever) |
| // or |MOJO_DEADLINE_INDEFINITE| (which is forever). |
| |
| // Add an entry to |handlers_|. |
| handlers_.insert(std::make_pair( |
| id, HandlerInfo(handler, handle_signals, absolute_deadline))); |
| // Add an entry to the wait set. |
| MojoResult result = |
| WaitSetAdd(wait_set_.get(), handle, handle_signals, id, nullptr); |
| MOJO_ALLOW_UNUSED_LOCAL(result); |
| assert(result == MOJO_RESULT_OK); |
| |
| return id; |
| } |
| |
| void RunLoop::RemoveHandler(RunLoopHandler::Id id) { |
| assert(current() == this); |
| |
| // Remove the entry from |handlers_|. |
| auto it = handlers_.find(id); |
| if (it == handlers_.end()) |
| return; |
| handlers_.erase(it); |
| // Remove the entry from the wait set. |
| MojoResult result = WaitSetRemove(wait_set_.get(), id); |
| MOJO_ALLOW_UNUSED_LOCAL(result); |
| assert(result == MOJO_RESULT_OK); |
| } |
| |
| void RunLoop::PostDelayedTask(const Closure& task, MojoTimeTicks delay) { |
| assert(current() == this); |
| |
| // Generate a |RunLoopHandler::Id|. |
| auto id = next_id_++; |
| |
| // Calculate the absolute run time. |
| auto now = GetTimeTicksNow(); |
| assert(delay <= std::numeric_limits<MojoTimeTicks>::max() - now); |
| auto absolute_run_time = now + delay; |
| |
| // Add an entry to |delayed_tasks_|. |
| delayed_tasks_.push(DelayedTaskInfo(id, task, absolute_run_time)); |
| } |
| |
| void RunLoop::Run() { |
| RunInternal(false); |
| } |
| |
| void RunLoop::RunUntilIdle() { |
| RunInternal(true); |
| } |
| |
| void RunLoop::Quit() { |
| assert(current() == this); |
| |
| if (current_run_state_) |
| current_run_state_->should_quit = true; |
| } |
| |
| void RunLoop::RunInternal(bool quit_when_idle) { |
| assert(current() == this); |
| |
| auto old_run_state = current_run_state_; |
| RunState run_state; |
| current_run_state_ = &run_state; |
| |
| while (DoIteration(quit_when_idle)) |
| ; // The work is done in |DoIteration()|. |
| |
| current_run_state_ = old_run_state; |
| } |
| |
| bool RunLoop::DoIteration(bool quit_when_idle) { |
| assert(current_run_state_); |
| RunState& run_state = *current_run_state_; |
| assert(!run_state.should_quit); |
| |
| bool should_continue = false; |
| |
| auto now = GetTimeTicksNow(); |
| |
| // First, execute any already-enqueued tasks that are ready. |
| |
| // This is a fake task that we use to compare to enqueued tasks (if one were |
| // to post a task now with no delay, it'd look like this). This is convenient |
| // since |DelayedTaskInfo| has an |operator<| (used by the priority queue |
| // |delayed_tasks_|). |
| // |
| // We want to execute tasks that are "greater" than |now_task| (i.e., |
| // |now_task| is less than them) -- this includes all tasks that are currently |
| // ready, but not any newly-posted tasks (i.e., those that are posted as a |
| // result of executing ready tasks). |
| DelayedTaskInfo now_task(next_id_, Closure(), now); |
| |
| while (!delayed_tasks_.empty() && now_task < delayed_tasks_.top()) { |
| // We could just execute the task directly from |delayed_tasks_.top()|, |
| // since no newly-posted task should change the top of the priority queue, |
| // but doing the below is more obviously correct. |
| Closure task = delayed_tasks_.top().task; |
| delayed_tasks_.pop(); |
| task.Run(); |
| should_continue = true; |
| |
| if (run_state.should_quit) |
| return false; |
| } |
| |
| // Next, "wait" and deal with handles/handlers. |
| |
| if (handlers_.empty()) |
| return should_continue; |
| |
| // Calculate the deadline for the wait. Don't wait if |quit_when_idle| is |
| // true. Otherwise, the minimum of the earliest delayed task run time and the |
| // earliest handler deadline (or "forever" if there are no delayed tasks and |
| // no handler deadlines). (Warning: |CalculateAbsoluteDeadline()| may return a |
| // deadline earlier than |now|.) |
| bool absolute_deadline_is_for_delayed_task = false; |
| MojoTimeTicks absolute_deadline = |
| quit_when_idle |
| ? now |
| : CalculateAbsoluteDeadline(&absolute_deadline_is_for_delayed_task); |
| MojoDeadline relative_deadline = |
| (absolute_deadline == kInvalidTimeTicks) |
| ? MOJO_DEADLINE_INDEFINITE |
| : static_cast<MojoDeadline>(std::max(now, absolute_deadline) - now); |
| |
| run_state.results.resize(run_state.results_size); |
| uint32_t max_results = run_state.results_size; |
| switch (WaitSetWait(wait_set_.get(), relative_deadline, |
| ¤t_run_state_->results, &max_results)) { |
| case MOJO_RESULT_OK: |
| // If there were more results than we could accept, try increasing the |
| // number we accept (up to our limit). |
| if (max_results > run_state.results_size) { |
| run_state.results_size = |
| std::min(kMaximumWaitSetNumResults, run_state.results_size * 2u); |
| } |
| should_continue |= NotifyResults(run_state.results); |
| break; |
| case MOJO_RESULT_INVALID_ARGUMENT: |
| assert(false); // This shouldn't happen. |
| return false; |
| case MOJO_RESULT_CANCELLED: |
| assert(false); // This shouldn't happen. |
| return false; |
| case MOJO_RESULT_RESOURCE_EXHAUSTED: |
| assert(false); // Sadness. |
| return false; |
| case MOJO_RESULT_BUSY: |
| assert(false); // This shouldn't happen. |
| return false; |
| case MOJO_RESULT_DEADLINE_EXCEEDED: |
| should_continue |= NotifyHandlersDeadlineExceeded(absolute_deadline); |
| // If we timed out due for a delayed task, pretend that we did work since |
| // we're not idle yet (there'll be work to do immediately the next time |
| // through the loop). |
| should_continue |= absolute_deadline_is_for_delayed_task; |
| break; |
| default: |
| assert(false); // This *really* shouldn't happen. |
| return false; |
| } |
| |
| if (run_state.should_quit) |
| return false; |
| |
| return quit_when_idle ? should_continue : !handlers_.empty(); |
| } |
| |
| MojoTimeTicks RunLoop::CalculateAbsoluteDeadline(bool* is_delayed_task) { |
| assert(!handlers_.empty()); |
| |
| // Default to "forever". |
| MojoTimeTicks absolute_deadline = kInvalidTimeTicks; |
| if (delayed_tasks_.empty()) { |
| *is_delayed_task = false; |
| } else { |
| // If there are delayed tasks, our deadline can be no later than the |
| // earliest run time. |
| absolute_deadline = delayed_tasks_.top().absolute_run_time; |
| *is_delayed_task = true; |
| } |
| |
| // Find the earliest handler deadline. |
| while (!handler_deadlines_.empty()) { |
| const HandlerDeadlineInfo& info = handler_deadlines_.top(); |
| const auto it = handlers_.find(info.id); |
| // We might have a stale entry at the top. If so, remove it and continue. |
| if (it == handlers_.end()) { |
| handler_deadlines_.pop(); |
| continue; |
| } |
| |
| if (absolute_deadline == kInvalidTimeTicks || |
| info.absolute_deadline < absolute_deadline) { |
| absolute_deadline = info.absolute_deadline; |
| *is_delayed_task = false; |
| } |
| |
| break; |
| } |
| |
| return absolute_deadline; |
| } |
| |
| bool RunLoop::NotifyResults(const std::vector<MojoWaitSetResult>& results) { |
| assert(!results.empty()); |
| |
| bool did_work = false; |
| for (const auto& result : results) { |
| auto id = result.cookie; |
| auto it = handlers_.find(id); |
| // Though we should find an entry for the first result, a handler that we |
| // invoke may remove other handlers. |
| if (it == handlers_.end()) |
| continue; |
| |
| auto handler = it->second.handler; |
| handlers_.erase(it); |
| MojoResult r = WaitSetRemove(wait_set_.get(), id); |
| MOJO_ALLOW_UNUSED_LOCAL(r); |
| assert(r == MOJO_RESULT_OK); |
| if (result.wait_result == MOJO_RESULT_OK) |
| handler->OnHandleReady(id); |
| else |
| handler->OnHandleError(id, result.wait_result); |
| did_work = true; |
| |
| if (current_run_state_->should_quit) |
| break; |
| } |
| return did_work; |
| } |
| |
| bool RunLoop::NotifyHandlersDeadlineExceeded(MojoTimeTicks absolute_deadline) { |
| assert(!handlers_.empty()); |
| assert(absolute_deadline != kInvalidTimeTicks); |
| |
| bool did_work = false; |
| while (!handler_deadlines_.empty()) { |
| const HandlerDeadlineInfo& info = handler_deadlines_.top(); |
| |
| if (info.absolute_deadline > absolute_deadline) |
| break; |
| |
| const auto it = handlers_.find(info.id); |
| // Though the top shouldn't be stale, there may be stale entries after it |
| // (with the same deadline). Moreover, previously-run handlers may have |
| // removed yet-to-be-run handlers. |
| if (it == handlers_.end()) { |
| handler_deadlines_.pop(); |
| continue; |
| } |
| |
| auto handler = it->second.handler; |
| auto id = info.id; |
| handlers_.erase(it); // Invalidates |it|. |
| handler_deadlines_.pop(); // Invalidates |info|. |
| handler->OnHandleError(id, MOJO_RESULT_DEADLINE_EXCEEDED); |
| did_work = true; |
| |
| if (current_run_state_->should_quit) |
| break; |
| } |
| return did_work; |
| } |
| |
| } // namespace mojo |