blob: c1345079a5c901d3bf339a93c47c8689de07769b [file] [log] [blame]
// Copyright 2015 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 "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/synchronization/lock.h"
#include "mojo/public/cpp/bindings/associated_group_controller.h"
#include "mojo/public/cpp/bindings/lib/may_auto_lock.h"
namespace mojo {
// ScopedInterfaceEndpointHandle::State ----------------------------------------
// State could be called from multiple threads.
class ScopedInterfaceEndpointHandle::State
: public base::RefCountedThreadSafe<State> {
public:
State() = default;
State(InterfaceId id,
scoped_refptr<AssociatedGroupController> group_controller)
: id_(id), group_controller_(group_controller) {}
void InitPendingState(scoped_refptr<State> peer) {
DCHECK(!lock_);
DCHECK(!pending_association_);
lock_.emplace();
pending_association_ = true;
peer_state_ = std::move(peer);
}
void Close(const base::Optional<DisconnectReason>& reason) {
scoped_refptr<AssociatedGroupController> cached_group_controller;
InterfaceId cached_id = kInvalidInterfaceId;
scoped_refptr<State> cached_peer_state;
{
internal::MayAutoLock locker(&lock_);
if (!association_event_handler_.is_null()) {
association_event_handler_.Reset();
runner_ = nullptr;
}
if (!pending_association_) {
if (IsValidInterfaceId(id_)) {
// Intentionally keep |group_controller_| unchanged.
// That is because the callback created by
// CreateGroupControllerGetter() could still be used after this point,
// potentially from another thread. We would like it to continue
// returning the same group controller.
//
// Imagine there is a ThreadSafeForwarder A:
// (1) On the IO thread, A's underlying associated interface pointer
// is closed.
// (2) On the proxy thread, the user makes a call on A to pass an
// associated request B_asso_req. The callback returned by
// CreateGroupControllerGetter() is used to associate B_asso_req.
// (3) On the proxy thread, the user immediately binds B_asso_ptr_info
// to B_asso_ptr and makes calls on it.
//
// If we reset |group_controller_| in step (1), step (2) won't be able
// to associate B_asso_req. Therefore, in step (3) B_asso_ptr won't be
// able to serialize associated endpoints or send message because it
// is still in "pending_association" state and doesn't have a group
// controller.
//
// We could "address" this issue by ignoring messages if there isn't a
// group controller. But the side effect is that we cannot detect
// programming errors of "using associated interface pointer before
// sending associated request".
cached_group_controller = group_controller_;
cached_id = id_;
id_ = kInvalidInterfaceId;
}
} else {
pending_association_ = false;
cached_peer_state = std::move(peer_state_);
}
}
if (cached_group_controller) {
cached_group_controller->CloseEndpointHandle(cached_id, reason);
} else if (cached_peer_state) {
cached_peer_state->OnPeerClosedBeforeAssociation(reason);
}
}
void SetAssociationEventHandler(AssociationEventCallback handler) {
internal::MayAutoLock locker(&lock_);
if (!pending_association_ && !IsValidInterfaceId(id_))
return;
association_event_handler_ = std::move(handler);
if (association_event_handler_.is_null()) {
runner_ = nullptr;
return;
}
runner_ = base::ThreadTaskRunnerHandle::Get();
if (!pending_association_) {
runner_->PostTask(
FROM_HERE,
base::Bind(
&ScopedInterfaceEndpointHandle::State::RunAssociationEventHandler,
this, runner_, ASSOCIATED));
} else if (!peer_state_) {
runner_->PostTask(
FROM_HERE,
base::Bind(
&ScopedInterfaceEndpointHandle::State::RunAssociationEventHandler,
this, runner_, PEER_CLOSED_BEFORE_ASSOCIATION));
}
}
bool NotifyAssociation(
InterfaceId id,
scoped_refptr<AssociatedGroupController> peer_group_controller) {
scoped_refptr<State> cached_peer_state;
{
internal::MayAutoLock locker(&lock_);
DCHECK(pending_association_);
pending_association_ = false;
cached_peer_state = std::move(peer_state_);
}
if (cached_peer_state) {
cached_peer_state->OnAssociated(id, std::move(peer_group_controller));
return true;
}
return false;
}
bool is_valid() const {
internal::MayAutoLock locker(&lock_);
return pending_association_ || IsValidInterfaceId(id_);
}
bool pending_association() const {
internal::MayAutoLock locker(&lock_);
return pending_association_;
}
InterfaceId id() const {
internal::MayAutoLock locker(&lock_);
return id_;
}
AssociatedGroupController* group_controller() const {
internal::MayAutoLock locker(&lock_);
return group_controller_.get();
}
const base::Optional<DisconnectReason>& disconnect_reason() const {
internal::MayAutoLock locker(&lock_);
return disconnect_reason_;
}
private:
friend class base::RefCountedThreadSafe<State>;
~State() {
DCHECK(!pending_association_);
DCHECK(!IsValidInterfaceId(id_));
}
// Called by the peer, maybe from a different thread.
void OnAssociated(InterfaceId id,
scoped_refptr<AssociatedGroupController> group_controller) {
AssociationEventCallback handler;
{
internal::MayAutoLock locker(&lock_);
// There may be race between Close() of endpoint A and
// NotifyPeerAssociation() of endpoint A_peer on different threads.
// Therefore, it is possible that endpoint A has been closed but it
// still gets OnAssociated() call from its peer.
if (!pending_association_)
return;
pending_association_ = false;
peer_state_ = nullptr;
id_ = id;
group_controller_ = std::move(group_controller);
if (!association_event_handler_.is_null()) {
if (runner_->BelongsToCurrentThread()) {
handler = std::move(association_event_handler_);
runner_ = nullptr;
} else {
runner_->PostTask(FROM_HERE,
base::Bind(&ScopedInterfaceEndpointHandle::State::
RunAssociationEventHandler,
this, runner_, ASSOCIATED));
}
}
}
if (!handler.is_null())
std::move(handler).Run(ASSOCIATED);
}
// Called by the peer, maybe from a different thread.
void OnPeerClosedBeforeAssociation(
const base::Optional<DisconnectReason>& reason) {
AssociationEventCallback handler;
{
internal::MayAutoLock locker(&lock_);
// There may be race between Close()/NotifyPeerAssociation() of endpoint
// A and Close() of endpoint A_peer on different threads.
// Therefore, it is possible that endpoint A is not in pending association
// state but still gets OnPeerClosedBeforeAssociation() call from its
// peer.
if (!pending_association_)
return;
disconnect_reason_ = reason;
// NOTE: This handle itself is still pending.
peer_state_ = nullptr;
if (!association_event_handler_.is_null()) {
if (runner_->BelongsToCurrentThread()) {
handler = std::move(association_event_handler_);
runner_ = nullptr;
} else {
runner_->PostTask(
FROM_HERE,
base::Bind(&ScopedInterfaceEndpointHandle::State::
RunAssociationEventHandler,
this, runner_, PEER_CLOSED_BEFORE_ASSOCIATION));
}
}
}
if (!handler.is_null())
std::move(handler).Run(PEER_CLOSED_BEFORE_ASSOCIATION);
}
void RunAssociationEventHandler(
scoped_refptr<base::SingleThreadTaskRunner> posted_to_runner,
AssociationEvent event) {
AssociationEventCallback handler;
{
internal::MayAutoLock locker(&lock_);
if (posted_to_runner == runner_) {
runner_ = nullptr;
handler = std::move(association_event_handler_);
}
}
if (!handler.is_null())
std::move(handler).Run(event);
}
// Protects the following members if the handle is initially set to pending
// association.
mutable base::Optional<base::Lock> lock_;
bool pending_association_ = false;
base::Optional<DisconnectReason> disconnect_reason_;
scoped_refptr<State> peer_state_;
AssociationEventCallback association_event_handler_;
scoped_refptr<base::SingleThreadTaskRunner> runner_;
InterfaceId id_ = kInvalidInterfaceId;
scoped_refptr<AssociatedGroupController> group_controller_;
DISALLOW_COPY_AND_ASSIGN(State);
};
// ScopedInterfaceEndpointHandle -----------------------------------------------
// static
void ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(
ScopedInterfaceEndpointHandle* handle0,
ScopedInterfaceEndpointHandle* handle1) {
ScopedInterfaceEndpointHandle result0;
ScopedInterfaceEndpointHandle result1;
result0.state_->InitPendingState(result1.state_);
result1.state_->InitPendingState(result0.state_);
*handle0 = std::move(result0);
*handle1 = std::move(result1);
}
ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle()
: state_(new State) {}
ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle(
ScopedInterfaceEndpointHandle&& other)
: state_(new State) {
state_.swap(other.state_);
}
ScopedInterfaceEndpointHandle::~ScopedInterfaceEndpointHandle() {
state_->Close(base::nullopt);
}
ScopedInterfaceEndpointHandle& ScopedInterfaceEndpointHandle::operator=(
ScopedInterfaceEndpointHandle&& other) {
reset();
state_.swap(other.state_);
return *this;
}
bool ScopedInterfaceEndpointHandle::is_valid() const {
return state_->is_valid();
}
bool ScopedInterfaceEndpointHandle::pending_association() const {
return state_->pending_association();
}
InterfaceId ScopedInterfaceEndpointHandle::id() const {
return state_->id();
}
AssociatedGroupController* ScopedInterfaceEndpointHandle::group_controller()
const {
return state_->group_controller();
}
const base::Optional<DisconnectReason>&
ScopedInterfaceEndpointHandle::disconnect_reason() const {
return state_->disconnect_reason();
}
void ScopedInterfaceEndpointHandle::SetAssociationEventHandler(
AssociationEventCallback handler) {
state_->SetAssociationEventHandler(std::move(handler));
}
void ScopedInterfaceEndpointHandle::reset() {
ResetInternal(base::nullopt);
}
void ScopedInterfaceEndpointHandle::ResetWithReason(
uint32_t custom_reason,
const std::string& description) {
ResetInternal(DisconnectReason(custom_reason, description));
}
ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle(
InterfaceId id,
scoped_refptr<AssociatedGroupController> group_controller)
: state_(new State(id, std::move(group_controller))) {
DCHECK(!IsValidInterfaceId(state_->id()) || state_->group_controller());
}
bool ScopedInterfaceEndpointHandle::NotifyAssociation(
InterfaceId id,
scoped_refptr<AssociatedGroupController> peer_group_controller) {
return state_->NotifyAssociation(id, peer_group_controller);
}
void ScopedInterfaceEndpointHandle::ResetInternal(
const base::Optional<DisconnectReason>& reason) {
scoped_refptr<State> new_state(new State);
state_->Close(reason);
state_.swap(new_state);
}
base::Callback<AssociatedGroupController*()>
ScopedInterfaceEndpointHandle::CreateGroupControllerGetter() const {
// We allow this callback to be run on any thread. If this handle is created
// in non-pending state, we don't have a lock but it should still be safe
// because the group controller never changes.
return base::Bind(&State::group_controller, state_);
}
} // namespace mojo