mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-24 14:26:09 +03:00
412 lines
13 KiB
C++
412 lines
13 KiB
C++
// 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 <lib/async/default.h>
|
|
#include <lib/async/task.h>
|
|
#include <lib/async/wait.h>
|
|
#include <lib/zx/handle.h>
|
|
#include <lib/zx/time.h>
|
|
#include <zircon/syscalls.h>
|
|
|
|
#include "base/fuchsia/fuchsia_logging.h"
|
|
|
|
namespace base {
|
|
|
|
namespace {
|
|
|
|
template <typename T>
|
|
uintptr_t key_from_ptr(T* ptr) {
|
|
return reinterpret_cast<uintptr_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) {
|
|
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>(),
|
|
"async_wait_t is expected to have standard layout.");
|
|
return reinterpret_cast<async_wait_t*>(reinterpret_cast<uint8_t*>(this) -
|
|
offsetof(async_wait_t, state));
|
|
}
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(WaitState);
|
|
};
|
|
|
|
class AsyncDispatcher::TaskState : public LinkNode<TaskState> {
|
|
public:
|
|
explicit TaskState(LinkNode<TaskState>* 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>(),
|
|
"async_task_t is expected to have standard layout.");
|
|
return reinterpret_cast<async_task_t*>(reinterpret_cast<uint8_t*>(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_.version = ASYNC_OPS_V2;
|
|
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_storage_.v2.bind_exception_port = BindExceptionPortOp;
|
|
ops_storage_.v2.unbind_exception_port = UnbindExceptionPortOp;
|
|
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 (!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();
|
|
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 (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);
|
|
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);
|
|
auto* wait = reinterpret_cast<async_wait_t*>(packet.key);
|
|
|
|
// 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();
|
|
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<AsyncDispatcher*>(async)->BeginWait(wait);
|
|
}
|
|
|
|
zx_status_t AsyncDispatcher::CancelWaitOp(async_dispatcher_t* async,
|
|
async_wait_t* wait) {
|
|
return static_cast<AsyncDispatcher*>(async)->CancelWait(wait);
|
|
}
|
|
|
|
zx_status_t AsyncDispatcher::PostTaskOp(async_dispatcher_t* async,
|
|
async_task_t* task) {
|
|
return static_cast<AsyncDispatcher*>(async)->PostTask(task);
|
|
}
|
|
|
|
zx_status_t AsyncDispatcher::CancelTaskOp(async_dispatcher_t* async,
|
|
async_task_t* task) {
|
|
return static_cast<AsyncDispatcher*>(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::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_);
|
|
|
|
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<uintptr_t>(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_);
|
|
|
|
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) {
|
|
auto* state = reinterpret_cast<WaitState*>(&(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<TaskState>* 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<TaskState*>(&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;
|
|
|
|
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()
|
|
// 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
|