Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1154)

Unified Diff: mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc

Issue 2646853003: Mojo C++ bindings: Simplify associated interface API. (Closed)
Patch Set: . Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698