blob: 7e8b0144b528ad2019a6ae8a8b8b7c579584412b [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_REMOTE_H_
#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_REMOTE_H_
#include <cstdint>
#include <tuple>
#include <utility>
#include "base/check.h"
#include "base/functional/callback_forward.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/sequenced_task_runner.h"
#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
#include "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/runtime_features.h"
#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
namespace mojo {
// An AssociatedRemote is similar to a Remote (see remote.h): it is used to
// issue mojom interface method calls that will be sent over a message pipe to
// be handled by the entangled AssociatedReceiver.
//
// An AssociatedRemote is needed when it is important to preserve the relative
// ordering of calls with another mojom interface. This is implemented by
// sharing the underlying message pipe between the mojom interfaces where
// ordering must be preserved.
//
// Because of this, an AssociatedRemote cannot be used to issue mojom interface
// method calls until one of its endpoints (either the AssociatedRemote itself
// or its entangled AssociatedReceiver) is sent over a Remote/Receiver pair
// or an already-established AssociatedRemote/AssociatedReceiver pair.
template <typename Interface>
class AssociatedRemote {
public:
using InterfaceType = Interface;
using PendingType = PendingAssociatedRemote<Interface>;
using Proxy = typename Interface::Proxy_;
// Constructs an unbound AssociatedRemote. This object cannot issue Interface
// method calls and does not schedule any tasks.
AssociatedRemote() = default;
AssociatedRemote(AssociatedRemote&& other) noexcept {
*this = std::move(other);
}
// Constructs a new AssociatedRemote which is bound from |pending_remote| and
// which schedules response callbacks and disconnection notifications on the
// default SequencedTaskRunner (i.e.,
// base::SequencedTaskRunner::GetCurrentDefault() at construction time).
explicit AssociatedRemote(PendingAssociatedRemote<Interface> pending_remote)
: AssociatedRemote(std::move(pending_remote), nullptr) {}
// Constructs a new AssociatedRemote which is bound from |pending_remote| and
// which schedules response callbacks and disconnection notifications on
// |task_runner|. |task_runner| must run tasks on the same sequence that owns
// this AssociatedRemote.
AssociatedRemote(PendingAssociatedRemote<Interface> pending_remote,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
Bind(std::move(pending_remote), std::move(task_runner));
}
AssociatedRemote(const AssociatedRemote&) = delete;
AssociatedRemote& operator=(const AssociatedRemote&) = delete;
~AssociatedRemote() = default;
AssociatedRemote& operator=(AssociatedRemote&& other) noexcept {
internal_state_.Swap(&other.internal_state_);
return *this;
}
// Exposes access to callable Interface methods directed at this
// AssociatedRemote's receiver. Must only be called on a bound
// AssociatedRemote.
//
// Please also see comments of |is_bound()| about when it is safe to make
// calls using the returned pointer.
typename Interface::Proxy_* get() const {
DCHECK(is_bound())
<< "Cannot issue Interface method calls on an unbound AssociatedRemote";
return internal_state_.instance();
}
// Shorthand form of |get()|. See above.
typename Interface::Proxy_* operator->() const { return get(); }
typename Interface::Proxy_& operator*() const { return *get(); }
// Indicates whether this AssociatedRemote is bound.
//
// NOTE:
// 1) The state of being "bound" should not be confused with the state of
// being "connected" (see |is_connected()| below). An AssociatedRemote is
// NEVER passively unbound and the only way for it to become unbound is to
// explicitly call |reset()| or |Unbind()|. As such, unless you make explicit
// calls to those methods, it is always safe to assume that an
// AssociatedRemote you've bound will remain bound.
//
// 2) The state of being "bound" is a necessary but not sufficient condition
// for Interface methods to be callable. For them to be callable, the
// AssociatedRemote must also be "associated", which means either one of
// the following cases:
// 2-1) Either itself or its entangled AssociatedReceiver must be sent over
// a Remote/Receiver pair or an already-established
// AssociatedRemote/AssociatedReceiver pair.
// 2-2) It is bound with a dedicated message pipe. Please see comments of
// BindNewEndpointAndPassDedicatedReceiver().
bool is_bound() const { return internal_state_.is_bound(); }
explicit operator bool() const { return is_bound(); }
// Indicates whether this AssociatedRemote is connected to a receiver. Must
// only be called on a bound AssociatedRemote. If this returns |true|, method
// calls made by this AssociatedRemote may eventually end up at the connected
// receiver (though it's of course possible for this call to race with
// disconnection). If this returns |false| however, all future Interface
// method calls on this AssociatedRemote will be silently dropped.
//
// A bound AssociatedRemote becomes disconnected automatically either when its
// receiver is destroyed, or when it receives a malformed or otherwise
// unexpected response message from the receiver.
//
// NOTE: The state of being "bound" should not be confused with the state of
// being "connected". See |is_bound()| above.
bool is_connected() const {
DCHECK(is_bound());
return !internal_state_.encountered_error();
}
// Sets a Closure to be invoked if this AssociatedRemote is cut off from its
// receiver. This can happen if the corresponding AssociatedReceiver (or
// unconsumed PendingAssociatedReceiver) is destroyed, or if the
// AssociatedReceiver sends a malformed or otherwise unexpected response
// message to this AssociatedRemote. Must only be called on a bound
// AssociatedRemote object, and only remains set as long as the
// AssociatedRemote is both bound and connected.
//
// If invoked at all, |handler| will be scheduled asynchronously using the
// AssociatedRemote's bound SequencedTaskRunner.
void set_disconnect_handler(base::OnceClosure handler) {
if (is_connected())
internal_state_.set_connection_error_handler(std::move(handler));
}
// Similar to above but the handler receives additional metadata if provided
// by the receiving endpoint when closing itself.
void set_disconnect_with_reason_handler(
ConnectionErrorWithReasonCallback handler) {
internal_state_.set_connection_error_with_reason_handler(
std::move(handler));
}
// A convenient helper that resets this AssociatedRemote on disconnect. Note
// that this replaces any previously set disconnection handler. Must be called
// on a bound AssociatedRemote object. If the AssociatedRemote is connected,
// a callback is set to reset it after it is disconnected. If AssociatedRemote
// is bound but disconnected then reset is called immediately.
void reset_on_disconnect() {
if (!is_connected()) {
reset();
return;
}
set_disconnect_handler(
base::BindOnce(&AssociatedRemote::reset, base::Unretained(this)));
}
// Resets this AssociatedRemote to an unbound state. To reset the
// AssociatedRemote and recover an PendingAssociatedRemote that can be bound
// again later, use |Unbind()| instead.
void reset() {
State doomed_state;
internal_state_.Swap(&doomed_state);
}
// Similar to the method above, but also specifies a disconnect reason.
void ResetWithReason(uint32_t custom_reason, const std::string& description) {
if (internal_state_.is_bound())
internal_state_.CloseWithReason(custom_reason, description);
reset();
}
// Helpers for binding and unbinding the AssociatedRemote. Only an unbound
// AssociatedRemote (i.e. |is_bound()| is false) may be bound. Similarly, only
// a bound AssociatedRemote may be unbound.
// Binds this AssociatedRemote with the returned PendingAssociatedReceiver.
// Mojom interface method calls made through |this| will be routed to the
// object that ends up binding the returned PendingAssociatedReceiver.
//
// Any response callbacks or disconnection notifications will be scheduled to
// run on |task_runner|. If |task_runner| is null, defaults to the current
// SequencedTaskRunner.
[[nodiscard]] PendingAssociatedReceiver<Interface>
BindNewEndpointAndPassReceiver(
scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) {
DCHECK(!is_bound()) << "AssociatedRemote for " << Interface::Name_
<< " is already bound";
if (!internal::GetRuntimeFeature_ExpectEnabled<Interface>()) {
return PendingAssociatedReceiver<Interface>();
}
ScopedInterfaceEndpointHandle remote_handle;
ScopedInterfaceEndpointHandle receiver_handle;
ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(
&remote_handle, &receiver_handle);
Bind(PendingAssociatedRemote<Interface>(std::move(remote_handle), 0),
std::move(task_runner));
return PendingAssociatedReceiver<Interface>(std::move(receiver_handle));
}
// Binds this AssociatedRemote by consuming |pending_remote|.
//
// Any response callbacks or disconnection notifications will be scheduled to
// run on |task_runner|. If |task_runner| is null, defaults to the current
// SequencedTaskRunner.
void Bind(PendingAssociatedRemote<Interface> pending_remote,
scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) {
DCHECK(!is_bound()) << "AssociatedRemote for " << Interface::Name_
<< " is already bound";
if (!pending_remote) {
reset();
return;
}
if (!internal::GetRuntimeFeature_ExpectEnabled<Interface>()) {
reset();
return;
}
internal_state_.Bind(
AssociatedInterfacePtrInfo<Interface>(pending_remote.PassHandle(),
pending_remote.version()),
std::move(task_runner));
// Force the internal state to configure its proxy. Unlike InterfacePtr we
// do not use AssociatedRemote in transit, so binding to a pipe handle can
// also imply binding to a SequencedTaskRunner and observing pipe handle
// state.
std::ignore = internal_state_.instance();
}
// Binds this AssociatedRemote with the returned PendingAssociatedReceiver
// using a dedicated message pipe. This allows the entangled
// AssociatedReceiver/AssociatedRemote endpoints to be used without ever being
// associated with any other mojom interfaces.
//
// Needless to say, messages sent between the two entangled endpoints will not
// be ordered with respect to any other mojom interfaces. This is generally
// useful for ignoring calls on an associated remote or for binding associated
// endpoints in tests.
[[nodiscard]] PendingAssociatedReceiver<Interface>
BindNewEndpointAndPassDedicatedReceiver() {
DCHECK(!is_bound()) << "AssociatedRemote for " << Interface::Name_
<< " is already bound";
PendingAssociatedReceiver<Interface> receiver =
BindNewEndpointAndPassReceiver();
if (receiver) {
receiver.EnableUnassociatedUsage();
}
return receiver;
}
// Unbinds this AssociatedRemote, rendering it unable to issue further
// Interface method calls. Returns a PendingAssociatedRemote which may be
// passed across threads or processes and consumed by another AssociatedRemote
// elsewhere.
//
// Note that it is an error (the bad, crashy kind of error) to attempt to
// |Unbind()| an AssociatedRemote which is awaiting one or more responses to
// previously issued Interface method calls. Calling this method should only
// be considered in cases where satisfaction of that constraint can be proven.
//
// Must only be called on a bound AssociatedRemote.
[[nodiscard]] PendingAssociatedRemote<Interface> Unbind() {
DCHECK(is_bound());
CHECK(!internal_state_.has_pending_callbacks());
State state;
internal_state_.Swap(&state);
AssociatedInterfacePtrInfo<Interface> info = state.PassInterface();
return PendingAssociatedRemote<Interface>(info.PassHandle(),
info.version());
}
// Sends a message on the underlying message pipe and runs the current
// message loop until its response is received. This can be used in tests to
// verify that no message was sent on a message pipe in response to some
// stimulus.
void FlushForTesting() { internal_state_.FlushForTesting(); }
internal::AssociatedInterfacePtrState<Interface>* internal_state() {
return &internal_state_;
}
private:
using State = internal::AssociatedInterfacePtrState<Interface>;
mutable State internal_state_;
};
} // namespace mojo
#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_REMOTE_H_