| Index: mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc
|
| diff --git a/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc b/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc
|
| index 1dffcb3f1eed05af373da344b7d83a4d409c08e8..45add014abbf2e26e1ad5f626df1500e32b0d765 100644
|
| --- a/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc
|
| +++ b/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc
|
| @@ -4,82 +4,380 @@
|
|
|
| #include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
|
|
|
| +#include "base/bind.h"
|
| #include "base/logging.h"
|
| +#include "base/memory/ptr_util.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_ = base::MakeUnique<base::Lock>();
|
| + 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_.get());
|
| +
|
| + 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_.get());
|
| +
|
| + 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_.get());
|
| +
|
| + 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_.get());
|
| + return pending_association_ || IsValidInterfaceId(id_);
|
| + }
|
| +
|
| + bool pending_association() const {
|
| + internal::MayAutoLock locker(lock_.get());
|
| + return pending_association_;
|
| + }
|
| +
|
| + InterfaceId id() const {
|
| + internal::MayAutoLock locker(lock_.get());
|
| + return id_;
|
| + }
|
| +
|
| + AssociatedGroupController* group_controller() const {
|
| + internal::MayAutoLock locker(lock_.get());
|
| + return group_controller_.get();
|
| + }
|
| +
|
| + const base::Optional<DisconnectReason>& disconnect_reason() const {
|
| + internal::MayAutoLock locker(lock_.get());
|
| + 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_.get());
|
| +
|
| + // 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_.get());
|
| +
|
| + // 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_.get());
|
| + 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.
|
| + std::unique_ptr<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()
|
| - : ScopedInterfaceEndpointHandle(kInvalidInterfaceId, true, nullptr) {}
|
| + : state_(new State) {}
|
|
|
| ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle(
|
| ScopedInterfaceEndpointHandle&& other)
|
| - : id_(other.id_), is_local_(other.is_local_) {
|
| - group_controller_.swap(other.group_controller_);
|
| - other.id_ = kInvalidInterfaceId;
|
| + : state_(new State) {
|
| + state_.swap(other.state_);
|
| }
|
|
|
| ScopedInterfaceEndpointHandle::~ScopedInterfaceEndpointHandle() {
|
| - reset();
|
| + state_->Close(base::nullopt);
|
| }
|
|
|
| ScopedInterfaceEndpointHandle& ScopedInterfaceEndpointHandle::operator=(
|
| ScopedInterfaceEndpointHandle&& other) {
|
| reset();
|
| - swap(other);
|
| -
|
| + state_.swap(other.state_);
|
| return *this;
|
| }
|
|
|
| -void ScopedInterfaceEndpointHandle::reset() {
|
| - ResetInternal(base::nullopt);
|
| +bool ScopedInterfaceEndpointHandle::is_valid() const {
|
| + return state_->is_valid();
|
| }
|
|
|
| -void ScopedInterfaceEndpointHandle::ResetWithReason(
|
| - uint32_t custom_reason,
|
| - const std::string& description) {
|
| - base::Optional<DisconnectReason> reason;
|
| - reason.emplace(custom_reason, description);
|
| - ResetInternal(reason);
|
| +bool ScopedInterfaceEndpointHandle::pending_association() const {
|
| + return state_->pending_association();
|
| }
|
|
|
| -void ScopedInterfaceEndpointHandle::swap(ScopedInterfaceEndpointHandle& other) {
|
| - using std::swap;
|
| - swap(other.id_, id_);
|
| - swap(other.is_local_, is_local_);
|
| - swap(other.group_controller_, group_controller_);
|
| +InterfaceId ScopedInterfaceEndpointHandle::id() const {
|
| + return state_->id();
|
| }
|
|
|
| -InterfaceId ScopedInterfaceEndpointHandle::release() {
|
| - InterfaceId result = id_;
|
| +AssociatedGroupController* ScopedInterfaceEndpointHandle::group_controller()
|
| + const {
|
| + return state_->group_controller();
|
| +}
|
|
|
| - id_ = kInvalidInterfaceId;
|
| - is_local_ = true;
|
| - group_controller_ = nullptr;
|
| +const base::Optional<DisconnectReason>&
|
| +ScopedInterfaceEndpointHandle::disconnect_reason() const {
|
| + return state_->disconnect_reason();
|
| +}
|
|
|
| - return result;
|
| +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,
|
| - bool is_local,
|
| scoped_refptr<AssociatedGroupController> group_controller)
|
| - : id_(id),
|
| - is_local_(is_local),
|
| - group_controller_(std::move(group_controller)) {
|
| - DCHECK(!IsValidInterfaceId(id) || 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) {
|
| - if (!IsValidInterfaceId(id_))
|
| - return;
|
| -
|
| - group_controller_->CloseEndpointHandle(id_, is_local_, reason);
|
| + scoped_refptr<State> new_state(new State);
|
| + state_->Close(reason);
|
| + state_.swap(new_state);
|
| +}
|
|
|
| - id_ = kInvalidInterfaceId;
|
| - is_local_ = true;
|
| - group_controller_ = nullptr;
|
| +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
|
|
|