Bug 1513118 Part 1 - Allow simultaneous middleman calls from multiple child processes, r=lsmyth.
☠☠ backed out by 9e3564442734 ☠ ☠
authorBrian Hackett <bhackett1024@gmail.com>
Sat, 29 Dec 2018 08:22:13 -1000
changeset 453661 9417ce02d4f2
parent 453660 ec9a7aa1898c
child 453662 5ce216ffcb1d
push id35365
push userdvarga@mozilla.com
push dateSun, 13 Jan 2019 10:05:55 +0000
treeherdermozilla-central@1218e374fbc7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslsmyth
bugs1513118
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1513118 Part 1 - Allow simultaneous middleman calls from multiple child processes, r=lsmyth.
toolkit/recordreplay/MiddlemanCall.cpp
toolkit/recordreplay/MiddlemanCall.h
toolkit/recordreplay/ipc/Channel.h
toolkit/recordreplay/ipc/ChildProcess.cpp
--- a/toolkit/recordreplay/MiddlemanCall.cpp
+++ b/toolkit/recordreplay/MiddlemanCall.cpp
@@ -6,36 +6,52 @@
 
 #include "MiddlemanCall.h"
 
 #include <unordered_map>
 
 namespace mozilla {
 namespace recordreplay {
 
-// In a replaying or middleman process, all middleman calls that have been
-// encountered, indexed by their ID.
-static StaticInfallibleVector<MiddlemanCall*> gMiddlemanCalls;
+typedef std::unordered_map<const void*, MiddlemanCall*> MiddlemanCallMap;
+
+// State used for keeping track of middleman calls in either a replaying
+// process or middleman process.
+struct MiddlemanCallState {
+  // In a replaying or middleman process, all middleman calls that have been
+  // encountered, indexed by their ID.
+  InfallibleVector<MiddlemanCall*> mCalls;
+
+  // In a replaying or middleman process, association between values produced by
+  // a middleman call and the call itself.
+  MiddlemanCallMap mCallMap;
 
-// In a replaying or middleman process, association between values produced by
-// a middleman call and the call itself.
-typedef std::unordered_map<const void*, MiddlemanCall*> MiddlemanCallMap;
-static MiddlemanCallMap* gMiddlemanCallMap;
+  // In a middleman process, any buffers allocated for performed calls.
+  InfallibleVector<void*> mAllocatedBuffers;
+};
 
-// In a middleman process, any buffers allocated for performed calls.
-static StaticInfallibleVector<void*> gAllocatedBuffers;
+// In a replaying process, all middleman call state. In a middleman process,
+// state for the child currently being processed.
+static MiddlemanCallState* gState;
 
-// Lock protecting middleman call state.
+// In a middleman process, middleman call state for each child process, indexed
+// by the child ID.
+static StaticInfallibleVector<MiddlemanCallState*> gStatePerChild;
+
+// In a replaying process, lock protecting middleman call state. In the
+// middleman, all accesses occur on the main thread.
 static Monitor* gMonitor;
 
 void InitializeMiddlemanCalls() {
   MOZ_RELEASE_ASSERT(IsRecordingOrReplaying() || IsMiddleman());
 
-  gMiddlemanCallMap = new MiddlemanCallMap();
-  gMonitor = new Monitor();
+  if (IsReplaying()) {
+    gState = new MiddlemanCallState();
+    gMonitor = new Monitor();
+  }
 }
 
 // Apply the ReplayInput phase to aCall and any calls it depends on that have
 // not been sent to the middleman yet, filling aOutgoingCalls with the set of
 // such calls.
 static bool GatherDependentCalls(
     InfallibleVector<MiddlemanCall*>& aOutgoingCalls, MiddlemanCall* aCall) {
   MOZ_RELEASE_ASSERT(!aCall->mSent);
@@ -74,31 +90,31 @@ bool SendCallToMiddleman(size_t aCallId,
   MOZ_RELEASE_ASSERT(IsReplaying());
 
   const Redirection& redirection = GetRedirection(aCallId);
   MOZ_RELEASE_ASSERT(redirection.mMiddlemanCall);
 
   MonitorAutoLock lock(*gMonitor);
 
   // Allocate and fill in a new MiddlemanCall.
-  size_t id = gMiddlemanCalls.length();
+  size_t id = gState->mCalls.length();
   MiddlemanCall* newCall = new MiddlemanCall();
-  gMiddlemanCalls.emplaceBack(newCall);
+  gState->mCalls.emplaceBack(newCall);
   newCall->mId = id;
   newCall->mCallId = aCallId;
   newCall->mArguments.CopyFrom(aArguments);
 
   // Perform the ReplayPreface phase on the new call.
   {
     MiddlemanCallContext cx(newCall, aArguments,
                             MiddlemanCallPhase::ReplayPreface);
     redirection.mMiddlemanCall(cx);
     if (cx.mFailed) {
       delete newCall;
-      gMiddlemanCalls.popBack();
+      gState->mCalls.popBack();
       if (child::CurrentRepaintCannotFail()) {
         child::ReportFatalError(Nothing(),
                                 "Middleman call preface failed: %s\n",
                                 redirection.mName);
       }
       return false;
     }
   }
@@ -149,20 +165,28 @@ bool SendCallToMiddleman(size_t aCallId,
   // Perform the ReplayOutput phase to fill in outputs for the current call.
   newCall->mArguments.CopyTo(aArguments);
   MiddlemanCallContext cx(newCall, aArguments,
                           MiddlemanCallPhase::ReplayOutput);
   redirection.mMiddlemanCall(cx);
   return true;
 }
 
-void ProcessMiddlemanCall(const char* aInputData, size_t aInputSize,
+void ProcessMiddlemanCall(size_t aChildId, const char* aInputData, size_t aInputSize,
                           InfallibleVector<char>* aOutputData) {
   MOZ_RELEASE_ASSERT(IsMiddleman());
 
+  while (aChildId >= gStatePerChild.length()) {
+    gStatePerChild.append(nullptr);
+  }
+  if (!gStatePerChild[aChildId]) {
+    gStatePerChild[aChildId] = new MiddlemanCallState();
+  }
+  gState = gStatePerChild[aChildId];
+
   BufferStream inputStream(aInputData, aInputSize);
   BufferStream outputStream(aOutputData);
 
   while (!inputStream.IsEmpty()) {
     MiddlemanCall* call = new MiddlemanCall();
     call->DecodeInput(inputStream);
 
     const Redirection& redirection = GetRedirection(call->mCallId);
@@ -187,89 +211,102 @@ void ProcessMiddlemanCall(const char* aI
       MiddlemanCallContext cx(call, &arguments,
                               MiddlemanCallPhase::MiddlemanOutput);
       redirection.mMiddlemanCall(cx);
     }
 
     call->mArguments.CopyFrom(&arguments);
     call->EncodeOutput(outputStream);
 
-    while (call->mId >= gMiddlemanCalls.length()) {
-      gMiddlemanCalls.emplaceBack(nullptr);
+    while (call->mId >= gState->mCalls.length()) {
+      gState->mCalls.emplaceBack(nullptr);
     }
-    MOZ_RELEASE_ASSERT(!gMiddlemanCalls[call->mId]);
-    gMiddlemanCalls[call->mId] = call;
+    MOZ_RELEASE_ASSERT(!gState->mCalls[call->mId]);
+    gState->mCalls[call->mId] = call;
   }
+
+  gState = nullptr;
 }
 
 void* MiddlemanCallContext::AllocateBytes(size_t aSize) {
   void* rv = malloc(aSize);
 
   // In a middleman process, any buffers we allocate live until the calls are
   // reset. In a replaying process, the buffers will either live forever
   // (if they are allocated in the ReplayPreface phase, to match the lifetime
   // of the MiddlemanCall itself) or will be recovered when we rewind after we
   // are done with our divergence from the recording (any other phase).
   if (IsMiddleman()) {
-    gAllocatedBuffers.append(rv);
+    gState->mAllocatedBuffers.append(rv);
   }
 
   return rv;
 }
 
-void ResetMiddlemanCalls() {
+void ResetMiddlemanCalls(size_t aChildId) {
   MOZ_RELEASE_ASSERT(IsMiddleman());
 
-  for (MiddlemanCall* call : gMiddlemanCalls) {
+  if (aChildId >= gStatePerChild.length()) {
+    return;
+  }
+
+  gState = gStatePerChild[aChildId];
+  if (!gState) {
+    return;
+  }
+
+  for (MiddlemanCall* call : gState->mCalls) {
     if (call) {
       CallArguments arguments;
       call->mArguments.CopyTo(&arguments);
 
       MiddlemanCallContext cx(call, &arguments,
                               MiddlemanCallPhase::MiddlemanRelease);
       GetRedirection(call->mCallId).mMiddlemanCall(cx);
     }
   }
 
   // Delete the calls in a second pass. The MiddlemanRelease phase depends on
   // previous middleman calls still existing.
-  for (MiddlemanCall* call : gMiddlemanCalls) {
+  for (MiddlemanCall* call : gState->mCalls) {
     delete call;
   }
 
-  gMiddlemanCalls.clear();
-  for (auto buffer : gAllocatedBuffers) {
+  gState->mCalls.clear();
+  for (auto buffer : gState->mAllocatedBuffers) {
     free(buffer);
   }
-  gAllocatedBuffers.clear();
-  gMiddlemanCallMap->clear();
+  gState->mAllocatedBuffers.clear();
+  gState->mCallMap.clear();
+
+  gState = nullptr;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // System Values
 ///////////////////////////////////////////////////////////////////////////////
 
 static void AddMiddlemanCallValue(const void* aThing, MiddlemanCall* aCall) {
-  gMiddlemanCallMap->erase(aThing);
-  gMiddlemanCallMap->insert(MiddlemanCallMap::value_type(aThing, aCall));
+  gState->mCallMap.erase(aThing);
+  gState->mCallMap.insert(MiddlemanCallMap::value_type(aThing, aCall));
 }
 
 static MiddlemanCall* LookupMiddlemanCall(const void* aThing) {
-  MiddlemanCallMap::const_iterator iter = gMiddlemanCallMap->find(aThing);
-  if (iter != gMiddlemanCallMap->end()) {
+  MiddlemanCallMap::const_iterator iter = gState->mCallMap.find(aThing);
+  if (iter != gState->mCallMap.end()) {
     return iter->second;
   }
   return nullptr;
 }
 
 static const void* GetMiddlemanCallValue(size_t aId) {
   MOZ_RELEASE_ASSERT(IsMiddleman());
-  MOZ_RELEASE_ASSERT(aId < gMiddlemanCalls.length() && gMiddlemanCalls[aId] &&
-                     gMiddlemanCalls[aId]->mMiddlemanValue.isSome());
-  return gMiddlemanCalls[aId]->mMiddlemanValue.ref();
+  MOZ_RELEASE_ASSERT(aId < gState->mCalls.length() && gState->mCalls[aId] &&
+                     gState->mCalls[aId]->mMiddlemanValue.isSome());
+  return gState->mCalls[aId]->mMiddlemanValue.ref();
 }
 
 bool MM_SystemInput(MiddlemanCallContext& aCx, const void** aThingPtr) {
   MOZ_RELEASE_ASSERT(aCx.AccessPreface());
 
   if (!*aThingPtr) {
     // Null values are handled by the normal argument copying logic.
     return true;
@@ -288,17 +325,17 @@ bool MM_SystemInput(MiddlemanCallContext
   aCx.ReadOrWritePrefaceBytes(&callId, sizeof(callId));
 
   switch (aCx.mPhase) {
     case MiddlemanCallPhase::ReplayPreface:
       return true;
     case MiddlemanCallPhase::ReplayInput:
       if (callId.isSome()) {
         aCx.WriteInputScalar(callId.ref());
-        aCx.mDependentCalls->append(gMiddlemanCalls[callId.ref()]);
+        aCx.mDependentCalls->append(gState->mCalls[callId.ref()]);
         return true;
       }
       return false;
     case MiddlemanCallPhase::MiddlemanInput:
       if (callId.isSome()) {
         size_t callIndex = aCx.ReadInputScalar();
         *aThingPtr = GetMiddlemanCallValue(callIndex);
         return true;
--- a/toolkit/recordreplay/MiddlemanCall.h
+++ b/toolkit/recordreplay/MiddlemanCall.h
@@ -338,22 +338,24 @@ struct MiddlemanCallContext {
 // aDiverged is set if the current thread has diverged from the recording and
 // any outputs for the call must be filled in; otherwise, they already have
 // been filled in using data from the recording. Returns false if the call was
 // unable to be processed.
 bool SendCallToMiddleman(size_t aCallId, CallArguments* aArguments,
                          bool aDiverged);
 
 // In the middleman process, perform one or more calls encoded in aInputData
-// and encode their outputs to aOutputData.
-void ProcessMiddlemanCall(const char* aInputData, size_t aInputSize,
+// and encode their outputs to aOutputData. The calls are associated with the
+// specified child process ID.
+void ProcessMiddlemanCall(size_t aChildId,
+                          const char* aInputData, size_t aInputSize,
                           InfallibleVector<char>* aOutputData);
 
-// In the middleman process, reset all call state.
-void ResetMiddlemanCalls();
+// In the middleman process, reset all call state for a child process ID.
+void ResetMiddlemanCalls(size_t aChildId);
 
 ///////////////////////////////////////////////////////////////////////////////
 // Middleman Call Helpers
 ///////////////////////////////////////////////////////////////////////////////
 
 // Capture the contents of an input buffer at BufferArg with element count at
 // CountArg.
 template <size_t BufferArg, size_t CountArg, typename ElemType = char>
--- a/toolkit/recordreplay/ipc/Channel.h
+++ b/toolkit/recordreplay/ipc/Channel.h
@@ -426,24 +426,16 @@ struct BinaryMessage : public Message {
 
 typedef BinaryMessage<MessageType::MiddlemanCallRequest>
     MiddlemanCallRequestMessage;
 typedef BinaryMessage<MessageType::MiddlemanCallResponse>
     MiddlemanCallResponseMessage;
 typedef EmptyMessage<MessageType::ResetMiddlemanCalls>
     ResetMiddlemanCallsMessage;
 
-static inline MiddlemanCallResponseMessage* ProcessMiddlemanCallMessage(
-    const MiddlemanCallRequestMessage& aMsg) {
-  InfallibleVector<char> outputData;
-  ProcessMiddlemanCall(aMsg.BinaryData(), aMsg.BinaryDataSize(), &outputData);
-  return MiddlemanCallResponseMessage::New(outputData.begin(),
-                                           outputData.length());
-}
-
 class Channel {
  public:
   // Note: the handler is responsible for freeing its input message. It will be
   // called on the channel's message thread.
   typedef std::function<void(Message::UniquePtr)> MessageHandler;
 
  private:
   // ID for this channel, unique for the middleman.
--- a/toolkit/recordreplay/ipc/ChildProcess.cpp
+++ b/toolkit/recordreplay/ipc/ChildProcess.cpp
@@ -89,22 +89,27 @@ void ChildProcessInfo::OnIncomingMessage
       js::OnDebuggerResponse(aMsg);
       break;
     case MessageType::RecordingFlushed:
       mPaused = true;
       break;
     case MessageType::MiddlemanCallRequest: {
       const MiddlemanCallRequestMessage& nmsg =
         static_cast<const MiddlemanCallRequestMessage&>(aMsg);
-      Message::UniquePtr response(ProcessMiddlemanCallMessage(nmsg));
+      InfallibleVector<char> outputData;
+      ProcessMiddlemanCall(GetId(), nmsg.BinaryData(), nmsg.BinaryDataSize(),
+                           &outputData);
+      Message::UniquePtr response(
+        MiddlemanCallResponseMessage::New(outputData.begin(),
+                                          outputData.length()));
       SendMessage(*response);
       break;
     }
     case MessageType::ResetMiddlemanCalls:
-      ResetMiddlemanCalls();
+      ResetMiddlemanCalls(GetId());
       break;
     default:
       break;
   }
 }
 
 void ChildProcessInfo::SendMessage(const Message& aMsg) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());