Commit b4509e8b authored by Wez's avatar Wez Committed by Commit Bot

[Fuchsia] Support ASYNC_OPS_V2 APIs, for exception port binding.

Extend the base::AsyncDispatcher implementation of the |async_ops_t| to
support the new exception port bind/unbind APIs.

Bug: 873489
Change-Id: I24cbc4aa687296fcefb09b5261486f1f8849b4ed
Reviewed-on: https://chromium-review.googlesource.com/1172149
Commit-Queue: Wez <wez@chromium.org>
Reviewed-by: default avatarSergey Ulanov <sergeyu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#585312}
parent eee8c331
......@@ -24,6 +24,27 @@ uintptr_t key_from_ptr(T* ptr) {
} // namespace
class AsyncDispatcher::ExceptionState : public LinkNode<ExceptionState> {
public:
explicit ExceptionState(AsyncDispatcher* async_dispatcher) {
async_dispatcher->exception_list_.Append(this);
}
~ExceptionState() { RemoveFromList(); }
async_exception_t* exception() {
// ExceptionState objects are allocated in-place in the |state| field of an
// enclosing async_exceptionwait_t, so async_exception_t address can be
// calculated by subtracting state offset in async_exception_t from |this|.
static_assert(std::is_standard_layout<async_exception_t>(),
"async_wait_t is expected to have standard layout.");
return reinterpret_cast<async_exception_t*>(
reinterpret_cast<uint8_t*>(this) - offsetof(async_exception_t, state));
}
private:
DISALLOW_COPY_AND_ASSIGN(ExceptionState);
};
class AsyncDispatcher::WaitState : public LinkNode<WaitState> {
public:
explicit WaitState(AsyncDispatcher* async_dispatcher) {
......@@ -82,6 +103,7 @@ AsyncDispatcher::AsyncDispatcher() : ops_storage_({}) {
ZX_EVENT_SIGNALED, ZX_WAIT_ASYNC_REPEATING);
ZX_DCHECK(status == ZX_OK, status);
ops_storage_.version = ASYNC_OPS_V2;
ops_storage_.v1.now = NowOp;
ops_storage_.v1.begin_wait = BeginWaitOp;
ops_storage_.v1.cancel_wait = CancelWaitOp;
......@@ -89,6 +111,8 @@ AsyncDispatcher::AsyncDispatcher() : ops_storage_({}) {
ops_storage_.v1.cancel_task = CancelTaskOp;
ops_storage_.v1.queue_packet = QueuePacketOp;
ops_storage_.v1.set_guest_bell_trap = SetGuestBellTrapOp;
ops_storage_.v2.bind_exception_port = BindExceptionPortOp;
ops_storage_.v2.unbind_exception_port = UnbindExceptionPortOp;
ops = &ops_storage_;
DCHECK(!async_get_default_dispatcher());
......@@ -102,6 +126,13 @@ AsyncDispatcher::~AsyncDispatcher() {
// Some waits and tasks may be canceled while the dispatcher is being
// destroyed, so pop-from-head until none remain.
while (!exception_list_.empty()) {
ExceptionState* state = exception_list_.head()->value();
async_exception_t* exception = state->exception();
state->~ExceptionState();
exception->handler(this, exception, ZX_ERR_CANCELED, nullptr);
}
while (!wait_list_.empty()) {
WaitState* state = wait_list_.head()->value();
async_wait_t* wait = state->wait();
......@@ -127,8 +158,7 @@ zx_status_t AsyncDispatcher::DispatchOrWaitUntil(zx_time_t deadline) {
if (status != ZX_OK)
return status;
if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE ||
packet.type == ZX_PKT_TYPE_SIGNAL_REP) {
if (ZX_PKT_IS_SIGNAL_ONE(packet.type) || ZX_PKT_IS_SIGNAL_REP(packet.type)) {
if (packet.key == key_from_ptr(&timer_)) {
// |timer_| has expired.
DCHECK(packet.signal.observed & ZX_TIMER_SIGNALED);
......@@ -142,16 +172,23 @@ zx_status_t AsyncDispatcher::DispatchOrWaitUntil(zx_time_t deadline) {
return ZX_ERR_CANCELED;
} else {
DCHECK_EQ(packet.type, ZX_PKT_TYPE_SIGNAL_ONE);
async_wait_t* wait = reinterpret_cast<async_wait_t*>(packet.key);
auto* wait = reinterpret_cast<async_wait_t*>(packet.key);
// Clean the state before invoking the handler: it may destroy the wait.
WaitState* state = reinterpret_cast<WaitState*>(&wait->state);
// Clean the state before invoking the handler: it may destroy |*wait|.
auto* state = reinterpret_cast<WaitState*>(&wait->state);
state->~WaitState();
wait->handler(this, wait, packet.status, &packet.signal);
return ZX_OK;
}
} else if (ZX_PKT_IS_EXCEPTION(packet.type)) {
auto* exception = reinterpret_cast<async_exception_t*>(packet.key);
// |exception| may have been deleted by the time |handler| returns.
exception->handler(this, exception, packet.status, &packet);
return ZX_OK;
}
NOTREACHED();
......@@ -204,6 +241,17 @@ zx_status_t AsyncDispatcher::SetGuestBellTrapOp(async_dispatcher_t* async,
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t AsyncDispatcher::BindExceptionPortOp(async_dispatcher_t* async,
async_exception_t* exception) {
return static_cast<AsyncDispatcher*>(async)->BindExceptionPort(exception);
}
zx_status_t AsyncDispatcher::UnbindExceptionPortOp(
async_dispatcher_t* async,
async_exception_t* exception) {
return static_cast<AsyncDispatcher*>(async)->UnbindExceptionPort(exception);
}
zx_status_t AsyncDispatcher::BeginWait(async_wait_t* wait) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
......@@ -224,10 +272,13 @@ zx_status_t AsyncDispatcher::BeginWait(async_wait_t* wait) {
zx_status_t AsyncDispatcher::CancelWait(async_wait_t* wait) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!wait->state.reserved[0])
return ZX_ERR_NOT_FOUND;
zx_status_t status = port_.cancel(*zx::unowned_handle(wait->object),
reinterpret_cast<uintptr_t>(wait));
if (status == ZX_OK) {
WaitState* state = reinterpret_cast<WaitState*>(&(wait->state));
auto* state = reinterpret_cast<WaitState*>(&(wait->state));
state->~WaitState();
}
......@@ -272,12 +323,46 @@ zx_status_t AsyncDispatcher::CancelTask(async_task_t* task) {
if (!task->state.reserved[0])
return ZX_ERR_NOT_FOUND;
TaskState* state = reinterpret_cast<TaskState*>(&task->state);
auto* state = reinterpret_cast<TaskState*>(&task->state);
state->~TaskState();
return ZX_OK;
}
zx_status_t AsyncDispatcher::BindExceptionPort(async_exception_t* exception) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
static_assert(
sizeof(AsyncDispatcher::ExceptionState) <= sizeof(async_state_t),
"ExceptionState is too big");
ExceptionState* state = new (&exception->state) ExceptionState(this);
zx_status_t status = zx_task_bind_exception_port(
exception->task, port_.get(), reinterpret_cast<uintptr_t>(exception),
exception->options);
if (status != ZX_OK)
state->~ExceptionState();
return status;
}
zx_status_t AsyncDispatcher::UnbindExceptionPort(async_exception_t* exception) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!exception->state.reserved[0])
return ZX_ERR_NOT_FOUND;
zx_status_t status = zx_task_bind_exception_port(
exception->task, ZX_HANDLE_INVALID,
reinterpret_cast<uintptr_t>(exception), exception->options);
if (status == ZX_OK) {
auto* state = reinterpret_cast<ExceptionState*>(&exception->state);
state->~ExceptionState();
}
return status;
}
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()
......
......@@ -6,6 +6,7 @@
#define BASE_FUCHSIA_ASYNC_DISPATCHER_H_
#include <lib/async/dispatcher.h>
#include <lib/async/exception.h>
#include <lib/zx/event.h>
#include <lib/zx/port.h>
#include <lib/zx/timer.h>
......@@ -35,9 +36,11 @@ class BASE_EXPORT AsyncDispatcher : public async_dispatcher_t {
void Stop();
private:
class ExceptionState;
class WaitState;
class TaskState;
// ASYNC_OPS_V1 operations.
static zx_time_t NowOp(async_dispatcher_t* async);
static zx_status_t BeginWaitOp(async_dispatcher_t* async, async_wait_t* wait);
static zx_status_t CancelWaitOp(async_dispatcher_t* async,
......@@ -54,11 +57,19 @@ class BASE_EXPORT AsyncDispatcher : public async_dispatcher_t {
zx_vaddr_t addr,
size_t length);
// ASYNC_OPS_V2 operations.
static zx_status_t BindExceptionPortOp(async_dispatcher_t* dispatcher,
async_exception_t* exception);
static zx_status_t UnbindExceptionPortOp(async_dispatcher_t* dispatcher,
async_exception_t* exception);
// async_ops_t implementation. Called by corresponding *Op() methods above.
zx_status_t BeginWait(async_wait_t* wait);
zx_status_t CancelWait(async_wait_t* wait);
zx_status_t PostTask(async_task_t* task);
zx_status_t CancelTask(async_task_t* task);
zx_status_t BindExceptionPort(async_exception_t* exception);
zx_status_t UnbindExceptionPort(async_exception_t* exception);
// Runs tasks in |task_list_| that have deadline in the past.
void DispatchTasks();
......@@ -73,6 +84,7 @@ class BASE_EXPORT AsyncDispatcher : public async_dispatcher_t {
zx::event stop_event_;
LinkedList<WaitState> wait_list_;
LinkedList<ExceptionState> exception_list_;
async_ops_t ops_storage_;
......
......@@ -7,12 +7,16 @@
#include <lib/async/default.h>
#include <lib/async/task.h>
#include <lib/async/wait.h>
#include <lib/zx/job.h>
#include <lib/zx/socket.h>
#include "base/callback.h"
#include "base/process/launch.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace base {
......@@ -47,7 +51,7 @@ void TestTask::TaskProc(async_dispatcher_t* async,
test_task->num_calls++;
test_task->last_status = status;
if (!test_task->on_call.is_null())
if (test_task->on_call)
std::move(test_task->on_call).Run();
if (test_task->num_calls < test_task->repeats)
......@@ -86,13 +90,46 @@ void TestWait::HandleProc(async_dispatcher_t* async,
test_wait->num_calls++;
test_wait->last_status = status;
if (!test_wait->on_call.is_null())
if (test_wait->on_call)
std::move(test_wait->on_call).Run();
}
struct TestException : public async_exception_t {
TestException(zx_handle_t handle) {
state = ASYNC_STATE_INIT;
handler = &HandleProc;
task = handle;
options = 0;
}
static void HandleProc(async_dispatcher_t* async,
async_exception_t* wait,
zx_status_t status,
const zx_port_packet_t* packet);
int num_calls = 0;
OnceClosure on_call;
zx_status_t last_status = ZX_OK;
};
// static
void TestException::HandleProc(async_dispatcher_t* async,
async_exception_t* wait,
zx_status_t status,
const zx_port_packet_t* packet) {
EXPECT_EQ(async, async_get_default_dispatcher());
auto* test_wait = static_cast<TestException*>(wait);
test_wait->num_calls++;
test_wait->last_status = status;
if (test_wait->on_call)
std::move(test_wait->on_call).Run();
}
} // namespace
class AsyncDispatcherTest : public testing::Test {
class AsyncDispatcherTest : public MultiProcessTest {
public:
AsyncDispatcherTest() {
dispatcher_ = std::make_unique<AsyncDispatcher>();
......@@ -217,4 +254,53 @@ TEST_F(AsyncDispatcherTest, WaitShutdown) {
EXPECT_EQ(wait.last_status, ZX_ERR_CANCELED);
}
// Sub-process which crashes itself, to generate an exception-port event.
MULTIPROCESS_TEST_MAIN(AsyncDispatcherCrashingChild) {
IMMEDIATE_CRASH();
return 0;
}
TEST_F(AsyncDispatcherTest, BindExceptionPort) {
zx::job child_job;
ASSERT_EQ(zx::job::create(*zx::job::default_job(), 0, &child_job), ZX_OK);
// Bind |child_job|'s exception port to the dispatcher.
TestException exception(child_job.get());
EXPECT_EQ(async_bind_exception_port(async_, &exception), ZX_OK);
// Launch a child process in the job, that will immediately crash.
LaunchOptions options;
options.job_handle = child_job.get();
Process child =
SpawnChildWithOptions("AsyncDispatcherCrashingChild", options);
ASSERT_TRUE(child.IsValid());
// Wait for the exception event to be handled.
EXPECT_EQ(
dispatcher_->DispatchOrWaitUntil(
(TimeTicks::Now() + TestTimeouts::action_max_timeout()).ToZxTime()),
ZX_OK);
EXPECT_EQ(exception.num_calls, 1);
EXPECT_EQ(exception.last_status, ZX_OK);
EXPECT_EQ(async_unbind_exception_port(async_, &exception), ZX_OK);
ASSERT_EQ(child_job.kill(), ZX_OK);
}
TEST_F(AsyncDispatcherTest, CancelExceptionPort) {
zx::job child_job;
ASSERT_EQ(zx::job::create(*zx::job::default_job(), 0, &child_job), ZX_OK);
// Bind |child_job|'s exception port to the dispatcher.
TestException exception(child_job.get());
EXPECT_EQ(async_bind_exception_port(async_, &exception), ZX_OK);
// Tear-down the dispatcher, and verify that the |exception| is cancelled.
dispatcher_ = nullptr;
EXPECT_EQ(exception.num_calls, 1);
EXPECT_EQ(exception.last_status, ZX_ERR_CANCELED);
ASSERT_EQ(child_job.kill(), ZX_OK);
}
} // namespace base
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment