blob: c92945867da9f2eda44409166504e68e65d66db7 [file] [log] [blame] [edit]
// Copyright 2018 The Fuchsia 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 <lib/async-testutils/test_loop_dispatcher.h>
#include <zircon/assert.h>
#include <zircon/syscalls.h>
#define TO_NODE(type, ptr) ((list_node_t*)&ptr->state)
#define FROM_NODE(type, ptr) ((type*)((char*)(ptr)-offsetof(type, state)))
namespace async {
namespace {
// Convenience functions for task, wait, and list node management.
inline list_node_t* WaitToNode(async_wait_t* wait) {
return TO_NODE(async_wait_t, wait);
}
inline async_wait_t* NodeToWait(list_node_t* node) {
return FROM_NODE(async_wait_t, node);
}
inline list_node_t* TaskToNode(async_task_t* task) {
return TO_NODE(async_task_t, task);
}
inline async_task_t* NodeToTask(list_node_t* node) {
return FROM_NODE(async_task_t, node);
}
inline void InsertTask(list_node_t* task_list, async_task_t* task) {
list_node_t* node;
for (node = task_list->prev; node != task_list; node = node->prev) {
if (task->deadline >= NodeToTask(node)->deadline)
break;
}
list_add_after(node, TaskToNode(task));
}
} // namespace
TestLoopDispatcher::TestLoopDispatcher(zx::time* current_time)
: current_time_(current_time) {
ZX_DEBUG_ASSERT(current_time);
list_initialize(&wait_list_);
list_initialize(&task_list_);
zx_status_t status = zx::port::create(0u, &port_);
ZX_ASSERT_MSG(status == ZX_OK, "status=%d", status);
}
TestLoopDispatcher::~TestLoopDispatcher() = default;
zx_status_t TestLoopDispatcher::BeginWait(async_wait_t* wait) {
ZX_DEBUG_ASSERT(wait);
list_add_head(&wait_list_, WaitToNode(wait));
zx_status_t status = zx_object_wait_async(wait->object, port_.get(),
reinterpret_cast<uintptr_t>(wait),
wait->trigger,
ZX_WAIT_ASYNC_ONCE);
if (status != ZX_OK) {
// In this rare condition, the wait failed. Since a dispatched handler will
// never be invoked on the wait object, we remove it ourselves.
list_delete(WaitToNode(wait));
}
return status;
}
zx_status_t TestLoopDispatcher::CancelWait(async_wait_t* wait) {
ZX_DEBUG_ASSERT(wait);
zx_status_t status = port_.cancel(wait->object, reinterpret_cast<uintptr_t>(wait));
if (status == ZX_OK) {
list_delete(WaitToNode(wait));
}
return status;
}
zx_status_t TestLoopDispatcher::PostTask(async_task_t* task) {
ZX_DEBUG_ASSERT(task);
InsertTask(&task_list_, task);
return ZX_OK;
}
zx_status_t TestLoopDispatcher::CancelTask(async_task_t* task) {
ZX_DEBUG_ASSERT(task);
list_node_t* node = TaskToNode(task);
if (!list_in_list(node)) {
return ZX_ERR_NOT_FOUND;
}
list_delete(node);
return ZX_OK;
}
zx_status_t TestLoopDispatcher::DispatchNextWait() {
zx_port_packet_t packet;
zx_status_t status = port_.wait(zx::time(0), &packet, 0);
if (status != ZX_OK) {
return status;
}
async_wait_t* wait = reinterpret_cast<async_wait_t*>(packet.key);
list_delete(WaitToNode(wait));
// Invoke the handler. Note that it might destroy itself.
wait->handler(this, wait, status, &packet.signal);
return ZX_OK;
}
void TestLoopDispatcher::DispatchTasks() {
zx::time dispatch_time = *current_time_;
// Dequeue and dispatch one task at a time in case an earlier task wants
// to cancel a later task which has also come due.
list_node_t* node;
while ((node = list_peek_head(&task_list_))) {
async_task_t* task = NodeToTask(node);
if (task->deadline > current_time_->get()) {
break;
}
list_delete(node);
// Invoke the handler. Note that it might destroy itself.
task->handler(this, task, ZX_OK);
}
// The following check fails when a task captures the TestLoop and advances
// time. For one thing, this prevents the prospect of an infinte run.
ZX_ASSERT(dispatch_time == *current_time_);
}
void TestLoopDispatcher::Shutdown() {
list_node_t* node;
while ((node = list_remove_head(&wait_list_))) {
async_wait_t* wait = NodeToWait(node);
wait->handler(this, wait, ZX_ERR_CANCELED, NULL);
}
while ((node = list_remove_head(&task_list_))) {
async_task_t* task = NodeToTask(node);
task->handler(this, task, ZX_ERR_CANCELED);
}
}
} // namespace async