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 |