// 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/fuchsia/async_dispatcher.h" #include #include #include #include #include #include #include "base/fuchsia/fuchsia_logging.h" namespace base { namespace { template uintptr_t key_from_ptr(T* ptr) { return reinterpret_cast(ptr); }; } // namespace class AsyncDispatcher::WaitState : public LinkNode { public: explicit WaitState(AsyncDispatcher* async_dispatcher) { async_dispatcher->wait_list_.Append(this); } ~WaitState() { RemoveFromList(); } async_wait_t* wait() { // WaitState objects are allocated in-place in the |state| field of an // enclosing async_wait_t, so async_wait_t address can be calculated by // subtracting state offset in async_wait_t from |this|. static_assert(std::is_standard_layout(), "async_wait_t is expected to have standard layout."); return reinterpret_cast(reinterpret_cast(this) - offsetof(async_wait_t, state)); } private: DISALLOW_COPY_AND_ASSIGN(WaitState); }; class AsyncDispatcher::TaskState : public LinkNode { public: explicit TaskState(LinkNode* previous_task) { InsertAfter(previous_task); } ~TaskState() { RemoveFromList(); } async_task_t* task() { // TaskState objects are allocated in-place in the |state| field of an // enclosing async_task_t, so async_task_t address can be calculated by // subtracting state offset in async_task_t from |this|. static_assert(std::is_standard_layout(), "async_task_t is expected to have standard layout."); return reinterpret_cast(reinterpret_cast(this) - offsetof(async_task_t, state)); } private: DISALLOW_COPY_AND_ASSIGN(TaskState); }; AsyncDispatcher::AsyncDispatcher() : ops_storage_({}) { zx_status_t status = zx::port::create(0u, &port_); ZX_DCHECK(status == ZX_OK, status); status = zx::timer::create(0u, ZX_CLOCK_MONOTONIC, &timer_); ZX_DCHECK(status == ZX_OK, status); status = timer_.wait_async(port_, key_from_ptr(&timer_), ZX_TIMER_SIGNALED, ZX_WAIT_ASYNC_REPEATING); ZX_DCHECK(status == ZX_OK, status); status = zx::event::create(0, &stop_event_); ZX_DCHECK(status == ZX_OK, status); status = stop_event_.wait_async(port_, key_from_ptr(&stop_event_), ZX_EVENT_SIGNALED, ZX_WAIT_ASYNC_REPEATING); ZX_DCHECK(status == ZX_OK, status); ops_storage_.v1.now = NowOp; ops_storage_.v1.begin_wait = BeginWaitOp; ops_storage_.v1.cancel_wait = CancelWaitOp; ops_storage_.v1.post_task = PostTaskOp; ops_storage_.v1.cancel_task = CancelTaskOp; ops_storage_.v1.queue_packet = QueuePacketOp; ops_storage_.v1.set_guest_bell_trap = SetGuestBellTrapOp; ops = &ops_storage_; DCHECK(!async_get_default_dispatcher()); async_set_default_dispatcher(this); } AsyncDispatcher::~AsyncDispatcher() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_EQ(async_get_default_dispatcher(), this); // Some waits and tasks may be canceled while the dispatcher is being // destroyed, so pop-from-head until none remain. while (!wait_list_.empty()) { WaitState* state = wait_list_.head()->value(); async_wait_t* wait = state->wait(); state->~WaitState(); wait->handler(this, wait, ZX_ERR_CANCELED, nullptr); } while (!task_list_.empty()) { TaskState* state = task_list_.head()->value(); async_task_t* task = state->task(); state->~TaskState(); task->handler(this, task, ZX_ERR_CANCELED); } async_set_default_dispatcher(nullptr); } zx_status_t AsyncDispatcher::DispatchOrWaitUntil(zx_time_t deadline) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); zx_port_packet_t packet = {}; zx_status_t status = port_.wait(zx::time(deadline), &packet); if (status != ZX_OK) return status; if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE || packet.type == ZX_PKT_TYPE_SIGNAL_REP) { if (packet.key == key_from_ptr(&timer_)) { // |timer_| has expired. DCHECK(packet.signal.observed & ZX_TIMER_SIGNALED); DispatchTasks(); return ZX_OK; } else if (packet.key == key_from_ptr(&stop_event_)) { // Stop() was called. DCHECK(packet.signal.observed & ZX_EVENT_SIGNALED); status = zx_object_signal(stop_event_.get(), ZX_EVENT_SIGNALED, 0); ZX_DCHECK(status == ZX_OK, status); return ZX_ERR_CANCELED; } else { DCHECK_EQ(packet.type, ZX_PKT_TYPE_SIGNAL_ONE); async_wait_t* wait = reinterpret_cast(packet.key); // Clean the state before invoking the handler: it may destroy the wait. WaitState* state = reinterpret_cast(&wait->state); state->~WaitState(); wait->handler(this, wait, packet.status, &packet.signal); return ZX_OK; } } NOTREACHED(); return ZX_ERR_INTERNAL; } void AsyncDispatcher::Stop() { // Can be called on any thread. zx_status_t status = zx_object_signal(stop_event_.get(), 0, ZX_EVENT_SIGNALED); ZX_DCHECK(status == ZX_OK, status); } zx_time_t AsyncDispatcher::NowOp(async_dispatcher_t* async) { DCHECK(async); return zx_clock_get(ZX_CLOCK_MONOTONIC); } zx_status_t AsyncDispatcher::BeginWaitOp(async_dispatcher_t* async, async_wait_t* wait) { return static_cast(async)->BeginWait(wait); } zx_status_t AsyncDispatcher::CancelWaitOp(async_dispatcher_t* async, async_wait_t* wait) { return static_cast(async)->CancelWait(wait); } zx_status_t AsyncDispatcher::PostTaskOp(async_dispatcher_t* async, async_task_t* task) { return static_cast(async)->PostTask(task); } zx_status_t AsyncDispatcher::CancelTaskOp(async_dispatcher_t* async, async_task_t* task) { return static_cast(async)->CancelTask(task); } zx_status_t AsyncDispatcher::QueuePacketOp(async_dispatcher_t* async, async_receiver_t* receiver, const zx_packet_user_t* data) { return ZX_ERR_NOT_SUPPORTED; } zx_status_t AsyncDispatcher::SetGuestBellTrapOp(async_dispatcher_t* async, async_guest_bell_trap_t* trap, zx_handle_t guest, zx_vaddr_t addr, size_t length) { return ZX_ERR_NOT_SUPPORTED; } zx_status_t AsyncDispatcher::BeginWait(async_wait_t* wait) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); static_assert(sizeof(AsyncDispatcher::WaitState) <= sizeof(async_state_t), "WaitState is too big"); WaitState* state = new (&wait->state) WaitState(this); zx_status_t status = zx::unowned_handle(wait->object) ->wait_async(port_, reinterpret_cast(wait), wait->trigger, ZX_WAIT_ASYNC_ONCE); if (status != ZX_OK) state->~WaitState(); return status; } zx_status_t AsyncDispatcher::CancelWait(async_wait_t* wait) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); zx_status_t status = port_.cancel(*zx::unowned_handle(wait->object), reinterpret_cast(wait)); if (status == ZX_OK) { WaitState* state = reinterpret_cast(&(wait->state)); state->~WaitState(); } return status; } zx_status_t AsyncDispatcher::PostTask(async_task_t* task) { // Can be called on any thread. AutoLock lock(lock_); // Find correct position for the new task in |task_list_| to keep the list // sorted by deadline. This implementation has O(N) complexity, but it's // acceptable - async task are not expected to be used frequently. // TODO(sergeyu): Consider using a more efficient data structure if tasks // performance becomes important. LinkNode* node; for (node = task_list_.head(); node != task_list_.end(); node = node->previous()) { if (task->deadline >= node->value()->task()->deadline) break; } static_assert(sizeof(AsyncDispatcher::TaskState) <= sizeof(async_state_t), "TaskState is too big"); // Will insert new task after |node|. new (&task->state) TaskState(node); if (reinterpret_cast(&task->state) == task_list_.head()) { // Task inserted at head. Earliest deadline changed. RestartTimerLocked(); } return ZX_OK; } zx_status_t AsyncDispatcher::CancelTask(async_task_t* task) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); AutoLock lock(lock_); if (!task->state.reserved[0]) return ZX_ERR_NOT_FOUND; TaskState* state = reinterpret_cast(&task->state); state->~TaskState(); return ZX_OK; } void AsyncDispatcher::DispatchTasks() { // Snapshot now value to set implicit bound for the tasks that will run before // DispatchTasks() returns. This also helps to avoid calling zx_clock_get() // more than necessary. zx_time_t now = zx_clock_get(ZX_CLOCK_MONOTONIC); while (true) { async_task_t* task; { AutoLock lock(lock_); if (task_list_.empty()) break; TaskState* task_state = task_list_.head()->value(); task = task_state->task(); if (task->deadline > now) { RestartTimerLocked(); break; } task_state->~TaskState(); // ~TaskState() is expected to reset the state to 0. The destructor // removes the task from the |task_list_| and LinkNode::RemoveFromList() // sets both its fields to nullptr, which is equivalent to resetting the // state to 0. DCHECK_EQ(task->state.reserved[0], 0u); } // The handler is responsible for freeing the |task| or it may reuse it. task->handler(this, task, ZX_OK); } } void AsyncDispatcher::RestartTimerLocked() { lock_.AssertAcquired(); if (task_list_.empty()) return; zx_time_t deadline = task_list_.head()->value()->task()->deadline; zx_status_t status = timer_.set(zx::time(deadline), zx::duration()); ZX_DCHECK(status == ZX_OK, status); } } // namespace base