| // 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/dispatcher.h> |
| #include <lib/async/task.h> |
| #include <lib/async/time.h> |
| #include <lib/fidl/epitaph.h> |
| #include <lib/fidl/llcpp/async_binding.h> |
| #include <lib/fidl/llcpp/async_transaction.h> |
| #include <lib/fidl/llcpp/client_base.h> |
| #include <lib/fidl/llcpp/internal/transport.h> |
| #include <lib/fidl/trace.h> |
| #include <zircon/assert.h> |
| #include <zircon/syscalls.h> |
| |
| #include <type_traits> |
| |
| namespace fidl { |
| namespace internal { |
| |
| bool DispatchError::RequiresImmediateTeardown() { |
| // Do not immediately teardown the bindings if some FIDL method failed to |
| // write to the transport due to peer closed. The message handler in |
| // |AsyncBinding| will eventually discover that the transport is in the peer |
| // closed state and begin teardown, so we are not ignoring this error just |
| // deferring it. |
| // |
| // To see why this is necessary, consider a FIDL method that is supposed to |
| // shutdown the server connection. Upon processing this FIDL method, the |
| // server may send a reply or a terminal event, and then close their endpoint. |
| // The server might have also sent other replies or events that are waiting to |
| // be read by the client. If the client immediately unbinds on the first call |
| // hitting peer closed, we would be dropping any unread messages that the |
| // server have sent. In other words, whether the terminal events etc. are |
| // surfaced to the user or discarded would depend on whether the user just |
| // happened to make another call after the server closed their endpoint, which |
| // is an undesirable race condition. By deferring the handling of peer closed |
| // errors, we ensure that any messages the server sent prior to closing the |
| // endpoint will be reliably drained by the client and exposed to the user. An |
| // equivalent situation applies in the server bindings in ensuring that client |
| // messages are reliably drained after peer closed. |
| return !(origin == fidl::ErrorOrigin::kSend && info.reason() == fidl::Reason::kPeerClosed); |
| } |
| |
| AsyncBinding::AsyncBinding(async_dispatcher_t* dispatcher, AnyUnownedTransport transport, |
| ThreadingPolicy threading_policy) |
| : dispatcher_(dispatcher), |
| transport_(transport), |
| thread_checker_(transport.vtable(), dispatcher, threading_policy) { |
| ZX_ASSERT(dispatcher_); |
| ZX_ASSERT(transport_.is_valid()); |
| transport_.create_waiter( |
| dispatcher, |
| [this](fidl::IncomingMessage& msg, internal::MessageStorageViewBase* storage_view) { |
| this->MessageHandler(msg, storage_view); |
| }, |
| [this](UnbindInfo info) { this->WaitFailureHandler(info); }, any_transport_waiter_); |
| } |
| |
| void AsyncBinding::MessageHandler(fidl::IncomingMessage& msg, |
| internal::MessageStorageViewBase* storage_view) { |
| ScopedThreadGuard guard(thread_checker_); |
| ZX_ASSERT(keep_alive_); |
| |
| // Flag indicating whether this thread still has access to the binding. |
| bool next_wait_begun_early = false; |
| // Dispatch the message. |
| std::optional<DispatchError> maybe_error = Dispatch(msg, &next_wait_begun_early, storage_view); |
| // If |next_wait_begun_early| is true, then the interest for the next |
| // message had been eagerly registered in the method handler, and another |
| // thread may already be running |MessageHandler|. We should exit without |
| // attempting to register yet another wait or attempting to modify the |
| // binding state here. |
| if (next_wait_begun_early) |
| return; |
| |
| // If there was any error enabling dispatch or an unexpected message, destroy the binding. |
| if (maybe_error) { |
| if (maybe_error->RequiresImmediateTeardown()) { |
| return PerformTeardown(maybe_error->info); |
| } |
| } |
| |
| if (CheckForTeardownAndBeginNextWait() != ZX_OK) |
| return PerformTeardown(std::nullopt); |
| } |
| |
| void AsyncBinding::WaitFailureHandler(UnbindInfo info) { |
| // When a dispatcher error of |ZX_ERR_CANCELED| happens, it indicates that the |
| // dispatcher is shutting down. We have ensured that the dispatcher is |
| // single-threaded via thread checkers at various places during message |
| // dispatch. Here, we can relax the thread checking since there will not be |
| // any parallel up-calls to user objects during shutdown under a |
| // single-threaded dispatcher. |
| if (info.reason() == fidl::Reason::kDispatcherError && info.status() == ZX_ERR_CANCELED) { |
| thread_checker_.assume_exclusive(); |
| } else { |
| thread_checker_.check(); |
| } |
| |
| ZX_ASSERT(keep_alive_); |
| return PerformTeardown(info); |
| } |
| |
| void AsyncBinding::BeginFirstWait() { |
| zx_status_t status; |
| { |
| std::scoped_lock lock(lock_); |
| ZX_ASSERT(lifecycle_.Is(Lifecycle::kCreated)); |
| status = any_transport_waiter_->Begin(); |
| if (status == ZX_OK) { |
| lifecycle_.TransitionToBound(); |
| return; |
| } |
| } |
| |
| // If the first |async_begin_wait| failed, attempt to report the error through |
| // the |on_unbound| handler - the interface was effectively unbound |
| // immediately on first dispatch. |
| // |
| // There are two possible error cases: |
| // |
| // - The server endpoint does not have the |ZX_RIGHT_WAIT| right. Since the |
| // server endpoint may be of foreign origin, asynchronously report the error |
| // through the |on_unbound| handler. |
| // |
| // - The dispatcher does not support waiting on a port, or was shutdown. This |
| // is a programming error. The user code should either switch to a |
| // supporting dispatcher, or properly implement teardown by not shutting |
| // down the event loop until all current incoming events have been |
| // processed. |
| // |
| using Result = AsyncBinding::TeardownTaskPostingResult; |
| Result result = |
| StartTeardownWithInfo(std::shared_ptr(keep_alive_), UnbindInfo::DispatcherError(status)); |
| switch (result) { |
| case Result::kDispatcherError: |
| // We are crashing the process anyways, but clearing |keep_alive_| helps |
| // death-tests pass the leak-sanitizer. |
| keep_alive_ = nullptr; |
| ZX_PANIC( |
| "When binding FIDL connection: " |
| "dispatcher was shutdown, or unsupported dispatcher."); |
| case Result::kRacedWithInProgressTeardown: |
| // Should never happen - the binding was only just created. |
| __builtin_unreachable(); |
| case Result::kOk: |
| return; |
| } |
| } |
| |
| zx_status_t AsyncBinding::CheckForTeardownAndBeginNextWait() { |
| std::scoped_lock lock(lock_); |
| |
| switch (lifecycle_.state()) { |
| case Lifecycle::kMustTeardown: |
| return ZX_ERR_CANCELED; |
| |
| case Lifecycle::kBound: { |
| zx_status_t status = any_transport_waiter_->Begin(); |
| if (status != ZX_OK) |
| lifecycle_.TransitionToMustTeardown(fidl::UnbindInfo::DispatcherError(status)); |
| return status; |
| } |
| |
| default: |
| // Other lifecycle states are illegal. |
| __builtin_abort(); |
| } |
| } |
| |
| void AsyncBinding::HandleError(std::shared_ptr<AsyncBinding>&& calling_ref, DispatchError error) { |
| if (error.RequiresImmediateTeardown()) { |
| StartTeardownWithInfo(std::move(calling_ref), error.info); |
| } |
| } |
| |
| bool AsyncBinding::IsDestructionImminent() const { |
| std::scoped_lock lock(lock_); |
| return lifecycle_.Is(Lifecycle::kMustTeardown) || lifecycle_.Is(Lifecycle::kTorndown); |
| } |
| |
| auto AsyncBinding::StartTeardownWithInfo(std::shared_ptr<AsyncBinding>&& calling_ref, |
| UnbindInfo info) -> TeardownTaskPostingResult { |
| ScopedThreadGuard guard(thread_checker_); |
| ZX_ASSERT(calling_ref); |
| // Move the calling reference into this scope. |
| auto binding = std::move(calling_ref); |
| |
| { |
| std::scoped_lock lock(lock_); |
| if (lifecycle_.Is(Lifecycle::kMustTeardown) || lifecycle_.Is(Lifecycle::kTorndown)) |
| return TeardownTaskPostingResult::kRacedWithInProgressTeardown; |
| lifecycle_.TransitionToMustTeardown(info); |
| } |
| |
| // A boolean value that will become available in the future. |Get| will block |
| // until |Set| is invoked once with the value. |
| class FutureBool { |
| public: |
| void Set(bool value) { |
| value_ = value; |
| sync_completion_signal(&result_ready_); |
| } |
| |
| bool Get() { |
| zx_status_t status = sync_completion_wait(&result_ready_, ZX_TIME_INFINITE); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| return value_; |
| } |
| |
| private: |
| bool value_ = false; |
| sync_completion_t result_ready_ = {}; |
| }; |
| std::shared_ptr message_handler_pending = std::make_shared<FutureBool>(); |
| |
| // Attempt to add a task to teardown the bindings. On failure, the dispatcher |
| // was shutdown; the message handler would notice and perform the teardown. |
| class TeardownTask : private async_task_t { |
| public: |
| static zx_status_t Post(async_dispatcher_t* dispatcher, |
| std::weak_ptr<AsyncBinding> weak_binding, |
| std::shared_ptr<FutureBool> message_handler_pending) { |
| auto* task = new TeardownTask{ |
| dispatcher, |
| std::move(weak_binding), |
| std::move(message_handler_pending), |
| }; |
| zx_status_t status = async_post_task(dispatcher, task); |
| if (status != ZX_OK) |
| delete task; |
| return status; |
| } |
| |
| static void Invoke(async_dispatcher_t* /*unused*/, async_task_t* task, zx_status_t status) { |
| auto* self = static_cast<TeardownTask*>(task); |
| struct Deferred { |
| TeardownTask* task; |
| ~Deferred() { delete task; } |
| } deferred{self}; |
| |
| if (self->message_handler_pending_->Get()) |
| return; |
| self->message_handler_pending_.reset(); |
| |
| // If |weak_binding_| fails to lock to a strong reference, that means the |
| // binding was already torn down by the message handler. This will never |
| // happen because we return early if a message handler is pending. |
| auto binding = self->weak_binding_.lock(); |
| ZX_DEBUG_ASSERT(binding); |
| auto* binding_raw = binding.get(); |
| // |binding->keep_alive_| is at least another reference. |
| ZX_DEBUG_ASSERT(!binding.unique()); |
| binding.reset(); |
| |
| // At this point, no other thread will touch the internal reference. |
| // Either the message handler never started or was canceled. |
| // Therefore, we can relax any thread checking here, since there are no |
| // parallel up-calls to user objects regardless of the current thread. |
| binding_raw->thread_checker_.assume_exclusive(); |
| binding_raw->PerformTeardown(std::nullopt); |
| } |
| |
| private: |
| TeardownTask(async_dispatcher_t* dispatcher, std::weak_ptr<AsyncBinding> weak_binding, |
| std::shared_ptr<FutureBool> message_handler_pending) |
| : async_task_t({{ASYNC_STATE_INIT}, &TeardownTask::Invoke, async_now(dispatcher)}), |
| weak_binding_(std::move(weak_binding)), |
| message_handler_pending_(std::move(message_handler_pending)) {} |
| |
| std::weak_ptr<AsyncBinding> weak_binding_; |
| std::shared_ptr<FutureBool> message_handler_pending_; |
| }; |
| |
| // We need to first post the teardown task, then attempt to cancel the message |
| // handler, and block the teardown task until the cancellation result is ready |
| // using a |FutureBool|. This is because the dispatcher might be shut down in |
| // between the posting and the cancelling. If we tried to cancel first then |
| // post a task, we might end up in a difficult situation where the message |
| // handler was successfully canceled, but the dispatcher was also shut down, |
| // preventing us from posting any more tasks. Then we would run out of threads |
| // from which to notify the user of teardown completion. |
| // |
| // This convoluted dance could be improved if |async_dispatcher_t| supported |
| // interrupting a wait with an error passed to the handler, as opposed to |
| // silent cancellation. |
| if (TeardownTask::Post(dispatcher_, binding, message_handler_pending) != ZX_OK) |
| return TeardownTaskPostingResult::kDispatcherError; |
| |
| { |
| std::scoped_lock lock(lock_); |
| if (lifecycle_.DidBecomeBound()) { |
| // Attempt to cancel the current message handler. On failure, the message |
| // handler is driving/will drive the teardown process. |
| zx_status_t status = any_transport_waiter_->Cancel(); |
| ZX_DEBUG_ASSERT(status == ZX_OK || status == ZX_ERR_NOT_FOUND); |
| message_handler_pending->Set(status != ZX_OK); |
| } else { |
| message_handler_pending->Set(false); |
| } |
| } |
| |
| return TeardownTaskPostingResult::kOk; |
| } |
| |
| void AsyncBinding::PerformTeardown(std::optional<UnbindInfo> info) { |
| auto binding = std::move(keep_alive_); |
| |
| fidl::UnbindInfo stored_info; |
| { |
| std::scoped_lock lock(lock_); |
| if (info.has_value()) |
| lifecycle_.TransitionToMustTeardown(info.value()); |
| stored_info = lifecycle_.TransitionToTorndown(); |
| } |
| |
| FinishTeardown(std::move(binding), stored_info); |
| } |
| |
| void AsyncBinding::Lifecycle::TransitionToBound() { |
| ZX_DEBUG_ASSERT(Is(kCreated)); |
| state_ = kBound; |
| did_enter_bound_ = true; |
| } |
| |
| void AsyncBinding::Lifecycle::TransitionToMustTeardown(fidl::UnbindInfo info) { |
| ZX_DEBUG_ASSERT(Is(kCreated) || Is(kBound) || Is(kMustTeardown)); |
| if (!Is(kMustTeardown)) { |
| state_ = kMustTeardown; |
| info_ = info; |
| } |
| } |
| |
| fidl::UnbindInfo AsyncBinding::Lifecycle::TransitionToTorndown() { |
| ZX_DEBUG_ASSERT(Is(kMustTeardown)); |
| fidl::UnbindInfo info = info_; |
| state_ = kTorndown; |
| info_ = {}; |
| return info; |
| } |
| |
| // |
| // Server binding specifics |
| // |
| |
| std::shared_ptr<AsyncServerBinding> AsyncServerBinding::Create( |
| async_dispatcher_t* dispatcher, fidl::internal::AnyTransport&& server_end, |
| IncomingMessageDispatcher* interface, AnyOnUnboundFn&& on_unbound_fn) { |
| auto binding = std::make_shared<AsyncServerBinding>(dispatcher, std::move(server_end), interface, |
| std::move(on_unbound_fn), ConstructionKey{}); |
| binding->InitKeepAlive(); |
| return binding; |
| } |
| |
| std::optional<DispatchError> AsyncServerBinding::Dispatch( |
| fidl::IncomingMessage& msg, bool* next_wait_begun_early, |
| internal::MessageStorageViewBase* storage_view) { |
| auto* hdr = msg.header(); |
| SyncTransaction txn(hdr->txid, this, next_wait_begun_early); |
| return txn.Dispatch(std::move(msg), storage_view); |
| } |
| |
| void AsyncServerBinding::FinishTeardown(std::shared_ptr<AsyncBinding>&& calling_ref, |
| UnbindInfo info) { |
| // Stash required state after deleting the binding, since the binding |
| // will be destroyed as part of this function. |
| auto* the_interface = interface(); |
| auto on_unbound_fn = std::move(on_unbound_fn_); |
| |
| // Downcast to our class. |
| std::shared_ptr<AsyncServerBinding> server_binding = |
| std::static_pointer_cast<AsyncServerBinding>(calling_ref); |
| calling_ref.reset(); |
| |
| // Delete the calling reference. |
| // Wait for any transient references to be released. |
| DestroyAndExtract( |
| std::move(server_binding), &AsyncServerBinding::server_end_, |
| [&info, the_interface, &on_unbound_fn](fidl::internal::AnyTransport server_end) { |
| // `this` is no longer valid. |
| |
| // If required, send the epitaph. |
| if (info.reason() == Reason::kClose) { |
| ZX_ASSERT(server_end.type() == FIDL_TRANSPORT_TYPE_CHANNEL); |
| info = UnbindInfo::Close(fidl_epitaph_write(server_end.handle(), info.status())); |
| } |
| |
| // Execute the unbound hook if specified. |
| if (on_unbound_fn) |
| on_unbound_fn(the_interface, info, std::move(server_end)); |
| }); |
| } |
| |
| // |
| // Client binding specifics |
| // |
| |
| std::shared_ptr<AsyncClientBinding> AsyncClientBinding::Create( |
| async_dispatcher_t* dispatcher, std::shared_ptr<fidl::internal::AnyTransport> transport, |
| std::shared_ptr<ClientBase> client, AsyncEventHandler* error_handler, |
| AnyTeardownObserver&& teardown_observer, ThreadingPolicy threading_policy) { |
| auto binding = std::shared_ptr<AsyncClientBinding>( |
| new AsyncClientBinding(dispatcher, std::move(transport), std::move(client), error_handler, |
| std::move(teardown_observer), threading_policy)); |
| binding->InitKeepAlive(); |
| return binding; |
| } |
| |
| AsyncClientBinding::AsyncClientBinding(async_dispatcher_t* dispatcher, |
| std::shared_ptr<fidl::internal::AnyTransport> transport, |
| std::shared_ptr<ClientBase> client, |
| AsyncEventHandler* error_handler, |
| AnyTeardownObserver&& teardown_observer, |
| ThreadingPolicy threading_policy) |
| : AsyncBinding(dispatcher, transport->borrow(), threading_policy), |
| transport_(std::move(transport)), |
| client_(std::move(client)), |
| error_handler_(error_handler), |
| teardown_observer_(std::move(teardown_observer)) {} |
| |
| std::optional<DispatchError> AsyncClientBinding::Dispatch( |
| fidl::IncomingMessage& msg, bool*, internal::MessageStorageViewBase* storage_view) { |
| std::optional<UnbindInfo> info = client_->Dispatch(msg, storage_view); |
| if (info.has_value()) { |
| // A client binding does not propagate synchronous sending errors as part of |
| // handling a message. All client callbacks return `void`. |
| return DispatchError{*info, fidl::ErrorOrigin::kReceive}; |
| } |
| return std::nullopt; |
| } |
| |
| void AsyncClientBinding::FinishTeardown(std::shared_ptr<AsyncBinding>&& calling_ref, |
| UnbindInfo info) { |
| // Move binding into scope. |
| std::shared_ptr<AsyncBinding> binding = std::move(calling_ref); |
| |
| // Stash state required after deleting the binding. |
| AnyTeardownObserver teardown_observer = std::move(teardown_observer_); |
| AsyncEventHandler* error_handler = error_handler_; |
| std::shared_ptr<ClientBase> client = std::move(client_); |
| |
| // Delete the calling reference. |
| // We are not returning the transport to the user, so don't wait for transient |
| // references to go away. |
| binding = nullptr; |
| |
| // There could be residual references to the binding, but those are only held |
| // briefly when obtaining the transport. To be conservative, assume that `this` |
| // is no longer valid past this point. |
| |
| // Outstanding async responses will no longer be received, so release the contexts. |
| client->ReleaseResponseContexts(info); |
| client = nullptr; |
| |
| // Execute the error hook if specified. |
| if (info.reason() != fidl::Reason::kUnbind) { |
| if (error_handler != nullptr) |
| error_handler->on_fidl_error(info); |
| } |
| |
| // Notify teardown. |
| std::move(teardown_observer).Notify(); |
| } |
| |
| } // namespace internal |
| } // namespace fidl |