blob: b4b1b6332fcacdc2be13710ca77ce28aaf170146 [file] [log] [blame]
// Copyright 2020 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.
#ifndef LIB_FIDL_LLCPP_CLIENT_BASE_H_
#define LIB_FIDL_LLCPP_CLIENT_BASE_H_
#include <lib/async/dispatcher.h>
#include <lib/fidl/llcpp/async_binding.h>
#include <lib/fidl/llcpp/extract_resource_on_destruction.h>
#include <lib/zx/channel.h>
#include <zircon/fidl.h>
#include <zircon/listnode.h>
#include <zircon/types.h>
#include <memory>
#include <mutex>
#include <fbl/intrusive_wavl_tree.h>
namespace fidl {
namespace internal {
// ResponseContext contains the state for an outstanding asynchronous transaction. It inherits from
// an intrusive container node so that ClientBase can track it. The user (or generated code) passes
// a pointer to the ResponseContext to the ClientBase, implicitly transferring ownership. Ownership
// is only returned to the caller once either OnReply() or OnError() is invoked.
// TODO(fxbug.dev/50664): fbl::WAVLTree must be made available in the SDK, otherwise it needs to be
// replaced here with some tree that is available there.
// NOTE: ResponseContext uses list_node_t in order to safely iterate over outstanding transactions
// on ClientBase destruction while invoking OnError() which can destroy the ResponseContext.
class ResponseContext : public fbl::WAVLTreeContainable<ResponseContext*>, private list_node_t {
public:
ResponseContext(const fidl_type_t* type, uint64_t ordinal)
: fbl::WAVLTreeContainable<ResponseContext*>(),
list_node_t(LIST_INITIAL_CLEARED_VALUE),
type_(type),
ordinal_(ordinal) {}
virtual ~ResponseContext() = default;
// Neither copyable nor movable.
ResponseContext(const ResponseContext& other) = delete;
ResponseContext& operator=(const ResponseContext& other) = delete;
ResponseContext(ResponseContext&& other) = delete;
ResponseContext& operator=(ResponseContext&& other) = delete;
const fidl_type_t* type() const { return type_; }
uint64_t ordinal() const { return ordinal_; }
zx_txid_t Txid() const { return txid_; }
// Invoked if a response has been received for this context.
virtual void OnReply(uint8_t* reply) = 0;
// Invoked if an error occurs handling the response message prior to invoking the user-specified
// callback or if the ClientBase is destroyed with the transaction outstanding. Note that
// OnError() may be invoked within ~ClientBase(), so the user must ensure that a ClientBase
// is not destroyed while holding any locks OnError() would take.
virtual void OnError() = 0;
private:
friend class ClientBase;
// For use with fbl::WAVLTree.
struct Traits {
static zx_txid_t GetKey(const ResponseContext& context) { return context.txid_; }
static bool LessThan(const zx_txid_t& key1, const zx_txid_t& key2) { return key1 < key2; }
static bool EqualTo(const zx_txid_t& key1, const zx_txid_t& key2) { return key1 == key2; }
};
const fidl_type_t* const type_; // Type of a response with ordinal |ordinal_|.
const uint64_t ordinal_; // Expected ordinal for the response.
zx_txid_t txid_ = 0; // Zircon txid of outstanding transaction.
};
// ChannelRef takes ownership of a channel. It can transfer the channel
// ownership on destruction with the use of |DestroyAndExtract|.
// Otherwise, the channel is closed.
class ChannelRef {
public:
explicit ChannelRef(zx::channel channel) : channel_(ExtractedOnDestruction(std::move(channel))) {}
zx_handle_t handle() const { return channel_.get().get(); }
private:
template <typename Callback>
friend void DestroyAndExtract(std::shared_ptr<ChannelRef>&& object, Callback&& callback);
ExtractedOnDestruction<zx::channel> channel_;
};
template <typename Callback>
void DestroyAndExtract(std::shared_ptr<ChannelRef>&& object, Callback&& callback) {
DestroyAndExtract(std::move(object), &ChannelRef::channel_, std::forward<Callback>(callback));
}
// ChannelRefTracker takes ownership of a channel, wrapping it in a ChannelRef. It is used to create
// and track one or more strong references to the channel.
class ChannelRefTracker {
public:
// Set the given channel as the owned channel.
void Init(zx::channel channel) __TA_EXCLUDES(lock_);
// If the ChannelRef is still alive, returns a strong reference to it.
std::shared_ptr<ChannelRef> Get() { return channel_weak_.lock(); }
// Blocks on the release of any outstanding strong references to the channel and returns it. Only
// one caller will be able to retrieve the channel. Other calls will return immediately with a
// null channel.
zx::channel WaitForChannel() __TA_EXCLUDES(lock_);
private:
std::mutex lock_;
std::shared_ptr<ChannelRef> channel_ __TA_GUARDED(lock_);
// Weak reference used to access channel without taking locks.
std::weak_ptr<ChannelRef> channel_weak_;
};
// Base LLCPP client class supporting use with a multithreaded asynchronous dispatcher, safe error
// handling and unbinding, and asynchronous transaction tracking. Users should not directly interact
// with this class. ClientBase objects are expected to be managed via std::shared_ptr.
class ClientBase {
public:
// Creates an unbound ClientBase. Bind() must be called before any other APIs are invoked.
ClientBase() = default;
virtual ~ClientBase() = default;
// Neither copyable nor movable.
ClientBase(const ClientBase& other) = delete;
ClientBase& operator=(const ClientBase& other) = delete;
ClientBase(ClientBase&& other) = delete;
ClientBase& operator=(ClientBase&& other) = delete;
// Bind the channel to the dispatcher. Invoke on_unbound on error or unbinding.
// NOTE: This is not thread-safe and must be called exactly once, before any other APIs.
zx_status_t Bind(std::shared_ptr<ClientBase> client, zx::channel channel,
async_dispatcher_t* dispatcher, OnClientUnboundFn on_unbound);
// Asynchronously unbind the channel from the dispatcher. on_unbound will be invoked on a
// dispatcher thread if provided.
void Unbind();
// Waits for all strong references to the channel to be released, then returns it. This
// necessarily triggers unbinding in order to release the binding's reference.
// NOTE: As this returns a zx::channel which owns the handle, only a single call is expected to
// succeed. Additional calls will simply return an empty zx::channel.
zx::channel WaitForChannel();
// Stores the given asynchronous transaction response context, setting the txid field.
void PrepareAsyncTxn(ResponseContext* context);
// Forget the transaction associated with the given context. Used when zx_channel_write() fails.
void ForgetAsyncTxn(ResponseContext* context);
// Releases all outstanding `ResponseContext`s. Invoked after the ClientBase is unbound.
void ReleaseResponseContextsWithError();
// Returns a strong reference to the channel to prevent destruction during a zx_channel_call() or
// zx_channel_write(). The caller is responsible for releasing the reference.
std::shared_ptr<ChannelRef> GetChannel() { return channel_tracker_.Get(); }
// For debugging.
size_t GetTransactionCount() {
std::scoped_lock lock(lock_);
return contexts_.size();
}
// Dispatch function invoked by AsyncClientBinding on incoming message. Invokes the virtual
// DispatchEvent().
std::optional<UnbindInfo> Dispatch(fidl_incoming_msg_t* msg);
// Generated client event dispatcher function.
virtual std::optional<UnbindInfo> DispatchEvent(fidl_incoming_msg_t* msg) = 0;
private:
ChannelRefTracker channel_tracker_;
// Weak reference to the internal binding state.
std::weak_ptr<AsyncClientBinding> binding_;
// State for tracking outstanding transactions.
std::mutex lock_;
// The base node of an intrusive container of ResponseContexts corresponding to outstanding
// asynchronous transactions.
fbl::WAVLTree<zx_txid_t, ResponseContext*, ResponseContext::Traits> contexts_ __TA_GUARDED(lock_);
// Mirror list used to safely invoke OnError() on outstanding ResponseContexts in ~ClientBase().
list_node_t delete_list_ __TA_GUARDED(lock_) = LIST_INITIAL_VALUE(delete_list_);
zx_txid_t txid_base_ __TA_GUARDED(lock_) = 0; // Value used to compute the next txid.
};
} // namespace internal
} // namespace fidl
#endif // LIB_FIDL_LLCPP_CLIENT_BASE_H_