blob: 6af66222616a2bbf5a580108592382ca8edc91f9 [file] [log] [blame]
// Copyright 2019 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/time.h>
#include <lib/fidl-async/cpp/async_bind_internal.h>
#include <lib/fidl-async/cpp/async_transaction.h>
#include <lib/fidl/epitaph.h>
#include <zircon/syscalls.h>
#include <type_traits>
namespace fidl {
namespace internal {
AsyncBinding::AsyncBinding(async_dispatcher_t* dispatcher, zx::channel channel, void* impl,
TypeErasedDispatchFn dispatch_fn, TypeErasedOnUnboundFn on_unbound_fn)
: wait_({{ASYNC_STATE_INIT},
&AsyncBinding::OnMessage,
channel.get(),
ZX_CHANNEL_PEER_CLOSED | ZX_CHANNEL_READABLE,
0}),
dispatcher_(dispatcher),
channel_(std::move(channel)),
interface_(impl),
dispatch_fn_(dispatch_fn),
on_unbound_fn_(std::move(on_unbound_fn)) {}
AsyncBinding::~AsyncBinding() {
if (on_delete_) {
sync_completion_signal(on_delete_);
if (out_channel_)
*out_channel_ = std::move(channel_);
}
}
void AsyncBinding::OnUnbind(zx_status_t epitaph, UnboundReason reason) {
ZX_ASSERT(keep_alive_);
// Move the internal reference into this scope.
auto binding = std::move(keep_alive_);
bool send_epitaph = false;
{
std::scoped_lock lock(lock_);
// Indicate that no other thread should wait for unbind.
if (!unbind_)
unbind_ = true;
// Determine the epitaph and whether to send it.
if (reason == UnboundReason::kInternalError || epitaph_.send) {
send_epitaph = true;
if (epitaph_.status != ZX_OK)
epitaph = epitaph_.status;
}
}
// Update the reason on a peer closed status.
if (epitaph == ZX_ERR_PEER_CLOSED)
reason = UnboundReason::kPeerClosed;
// Store the error handler and interface pointers before the binding is deleted.
auto on_unbound_fn = std::move(on_unbound_fn_);
auto* intf = interface_;
// Release the internal reference and wait for the deleter to run.
auto channel = WaitForDelete(std::move(binding), reason != UnboundReason::kPeerClosed);
// If required, send the epitaph and close the channel.
if (send_epitaph && channel) {
auto tmp = std::move(channel);
fidl_epitaph_write(tmp.get(), epitaph);
}
// Execute the unbound hook if specified.
if (on_unbound_fn)
on_unbound_fn(intf, reason, std::move(channel));
}
void AsyncBinding::MessageHandler(zx_status_t status, const zx_packet_signal_t* signal) {
if (status != ZX_OK)
return OnUnbind(status, UnboundReason::kInternalError);
if (signal->observed & ZX_CHANNEL_READABLE) {
char bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
for (uint64_t i = 0; i < signal->count; i++) {
fidl_msg_t msg = {
.bytes = bytes,
.handles = handles,
.num_bytes = 0u,
.num_handles = 0u,
};
status = channel_.read(0, bytes, handles, ZX_CHANNEL_MAX_MSG_BYTES,
ZX_CHANNEL_MAX_MSG_HANDLES, &msg.num_bytes, &msg.num_handles);
if (status != ZX_OK || msg.num_bytes < sizeof(fidl_message_header_t)) {
if (status == ZX_OK)
status = ZX_ERR_INTERNAL;
return OnUnbind(status, UnboundReason::kInternalError);
}
// Flag indicating whether this thread still has access to the binding.
bool binding_released = false;
auto hdr = reinterpret_cast<fidl_message_header_t*>(msg.bytes);
AsyncTransaction txn(hdr->txid, &binding_released, &status);
// Transfer keep_alive_ to the AsyncTransaction. If binding_released is false after Dispatch()
// returns, keep_alive_ is still valid and this thread may continue to access the binding.
txn.Dispatch(std::move(keep_alive_), msg); // txn may be moved, cannot access after this.
if (binding_released)
return;
// If there was any error enabling dispatch, destroy the binding.
if (status != ZX_OK)
return OnEnableNextDispatchError(status);
}
// Add the wait back to the dispatcher.
if ((status = EnableNextDispatch()) != ZX_OK)
return OnEnableNextDispatchError(status);
} else {
ZX_DEBUG_ASSERT(signal->observed & ZX_CHANNEL_PEER_CLOSED);
// No epitaph triggered by error due to a PEER_CLOSED.
OnUnbind(ZX_OK, UnboundReason::kPeerClosed);
}
}
zx_status_t AsyncBinding::BeginWait() {
std::scoped_lock lock(lock_);
ZX_ASSERT(!begun_);
begun_ = true;
auto status = async_begin_wait(dispatcher_, &wait_);
// On error, release the internal reference so it can be destroyed.
if (status != ZX_OK)
keep_alive_ = nullptr;
return status;
}
zx_status_t AsyncBinding::EnableNextDispatch() {
std::scoped_lock lock(lock_);
if (unbind_)
return ZX_ERR_CANCELED;
auto status = async_begin_wait(dispatcher_, &wait_);
if (status != ZX_OK)
epitaph_ = {epitaph_.status == ZX_OK ? status : epitaph_.status, true};
return status;
}
void AsyncBinding::UnbindInternal(std::shared_ptr<AsyncBinding>&& calling_ref,
zx_status_t* epitaph) {
ZX_ASSERT(calling_ref);
// Move the calling reference into this scope.
auto binding = std::move(calling_ref);
bool send_epitaph = false;
zx_status_t epitaph_val = ZX_OK;
{
std::scoped_lock lock(lock_);
// Another thread has entered this critical section already via Unbind(), Close(), or
// OnUnbind(). Release our reference and return to unblock that caller.
if (unbind_)
return;
unbind_ = true; // Indicate that waits should no longer be added to the dispatcher.
// Set or update the epitaph in binding state.
send_epitaph = epitaph || epitaph_.send;
if (send_epitaph)
epitaph_val = epitaph_.send ? epitaph_.status : *epitaph;
epitaph_ = {epitaph_val, send_epitaph};
// Attempt to cancel the current wait. On failure, a dispatcher thread will invoke OnUnbind().
if (async_cancel_wait(dispatcher_, &wait_) != ZX_OK)
return;
}
keep_alive_ = nullptr; // No one left to access the internal reference.
// Stash data which must outlive the AsyncBinding.
auto on_unbound_fn = std::move(on_unbound_fn_);
auto* intf = interface_;
auto* dispatcher = dispatcher_;
// Wait for deletion and take the channel. This will only wait on internal code which will not
// block indefinitely.
auto channel = WaitForDelete(std::move(binding), true);
// If required, send the epitaph and close the channel.
if (channel && send_epitaph) {
auto tmp = std::move(channel);
fidl_epitaph_write(tmp.get(), epitaph_val);
}
if (!on_unbound_fn)
return;
// Send the error handler as part of a new task on the dispatcher. This avoids nesting user code
// in the same thread context which could cause deadlock.
auto task = new UnboundTask{
.task = {{ASYNC_STATE_INIT}, &AsyncBinding::OnUnboundTask, async_now(dispatcher)},
.on_unbound_fn = std::move(on_unbound_fn),
.intf = intf,
.channel = std::move(channel),
.reason =
epitaph_val == ZX_ERR_PEER_CLOSED ? UnboundReason::kPeerClosed : UnboundReason::kUnbind};
ZX_ASSERT(async_post_task(dispatcher, &task->task) == ZX_OK);
}
zx::channel AsyncBinding::WaitForDelete(std::shared_ptr<AsyncBinding>&& calling_ref,
bool get_channel) {
sync_completion_t on_delete;
calling_ref->on_delete_ = &on_delete;
zx::channel channel;
if (get_channel)
calling_ref->out_channel_ = &channel;
calling_ref.reset();
ZX_ASSERT(sync_completion_wait(&on_delete, ZX_TIME_INFINITE) == ZX_OK);
return channel;
}
void AsyncBinding::OnEnableNextDispatchError(zx_status_t error) {
ZX_ASSERT(error != ZX_OK);
if (error == ZX_ERR_CANCELED) {
OnUnbind(ZX_OK, UnboundReason::kUnbind);
return;
}
OnUnbind(error, UnboundReason::kInternalError);
}
std::shared_ptr<internal::AsyncBinding> AsyncBinding::CreateSelfManagedBinding(
async_dispatcher_t* dispatcher, zx::channel channel, void* impl,
TypeErasedDispatchFn dispatch_fn, TypeErasedOnUnboundFn on_unbound_fn) {
auto ret = std::shared_ptr<internal::AsyncBinding>(new internal::AsyncBinding(
dispatcher, std::move(channel), impl, dispatch_fn, std::move(on_unbound_fn)));
ret->keep_alive_ = ret; // We keep the binding alive until somebody decides to close the channel.
return ret;
}
fit::result<BindingRef, zx_status_t> AsyncTypeErasedBind(async_dispatcher_t* dispatcher,
zx::channel channel, void* impl,
TypeErasedDispatchFn dispatch_fn,
TypeErasedOnUnboundFn on_unbound_fn) {
auto internal_binding = internal::AsyncBinding::CreateSelfManagedBinding(
dispatcher, std::move(channel), impl, dispatch_fn, std::move(on_unbound_fn));
auto status = internal_binding->BeginWait();
if (status == ZX_OK) {
return fit::ok(fidl::BindingRef(std::move(internal_binding)));
} else {
return fit::error(status);
}
}
} // namespace internal
void BindingRef::Unbind() {
if (binding_)
binding_->Unbind(std::move(binding_));
}
void BindingRef::Close(zx_status_t epitaph) {
if (binding_)
binding_->Close(std::move(binding_), epitaph);
}
} // namespace fidl