summaryrefslogtreecommitdiffstats
path: root/ipc/glue
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/glue')
-rw-r--r--ipc/glue/BackgroundImpl.cpp7
-rw-r--r--ipc/glue/Endpoint.cpp4
-rw-r--r--ipc/glue/Endpoint.h5
-rw-r--r--ipc/glue/IPCMessageUtilsSpecializations.h4
-rw-r--r--ipc/glue/MessageChannel.cpp8
-rw-r--r--ipc/glue/NodeController.cpp132
-rw-r--r--ipc/glue/NodeController.h6
-rw-r--r--ipc/glue/ProtocolUtils.cpp220
-rw-r--r--ipc/glue/ProtocolUtils.h109
9 files changed, 276 insertions, 219 deletions
diff --git a/ipc/glue/BackgroundImpl.cpp b/ipc/glue/BackgroundImpl.cpp
index bba09c261a..bf040a18a4 100644
--- a/ipc/glue/BackgroundImpl.cpp
+++ b/ipc/glue/BackgroundImpl.cpp
@@ -561,7 +561,12 @@ class ChildImpl final : public BackgroundChildImpl {
#endif
}
- NS_INLINE_DECL_REFCOUNTING(ChildImpl, override)
+ // This type is threadsafe refcounted as actors managed by it may be destroyed
+ // after the thread it is bound to dies, and hold a reference to this object.
+ //
+ // It is _not_ safe to use this type or any methods on it from off of the
+ // thread it was created for.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChildImpl, override)
private:
// Forwarded from BackgroundChild.
diff --git a/ipc/glue/Endpoint.cpp b/ipc/glue/Endpoint.cpp
index 3391f8b359..728c37fa3f 100644
--- a/ipc/glue/Endpoint.cpp
+++ b/ipc/glue/Endpoint.cpp
@@ -57,7 +57,7 @@ UntypedManagedEndpoint::~UntypedManagedEndpoint() {
}
bool UntypedManagedEndpoint::BindCommon(IProtocol* aActor,
- IProtocol* aManager) {
+ IRefCountedProtocol* aManager) {
MOZ_ASSERT(aManager);
if (!mInner) {
NS_WARNING("Cannot bind to invalid endpoint");
@@ -88,7 +88,7 @@ bool UntypedManagedEndpoint::BindCommon(IProtocol* aActor,
mInner.reset();
// Our typed caller will insert the actor into the managed container.
- aActor->SetManagerAndRegister(aManager, id);
+ MOZ_ALWAYS_TRUE(aActor->SetManagerAndRegister(aManager, id));
aManager->GetIPCChannel()->Send(
MakeUnique<IPC::Message>(id, MANAGED_ENDPOINT_BOUND_MESSAGE_TYPE));
diff --git a/ipc/glue/Endpoint.h b/ipc/glue/Endpoint.h
index d7eea94dfc..e4d32a7119 100644
--- a/ipc/glue/Endpoint.h
+++ b/ipc/glue/Endpoint.h
@@ -214,7 +214,7 @@ class UntypedManagedEndpoint {
~UntypedManagedEndpoint() noexcept;
- bool BindCommon(IProtocol* aActor, IProtocol* aManager);
+ bool BindCommon(IProtocol* aActor, IRefCountedProtocol* aManager);
private:
friend struct IPDLParamTraits<UntypedManagedEndpoint>;
@@ -267,7 +267,8 @@ class ManagedEndpoint : public UntypedManagedEndpoint {
ManagedEndpoint(const PrivateIPDLInterface&, IProtocol* aActor)
: UntypedManagedEndpoint(aActor) {}
- bool Bind(const PrivateIPDLInterface&, PFooSide* aActor, IProtocol* aManager,
+ bool Bind(const PrivateIPDLInterface&, PFooSide* aActor,
+ IRefCountedProtocol* aManager,
ManagedContainer<PFooSide>& aContainer) {
if (!BindCommon(aActor, aManager)) {
return false;
diff --git a/ipc/glue/IPCMessageUtilsSpecializations.h b/ipc/glue/IPCMessageUtilsSpecializations.h
index e9742c5c74..ec0620d057 100644
--- a/ipc/glue/IPCMessageUtilsSpecializations.h
+++ b/ipc/glue/IPCMessageUtilsSpecializations.h
@@ -775,6 +775,7 @@ struct ParamTraits<mozilla::net::LinkHeader> {
WriteParam(aWriter, aParam.mSizes);
WriteParam(aWriter, aParam.mType);
WriteParam(aWriter, aParam.mMedia);
+ WriteParam(aWriter, aParam.mAnchor);
WriteParam(aWriter, aParam.mCrossOrigin);
WriteParam(aWriter, aParam.mReferrerPolicy);
WriteParam(aWriter, aParam.mAs);
@@ -812,6 +813,9 @@ struct ParamTraits<mozilla::net::LinkHeader> {
if (!ReadParam(aReader, &aResult->mMedia)) {
return false;
}
+ if (!ReadParam(aReader, &aResult->mAnchor)) {
+ return false;
+ }
if (!ReadParam(aReader, &aResult->mCrossOrigin)) {
return false;
}
diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp
index 16eb897175..544ce59b8e 100644
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -1604,9 +1604,13 @@ nsresult MessageChannel::MessageTask::Run() {
return NS_OK;
}
+ Channel()->AssertWorkerThread();
+ mMonitor->AssertSameMonitor(*Channel()->mMonitor);
+
#ifdef FUZZING_SNAPSHOT
if (!mIsFuzzMsg) {
- if (fuzzing::Nyx::instance().started()) {
+ if (fuzzing::Nyx::instance().started() && XRE_IsParentProcess() &&
+ Channel()->IsCrossProcess()) {
// Once we started fuzzing, prevent non-fuzzing tasks from being
// run and potentially blocking worker threads.
//
@@ -1622,8 +1626,6 @@ nsresult MessageChannel::MessageTask::Run() {
}
#endif
- Channel()->AssertWorkerThread();
- mMonitor->AssertSameMonitor(*Channel()->mMonitor);
proxy = Channel()->Listener()->GetLifecycleProxy();
Channel()->RunMessage(proxy, *this);
diff --git a/ipc/glue/NodeController.cpp b/ipc/glue/NodeController.cpp
index 7781771660..325eff1834 100644
--- a/ipc/glue/NodeController.cpp
+++ b/ipc/glue/NodeController.cpp
@@ -307,67 +307,78 @@ void NodeController::DropPeer(NodeName aNodeName) {
mNode->LostConnectionToNode(aNodeName);
}
-void NodeController::ForwardEvent(const NodeName& aNode,
- UniquePtr<Event> aEvent) {
- if (aNode == mName) {
- (void)mNode->AcceptEvent(mName, std::move(aEvent));
- } else {
- // On Windows and macOS, messages holding HANDLEs or mach ports must be
- // relayed via the broker process so it can transfer ownership.
- bool needsRelay = false;
+void NodeController::ContactRemotePeer(const NodeName& aNode,
+ UniquePtr<Event> aEvent) {
+ // On Windows and macOS, messages holding HANDLEs or mach ports must be
+ // relayed via the broker process so it can transfer ownership.
+ bool needsRelay = false;
#if defined(XP_WIN) || defined(XP_DARWIN)
- if (!IsBroker() && aNode != kBrokerNodeName &&
- aEvent->type() == Event::kUserMessage) {
- auto* userEvent = static_cast<UserMessageEvent*>(aEvent.get());
- needsRelay =
- userEvent->HasMessage() &&
- userEvent->GetMessage<IPC::Message>()->num_relayed_attachments() > 0;
- }
+ if (aEvent && !IsBroker() && aNode != kBrokerNodeName &&
+ aEvent->type() == Event::kUserMessage) {
+ auto* userEvent = static_cast<UserMessageEvent*>(aEvent.get());
+ needsRelay =
+ userEvent->HasMessage() &&
+ userEvent->GetMessage<IPC::Message>()->num_relayed_attachments() > 0;
+ }
#endif
- UniquePtr<IPC::Message> message =
+ UniquePtr<IPC::Message> message;
+ if (aEvent) {
+ message =
SerializeEventMessage(std::move(aEvent), needsRelay ? &aNode : nullptr);
MOZ_ASSERT(message->is_relay() == needsRelay,
"Message relay status set incorrectly");
+ }
- RefPtr<NodeChannel> peer;
- RefPtr<NodeChannel> broker;
- bool needsIntroduction = false;
- {
- auto state = mState.Lock();
+ RefPtr<NodeChannel> peer;
+ RefPtr<NodeChannel> broker;
+ bool needsIntroduction = false;
+ bool needsBroker = needsRelay;
+ {
+ auto state = mState.Lock();
- // Check if we know this peer. If we don't, we'll need to request an
- // introduction.
- peer = state->mPeers.Get(aNode);
- if (!peer || needsRelay) {
- if (IsBroker()) {
- NODECONTROLLER_WARNING("Ignoring message '%s' to unknown peer %s",
- message->name(), ToString(aNode).c_str());
- return;
- }
-
- broker = state->mPeers.Get(kBrokerNodeName);
- if (!broker) {
- NODECONTROLLER_WARNING(
- "Ignoring message '%s' to peer %s due to a missing broker",
- message->name(), ToString(aNode).c_str());
- return;
- }
-
- if (!needsRelay) {
- auto& queue =
- state->mPendingMessages.LookupOrInsertWith(aNode, [&]() {
- needsIntroduction = true;
- return Queue<UniquePtr<IPC::Message>, 64>{};
- });
- queue.Push(std::move(message));
- }
+ // Check if we know this peer. If we don't, we'll need to request an
+ // introduction.
+ peer = state->mPeers.Get(aNode);
+ if (!peer) {
+ // We don't know the peer, check if we've already requested an
+ // introduction, or if we need to request a new one.
+ auto& queue = state->mPendingMessages.LookupOrInsertWith(aNode, [&]() {
+ needsIntroduction = true;
+ needsBroker = true;
+ return Queue<UniquePtr<IPC::Message>, 64>{};
+ });
+ // If we aren't relaying, queue up the message to be sent.
+ if (message && !needsRelay) {
+ queue.Push(std::move(message));
}
}
- MOZ_ASSERT(!needsIntroduction || !needsRelay,
- "Only one of the two should ever be set");
+ if (needsBroker && !IsBroker()) {
+ broker = state->mPeers.Get(kBrokerNodeName);
+ }
+ }
+ if (needsBroker && !broker) {
+ NODECONTROLLER_WARNING(
+ "Dropping message '%s'; no connection to unknown peer %s",
+ message ? message->name() : "<null>", ToString(aNode).c_str());
+ if (needsIntroduction) {
+ // We have no broker and will never be able to be introduced to this node.
+ // Queue a task to clean up any ports connected to it.
+ XRE_GetIOMessageLoop()->PostTask(NewRunnableMethod<NodeName>(
+ "NodeController::DropPeer", this, &NodeController::DropPeer, aNode));
+ }
+ return;
+ }
+
+ if (needsIntroduction) {
+ NODECONTROLLER_LOG(LogLevel::Info, "Requesting introduction to peer %s",
+ ToString(aNode).c_str());
+ broker->RequestIntroduction(aNode);
+ }
+
+ if (message) {
if (needsRelay) {
NODECONTROLLER_LOG(LogLevel::Info,
"Relaying message '%s' for peer %s due to %" PRIu32
@@ -376,15 +387,22 @@ void NodeController::ForwardEvent(const NodeName& aNode,
message->num_relayed_attachments());
MOZ_ASSERT(message->num_relayed_attachments() > 0 && broker);
broker->SendEventMessage(std::move(message));
- } else if (needsIntroduction) {
- MOZ_ASSERT(broker);
- broker->RequestIntroduction(aNode);
} else if (peer) {
peer->SendEventMessage(std::move(message));
}
}
}
+void NodeController::ForwardEvent(const NodeName& aNode,
+ UniquePtr<Event> aEvent) {
+ MOZ_ASSERT(aEvent, "cannot forward null event");
+ if (aNode == mName) {
+ (void)mNode->AcceptEvent(mName, std::move(aEvent));
+ } else {
+ ContactRemotePeer(aNode, std::move(aEvent));
+ }
+}
+
void NodeController::BroadcastEvent(UniquePtr<Event> aEvent) {
UniquePtr<IPC::Message> message =
SerializeEventMessage(std::move(aEvent), nullptr, BROADCAST_MESSAGE_TYPE);
@@ -415,6 +433,11 @@ void NodeController::PortStatusChanged(const PortRef& aPortRef) {
}
}
+void NodeController::ObserveRemoteNode(const NodeName& aNode) {
+ MOZ_ASSERT(aNode != mName);
+ ContactRemotePeer(aNode, nullptr);
+}
+
void NodeController::OnEventMessage(const NodeName& aFromNode,
UniquePtr<IPC::Message> aMessage) {
AssertIOThread();
@@ -715,8 +738,6 @@ void NodeController::OnAcceptInvite(const NodeName& aFromNode,
Invite invite;
{
auto state = mState.Lock();
- MOZ_ASSERT(state->mPendingMessages.IsEmpty(),
- "Shouldn't have pending messages in broker");
// Try to remove the source node from our invites list and insert it into
// our peers map under the new name.
@@ -840,6 +861,9 @@ void NodeController::CleanUp() {
lostConnections.AppendElement(chan.GetKey());
channelsToClose.AppendElement(chan.GetData());
}
+ for (const auto& pending : state->mPendingMessages.Keys()) {
+ lostConnections.AppendElement(pending);
+ }
for (const auto& invite : state->mInvites.Values()) {
channelsToClose.AppendElement(invite.mChannel);
portsToClose.AppendElement(invite.mToMerge);
diff --git a/ipc/glue/NodeController.h b/ipc/glue/NodeController.h
index 5356b85084..c1c2d33750 100644
--- a/ipc/glue/NodeController.h
+++ b/ipc/glue/NodeController.h
@@ -121,6 +121,11 @@ class NodeController final : public mojo::core::ports::NodeDelegate,
// Stop communicating with this peer. Must be called on the IO thread.
void DropPeer(NodeName aNodeName);
+ // Ensure that there is a direct connection to a remote node, requesting an
+ // introduction if there is not.
+ // If provided, will optionally send an event to the remote node.
+ void ContactRemotePeer(const NodeName& aNode, UniquePtr<Event> aEvent);
+
// Message Handlers
void OnEventMessage(const NodeName& aFromNode,
UniquePtr<IPC::Message> aMessage) override;
@@ -138,6 +143,7 @@ class NodeController final : public mojo::core::ports::NodeDelegate,
void ForwardEvent(const NodeName& aNode, UniquePtr<Event> aEvent) override;
void BroadcastEvent(UniquePtr<Event> aEvent) override;
void PortStatusChanged(const PortRef& aPortRef) override;
+ void ObserveRemoteNode(const NodeName& aNode) override;
const NodeName mName;
const UniquePtr<Node> mNode;
diff --git a/ipc/glue/ProtocolUtils.cpp b/ipc/glue/ProtocolUtils.cpp
index b29221d476..42bab98320 100644
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -251,12 +251,6 @@ ActorLifecycleProxy::ActorLifecycleProxy(IProtocol* aActor) : mActor(aActor) {
MOZ_ASSERT(mActor->CanSend(),
"Cannot create LifecycleProxy for non-connected actor!");
- // Take a reference to our manager's lifecycle proxy to try to hold it &
- // ensure it doesn't die before us.
- if (mActor->mManager) {
- mManager = mActor->mManager->mLifecycleProxy;
- }
-
// Record that we've taken our first reference to our actor.
mActor->ActorAlloc();
}
@@ -321,6 +315,7 @@ IProtocol::~IProtocol() {
// Gecko, simply emit a warning and clear the weak backreference from our
// LifecycleProxy back to us.
if (mLifecycleProxy) {
+ MOZ_ASSERT(mLinkStatus != LinkStatus::Inactive);
NS_WARNING(
nsPrintfCString("Actor destructor for '%s%s' called before IPC "
"lifecycle complete!\n"
@@ -329,34 +324,13 @@ IProtocol::~IProtocol() {
.get());
mLifecycleProxy->mActor = nullptr;
-
- // If we are somehow being destroyed while active, make sure that the
- // existing IPC reference has been freed. If the status of the actor is
- // `Destroyed`, the reference has already been freed, and we shouldn't free
- // it a second time.
- MOZ_ASSERT(mLinkStatus != LinkStatus::Inactive);
- if (mLinkStatus != LinkStatus::Destroyed) {
- NS_IF_RELEASE(mLifecycleProxy);
- }
mLifecycleProxy = nullptr;
}
}
// The following methods either directly forward to the toplevel protocol, or
// almost directly do.
-int32_t IProtocol::Register(IProtocol* aRouted) {
- return mToplevel->Register(aRouted);
-}
-int32_t IProtocol::RegisterID(IProtocol* aRouted, int32_t aId) {
- return mToplevel->RegisterID(aRouted, aId);
-}
IProtocol* IProtocol::Lookup(int32_t aId) { return mToplevel->Lookup(aId); }
-void IProtocol::Unregister(int32_t aId) {
- if (aId == mId) {
- mId = kFreedActorId;
- }
- return mToplevel->Unregister(aId);
-}
Shmem::SharedMemory* IProtocol::CreateSharedMemory(size_t aSize, bool aUnsafe,
int32_t* aId) {
@@ -383,11 +357,6 @@ nsISerialEventTarget* IProtocol::GetActorEventTarget() {
return GetIPCChannel()->GetWorkerEventTarget();
}
-void IProtocol::SetId(int32_t aId) {
- MOZ_ASSERT(mId == aId || mLinkStatus == LinkStatus::Inactive);
- mId = aId;
-}
-
Maybe<IProtocol*> IProtocol::ReadActor(IPC::MessageReader* aReader,
bool aNullable,
const char* aActorDescription,
@@ -487,26 +456,60 @@ bool IProtocol::DeallocShmem(Shmem& aMem) {
return ok;
}
-void IProtocol::SetManager(IProtocol* aManager) {
+void IProtocol::SetManager(IRefCountedProtocol* aManager) {
MOZ_RELEASE_ASSERT(!mManager || mManager == aManager);
mManager = aManager;
mToplevel = aManager->mToplevel;
}
-void IProtocol::SetManagerAndRegister(IProtocol* aManager) {
- // Set the manager prior to registering so registering properly inherits
- // the manager's event target.
- SetManager(aManager);
+bool IProtocol::SetManagerAndRegister(IRefCountedProtocol* aManager,
+ int32_t aId) {
+ MOZ_RELEASE_ASSERT(mLinkStatus == LinkStatus::Inactive,
+ "Actor must be inactive to SetManagerAndRegister");
- aManager->Register(this);
-}
+ // Set to `false` if the actor is to be torn down after registration.
+ bool success = true;
-void IProtocol::SetManagerAndRegister(IProtocol* aManager, int32_t aId) {
// Set the manager prior to registering so registering properly inherits
// the manager's event target.
SetManager(aManager);
- aManager->RegisterID(this, aId);
+ mId = aId == kNullActorId ? mToplevel->NextId() : aId;
+ while (mToplevel->mActorMap.Contains(mId)) {
+ // The ID already existing is an error case, but we want to proceed with
+ // registration so that we can tear down the actor cleanly - generate a new
+ // ID for that case.
+ NS_WARNING("Actor already exists with the selected ID!");
+ mId = mToplevel->NextId();
+ success = false;
+ }
+
+ RefPtr<ActorLifecycleProxy> proxy = ActorConnected();
+ mToplevel->mActorMap.InsertOrUpdate(mId, proxy);
+ MOZ_ASSERT(proxy->Get() == this);
+
+ // If our manager is already dying, mark ourselves as doomed as well.
+ if (aManager && aManager->mLinkStatus != LinkStatus::Connected) {
+ mLinkStatus = LinkStatus::Doomed;
+ if (aManager->mLinkStatus != LinkStatus::Doomed) {
+ // Our manager is already fully dead, make sure we call
+ // `ActorDisconnected`.
+ success = false;
+ }
+ }
+
+ // If setting the manager failed, call `ActorDisconnected` and return false.
+ if (!success) {
+ ActorDisconnected(FailedConstructor);
+ MOZ_ASSERT(mLinkStatus == LinkStatus::Destroyed);
+ return false;
+ }
+ return true;
+}
+
+void IProtocol::UnlinkManager() {
+ mToplevel = nullptr;
+ mManager = nullptr;
}
bool IProtocol::ChannelSend(UniquePtr<IPC::Message> aMsg) {
@@ -540,9 +543,9 @@ void IProtocol::WarnMessageDiscarded(IPC::Message* aMsg) {
}
#endif
-void IProtocol::ActorConnected() {
+already_AddRefed<ActorLifecycleProxy> IProtocol::ActorConnected() {
if (mLinkStatus != LinkStatus::Inactive) {
- return;
+ return nullptr;
}
#ifdef FUZZING_SNAPSHOT
@@ -552,73 +555,81 @@ void IProtocol::ActorConnected() {
mLinkStatus = LinkStatus::Connected;
MOZ_ASSERT(!mLifecycleProxy, "double-connecting live actor");
- mLifecycleProxy = new ActorLifecycleProxy(this);
- NS_ADDREF(mLifecycleProxy); // Reference freed in DestroySubtree();
+ RefPtr<ActorLifecycleProxy> proxy = new ActorLifecycleProxy(this);
+ mLifecycleProxy = proxy;
+ return proxy.forget();
}
-void IProtocol::DoomSubtree() {
- MOZ_ASSERT(CanSend(), "dooming non-connected actor");
- MOZ_ASSERT(mLifecycleProxy, "dooming zombie actor");
-
- nsTArray<RefPtr<ActorLifecycleProxy>> managed;
- AllManagedActors(managed);
- for (ActorLifecycleProxy* proxy : managed) {
- // Guard against actor being disconnected or destroyed during previous Doom
- IProtocol* actor = proxy->Get();
- if (actor && actor->CanSend()) {
- actor->DoomSubtree();
- }
+void IProtocol::ActorDisconnected(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(mLifecycleProxy, "destroying zombie actor");
+ // If the actor has already been marked as `Destroyed`, there's nothing to do.
+ if (mLinkStatus != LinkStatus::Connected &&
+ mLinkStatus != LinkStatus::Doomed) {
+ return;
}
- // ActorDoom is called immediately before changing state, this allows messages
- // to be sent during ActorDoom immediately before the channel is closed and
- // sending messages is disabled.
- ActorDoom();
- mLinkStatus = LinkStatus::Doomed;
-}
+ // Mark the entire subtree as doomed so that no further messages can be
+ // sent/recieved, and newly created managed actors are immediately marked as
+ // doomed on creation.
+ DoomSubtree();
-void IProtocol::DestroySubtree(ActorDestroyReason aWhy) {
- MOZ_ASSERT(CanRecv(), "destroying non-connected actor");
- MOZ_ASSERT(mLifecycleProxy, "destroying zombie actor");
+ // Perform the steps to fully destroy an actor after it has been unregistered
+ // from its manager.
+ auto doActorDestroy = [toplevel = mToplevel, ipcChannel = GetIPCChannel()](
+ IProtocol* actor, ActorDestroyReason why) {
+ MOZ_ASSERT(actor->mLinkStatus == LinkStatus::Doomed,
+ "Actor must be doomed when calling doActorDestroy");
+ MOZ_ASSERT(actor->AllManagedActorsCount() == 0,
+ "All managed actors must have been destroyed first");
+
+ // Mark the actor as Destroyed, ensuring we can't re-enter `ActorDestroy`,
+ // even if an callback spins a nested event loop.
+ actor->mLinkStatus = LinkStatus::Destroyed;
#ifdef FUZZING_SNAPSHOT
- fuzzing::IPCFuzzController::instance().OnActorDestroyed(this);
+ fuzzing::IPCFuzzController::instance().OnActorDestroyed(actor);
#endif
- int32_t id = Id();
+ int32_t id = actor->mId;
+ if (IProtocol* manager = actor->Manager()) {
+ actor->mId = kFreedActorId;
+ auto entry = toplevel->mActorMap.Lookup(id);
+ MOZ_DIAGNOSTIC_ASSERT(entry && *entry == actor->GetLifecycleProxy(),
+ "ID must be present and reference this actor");
+ entry.Remove();
+ manager->RemoveManagee(actor->GetProtocolId(), actor);
+ }
+
+ ipcChannel->RejectPendingResponsesForActor(id);
+ actor->ActorDestroy(why);
+ };
- // If we're a managed actor, unregister from our manager
- if (Manager()) {
- Unregister(id);
- }
+ // Hold all ActorLifecycleProxy instances for managed actors until we return.
+ nsTArray<RefPtr<ActorLifecycleProxy>> proxyHolder;
+ proxyHolder.AppendElement(GetLifecycleProxy());
- // Destroy subtree
+ // Invoke `ActorDestroy` for all managed actors in the subtree. These are
+ // handled one at a time, so that new actors which are potentially registered
+ // during `ActorDestroy` callbacks are not missed.
ActorDestroyReason subtreeWhy = aWhy;
if (aWhy == Deletion || aWhy == FailedConstructor) {
subtreeWhy = AncestorDeletion;
}
-
- nsTArray<RefPtr<ActorLifecycleProxy>> managed;
- AllManagedActors(managed);
- for (ActorLifecycleProxy* proxy : managed) {
- // Guard against actor being disconnected or destroyed during previous
- // Destroy
- IProtocol* actor = proxy->Get();
- if (actor && actor->CanRecv()) {
- actor->DestroySubtree(subtreeWhy);
+ while (IProtocol* actor = PeekManagedActor()) {
+ // If the selected actor manages other actors, destroy those first.
+ while (IProtocol* inner = actor->PeekManagedActor()) {
+ actor = inner;
}
- }
- // Ensure that we don't send any messages while we're calling `ActorDestroy`
- // by setting our state to `Doomed`.
- mLinkStatus = LinkStatus::Doomed;
+ proxyHolder.AppendElement(actor->GetLifecycleProxy());
+ doActorDestroy(actor, subtreeWhy);
+ }
- // The actor is being destroyed, reject any pending responses, invoke
- // `ActorDestroy` to destroy it, and then clear our status to
- // `LinkStatus::Destroyed`.
- GetIPCChannel()->RejectPendingResponsesForActor(id);
- ActorDestroy(aWhy);
- mLinkStatus = LinkStatus::Destroyed;
+ // Destroy ourselves if we were not not otherwise destroyed while destroying
+ // managed actors.
+ if (mLinkStatus == LinkStatus::Doomed) {
+ doActorDestroy(this, aWhy);
+ }
}
IToplevelProtocol::IToplevelProtocol(const char* aName, ProtocolId aProtoId,
@@ -689,28 +700,11 @@ int32_t IToplevelProtocol::NextId() {
return (++mLastLocalId << 2) | tag;
}
-int32_t IToplevelProtocol::Register(IProtocol* aRouted) {
- if (aRouted->Id() != kNullActorId && aRouted->Id() != kFreedActorId) {
- // If there's already an ID, just return that.
- return aRouted->Id();
+IProtocol* IToplevelProtocol::Lookup(int32_t aId) {
+ if (auto entry = mActorMap.Lookup(aId)) {
+ return entry.Data()->Get();
}
- return RegisterID(aRouted, NextId());
-}
-
-int32_t IToplevelProtocol::RegisterID(IProtocol* aRouted, int32_t aId) {
- aRouted->SetId(aId);
- aRouted->ActorConnected();
- MOZ_ASSERT(!mActorMap.Contains(aId), "Don't insert with an existing ID");
- mActorMap.InsertOrUpdate(aId, aRouted);
- return aId;
-}
-
-IProtocol* IToplevelProtocol::Lookup(int32_t aId) { return mActorMap.Get(aId); }
-
-void IToplevelProtocol::Unregister(int32_t aId) {
- MOZ_ASSERT(mActorMap.Contains(aId),
- "Attempting to remove an ID not in the actor map");
- mActorMap.Remove(aId);
+ return nullptr;
}
Shmem::SharedMemory* IToplevelProtocol::CreateSharedMemory(size_t aSize,
diff --git a/ipc/glue/ProtocolUtils.h b/ipc/glue/ProtocolUtils.h
index 2800a41c91..995ef2f256 100644
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -124,12 +124,16 @@ enum class LinkStatus : uint8_t {
// A live link is connected to the other side of this actor.
Connected,
- // The link has begun being destroyed. Messages may still be received, but
- // cannot be sent. (exception: sync replies may be sent while Doomed).
+ // The link has begun being destroyed. Messages may no longer be sent. The
+ // ActorDestroy method is queued to be called, but has not been invoked yet,
+ // as managed actors still need to be destroyed first.
+ //
+ // NOTE: While no new IPC can be received at this point, `CanRecv` will still
+ // be true until `LinkStatus::Destroyed`.
Doomed,
- // The link has been destroyed, and messages will no longer be sent or
- // received.
+ // The actor has been destroyed, and ActorDestroy has been called, however an
+ // ActorLifecycleProxy still holds a reference to the actor.
Destroyed,
};
@@ -171,12 +175,8 @@ class IProtocol : public HasResultCodes {
IToplevelProtocol* ToplevelProtocol() { return mToplevel; }
const IToplevelProtocol* ToplevelProtocol() const { return mToplevel; }
- // The following methods either directly forward to the toplevel protocol, or
- // almost directly do.
- int32_t Register(IProtocol* aRouted);
- int32_t RegisterID(IProtocol* aRouted, int32_t aId);
+ // Lookup() is forwarded directly to the toplevel protocol.
IProtocol* Lookup(int32_t aId);
- void Unregister(int32_t aId);
Shmem::SharedMemory* CreateSharedMemory(size_t aSize, bool aUnsafe,
int32_t* aId);
@@ -197,13 +197,16 @@ class IProtocol : public HasResultCodes {
const char* GetProtocolName() const { return ProtocolIdToName(mProtocolId); }
int32_t Id() const { return mId; }
- IProtocol* Manager() const { return mManager; }
+ IRefCountedProtocol* Manager() const { return mManager; }
ActorLifecycleProxy* GetLifecycleProxy() { return mLifecycleProxy; }
WeakActorLifecycleProxy* GetWeakLifecycleProxy();
Side GetSide() const { return mSide; }
bool CanSend() const { return mLinkStatus == LinkStatus::Connected; }
+
+ // Returns `true` for an active actor until the actor's `ActorDestroy` method
+ // has been called.
bool CanRecv() const {
return mLinkStatus == LinkStatus::Connected ||
mLinkStatus == LinkStatus::Doomed;
@@ -235,17 +238,20 @@ class IProtocol : public HasResultCodes {
friend class IPDLResolverInner;
friend class UntypedManagedEndpoint;
- void SetId(int32_t aId);
+ // We have separate functions because the accessibility code and BrowserParent
+ // manually calls SetManager.
+ void SetManager(IRefCountedProtocol* aManager);
- // We have separate functions because the accessibility code manually
- // calls SetManager.
- void SetManager(IProtocol* aManager);
+ // Clear `mManager` and `mToplevel` to nullptr. Only intended to be called
+ // within the unlink implementation of cycle collected IPDL actors with cycle
+ // collected managers.
+ void UnlinkManager();
// Sets the manager for the protocol and registers the protocol with
// its manager, setting up channels for the protocol as well. Not
// for use outside of IPDL.
- void SetManagerAndRegister(IProtocol* aManager);
- void SetManagerAndRegister(IProtocol* aManager, int32_t aId);
+ bool SetManagerAndRegister(IRefCountedProtocol* aManager,
+ int32_t aId = kNullActorId);
// Helpers for calling `Send` on our underlying IPC channel.
bool ChannelSend(UniquePtr<IPC::Message> aMsg);
@@ -265,28 +271,31 @@ class IProtocol : public HasResultCodes {
}
}
- // Collect all actors managed by this object in an array. To make this safer
- // to iterate, `ActorLifecycleProxy` references are returned rather than raw
- // actor pointers.
- virtual void AllManagedActors(
- nsTArray<RefPtr<ActorLifecycleProxy>>& aActors) const = 0;
-
virtual uint32_t AllManagedActorsCount() const = 0;
// Internal method called when the actor becomes connected.
- void ActorConnected();
+ already_AddRefed<ActorLifecycleProxy> ActorConnected();
+
+ // Internal method called when actor becomes disconnected.
+ void ActorDisconnected(ActorDestroyReason aWhy);
+
+ // Called by DoomSubtree on each managed actor to mark it as Doomed and
+ // prevent further IPC.
+ void SetDoomed() {
+ MOZ_ASSERT(mLinkStatus == LinkStatus::Connected ||
+ mLinkStatus == LinkStatus::Doomed,
+ "Invalid link status for SetDoomed");
+ mLinkStatus = LinkStatus::Doomed;
+ }
+ virtual void DoomSubtree() = 0;
- // Called immediately before setting the actor state to doomed, and triggering
- // async actor destruction. Messages may be sent from this callback, but no
- // later.
- // FIXME(nika): This is currently unused!
- virtual void ActorDoom() {}
- void DoomSubtree();
+ // Internal function returning an arbitrary directly managed actor. Used to
+ // identify managed actors to destroy when tearing down an actor tree.
+ virtual IProtocol* PeekManagedActor() = 0;
// Called when the actor has been destroyed due to an error, a __delete__
// message, or a __doom__ reply.
virtual void ActorDestroy(ActorDestroyReason aWhy) {}
- void DestroySubtree(ActorDestroyReason aWhy);
// Called when IPC has acquired its first reference to the actor. This method
// may take references which will later be freed by `ActorDealloc`.
@@ -313,7 +322,7 @@ class IProtocol : public HasResultCodes {
Side mSide;
LinkStatus mLinkStatus;
ActorLifecycleProxy* mLifecycleProxy;
- IProtocol* mManager;
+ RefPtr<IRefCountedProtocol> mManager;
IToplevelProtocol* mToplevel;
};
@@ -414,6 +423,7 @@ class IRefCountedProtocol : public IProtocol {
* this protocol actor.
*/
class IToplevelProtocol : public IRefCountedProtocol {
+ friend class IProtocol;
template <class PFooSide>
friend class Endpoint;
@@ -423,12 +433,8 @@ class IToplevelProtocol : public IRefCountedProtocol {
~IToplevelProtocol() = default;
public:
- // Shadow methods on IProtocol which are implemented directly on toplevel
- // actors.
- int32_t Register(IProtocol* aRouted);
- int32_t RegisterID(IProtocol* aRouted, int32_t aId);
+ // Shadows the method on IProtocol, which will forward to the top.
IProtocol* Lookup(int32_t aId);
- void Unregister(int32_t aId);
Shmem::SharedMemory* CreateSharedMemory(size_t aSize, bool aUnsafe,
int32_t* aId);
@@ -441,8 +447,6 @@ class IToplevelProtocol : public IRefCountedProtocol {
void SetOtherProcessId(base::ProcessId aOtherPid);
- virtual void OnChannelClose() = 0;
- virtual void OnChannelError() = 0;
virtual void ProcessingError(Result aError, const char* aMsgName) {}
bool Open(ScopedPort aPort, const nsID& aMessageChannelId,
@@ -508,7 +512,26 @@ class IToplevelProtocol : public IRefCountedProtocol {
virtual void OnChannelReceivedMessage(const Message& aMsg) {}
- void OnIPCChannelOpened() { ActorConnected(); }
+ // MessageChannel lifecycle callbacks.
+ void OnIPCChannelOpened() {
+ // Leak the returned ActorLifecycleProxy reference. It will be destroyed in
+ // `OnChannelClose` or `OnChannelError`.
+ Unused << ActorConnected();
+ }
+ void OnChannelClose() {
+ // Re-acquire the ActorLifecycleProxy reference acquired in
+ // OnIPCChannelOpened.
+ RefPtr<ActorLifecycleProxy> proxy = dont_AddRef(GetLifecycleProxy());
+ ActorDisconnected(NormalShutdown);
+ DeallocShmems();
+ }
+ void OnChannelError() {
+ // Re-acquire the ActorLifecycleProxy reference acquired in
+ // OnIPCChannelOpened.
+ RefPtr<ActorLifecycleProxy> proxy = dont_AddRef(GetLifecycleProxy());
+ ActorDisconnected(AbnormalShutdown);
+ DeallocShmems();
+ }
base::ProcessId OtherPidMaybeInvalid() const { return mOtherPid; }
@@ -523,7 +546,7 @@ class IToplevelProtocol : public IRefCountedProtocol {
// NOTE NOTE NOTE
// Used to be on mState
int32_t mLastLocalId;
- IDMap<IProtocol*> mActorMap;
+ IDMap<RefPtr<ActorLifecycleProxy>> mActorMap;
IDMap<RefPtr<Shmem::SharedMemory>> mShmemMap;
MessageChannel mChannel;
@@ -652,10 +675,6 @@ class ActorLifecycleProxy {
IProtocol* MOZ_NON_OWNING_REF mActor;
- // Hold a reference to the actor's manager's ActorLifecycleProxy to help
- // prevent it from dying while we're still alive!
- RefPtr<ActorLifecycleProxy> mManager;
-
// When requested, the current self-referencing weak reference for this
// ActorLifecycleProxy.
RefPtr<WeakActorLifecycleProxy> mWeakProxy;
@@ -753,6 +772,8 @@ class ManagedContainer {
}
}
+ Protocol* Peek() { return mArray.IsEmpty() ? nullptr : mArray.LastElement(); }
+
void Clear() { mArray.Clear(); }
private: