Bug 1488808 Part 6 - IPC for performing system calls in the middleman, r=mccr8.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 17 Oct 2018 10:01:32 -0600
changeset 442011 5b5ae360b887bc49a765f43a8b38f400d52cb3cc
parent 442010 4478e865d77054f42534630aca3d85d41e810f44
child 442012 b6ca168b7e52bff9f36a6ca07c1dab6750c87882
push id34885
push usercsabou@mozilla.com
push dateFri, 19 Oct 2018 04:17:46 +0000
treeherdermozilla-central@7d74c5905384 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1488808
milestone64.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 1488808 Part 6 - IPC for performing system calls in the middleman, r=mccr8.
toolkit/recordreplay/ipc/Channel.cpp
toolkit/recordreplay/ipc/Channel.h
toolkit/recordreplay/ipc/ChildIPC.cpp
toolkit/recordreplay/ipc/ChildInternal.h
toolkit/recordreplay/ipc/ChildProcess.cpp
toolkit/recordreplay/ipc/ParentIPC.cpp
--- a/toolkit/recordreplay/ipc/Channel.cpp
+++ b/toolkit/recordreplay/ipc/Channel.cpp
@@ -145,17 +145,19 @@ Channel::ThreadMain(void* aChannelArg)
     }
     channel->mHandler(msg);
   }
 }
 
 void
 Channel::SendMessage(const Message& aMsg)
 {
-  MOZ_RELEASE_ASSERT(NS_IsMainThread() || aMsg.mType == MessageType::FatalError);
+  MOZ_RELEASE_ASSERT(NS_IsMainThread() ||
+                     aMsg.mType == MessageType::FatalError ||
+                     aMsg.mType == MessageType::MiddlemanCallRequest);
 
   // Block until the channel is initialized.
   if (!mInitialized) {
     MonitorAutoLock lock(mMonitor);
     while (!mInitialized) {
       mMonitor.Wait();
     }
   }
--- a/toolkit/recordreplay/ipc/Channel.h
+++ b/toolkit/recordreplay/ipc/Channel.h
@@ -93,16 +93,19 @@ namespace recordreplay {
   _Macro(SetIsActive)                                          \
                                                                \
   /* Set whether to perform intentional crashes, for testing. */ \
   _Macro(SetAllowIntentionalCrashes)                           \
                                                                \
   /* Set whether to save a particular checkpoint. */           \
   _Macro(SetSaveCheckpoint)                                    \
                                                                \
+  /* Respond to a MiddlemanCallRequest message. */             \
+  _Macro(MiddlemanCallResponse)                                \
+                                                               \
   /* Messages sent from the child process to the middleman. */ \
                                                                \
   /* Sent in response to a FlushRecording, telling the middleman that the flush */ \
   /* has finished. */                                          \
   _Macro(RecordingFlushed)                                     \
                                                                \
   /* A critical error occurred and execution cannot continue. The child will */ \
   /* stop executing after sending this message and will wait to be terminated. */ \
@@ -114,16 +117,23 @@ namespace recordreplay {
   /* Notify the middleman that a checkpoint or breakpoint was hit. */ \
   /* The child will pause after sending these messages. */     \
   _Macro(HitCheckpoint)                                        \
   _Macro(HitBreakpoint)                                        \
                                                                \
   /* Send a response to a DebuggerRequest message. */          \
   _Macro(DebuggerResponse)                                     \
                                                                \
+  /* Call a system function from the middleman process which the child has */ \
+  /* encountered after diverging from the recording. */        \
+  _Macro(MiddlemanCallRequest)                                 \
+                                                               \
+  /* Reset all information generated by previous MiddlemanCallRequest messages. */ \
+  _Macro(ResetMiddlemanCalls)                                  \
+                                                               \
   /* Notify that the 'AlwaysMarkMajorCheckpoints' directive was invoked. */ \
   _Macro(AlwaysMarkMajorCheckpoints)
 
 enum class MessageType
 {
 #define DefineEnum(Kind) Kind,
   ForEachMessageType(DefineEnum)
 #undef DefineEnum
@@ -159,16 +169,17 @@ public:
     }
   }
 
   // Return whether this is a middleman->child message that can be sent while
   // the child is unpaused.
   bool CanBeSentWhileUnpaused() const {
     return mType == MessageType::CreateCheckpoint
         || mType == MessageType::SetDebuggerRunsInMiddleman
+        || mType == MessageType::MiddlemanCallResponse
         || mType == MessageType::Terminate;
   }
 
 protected:
   template <typename T, typename Elem>
   Elem* Data() { return (Elem*) (sizeof(T) + (char*) this); }
 
   template <typename T, typename Elem>
@@ -413,16 +424,39 @@ struct HitBreakpointMessage : public Mes
     MOZ_RELEASE_ASSERT(res->NumBreakpoints() == aNumBreakpoints);
     PodCopy(res->Data<HitBreakpointMessage, uint32_t>(), aBreakpoints, aNumBreakpoints);
     return res;
   }
 };
 
 typedef EmptyMessage<MessageType::AlwaysMarkMajorCheckpoints> AlwaysMarkMajorCheckpointsMessage;
 
+template <MessageType Type>
+struct BinaryMessage : public Message
+{
+  explicit BinaryMessage(uint32_t aSize)
+    : Message(Type, aSize)
+  {}
+
+  const char* BinaryData() const { return Data<BinaryMessage<Type>, char>(); }
+  size_t BinaryDataSize() const { return DataSize<BinaryMessage<Type>, char>(); }
+
+  static BinaryMessage<Type>*
+  New(const char* aData, size_t aDataSize) {
+    BinaryMessage<Type>* res = NewWithData<BinaryMessage<Type>, char>(aDataSize);
+    MOZ_RELEASE_ASSERT(res->BinaryDataSize() == aDataSize);
+    PodCopy(res->Data<BinaryMessage<Type>, char>(), aData, aDataSize);
+    return res;
+  }
+};
+
+typedef BinaryMessage<MessageType::MiddlemanCallRequest> MiddlemanCallRequestMessage;
+typedef BinaryMessage<MessageType::MiddlemanCallResponse> MiddlemanCallResponseMessage;
+typedef EmptyMessage<MessageType::ResetMiddlemanCalls> ResetMiddlemanCallsMessage;
+
 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*)> MessageHandler;
 
 private:
--- a/toolkit/recordreplay/ipc/ChildIPC.cpp
+++ b/toolkit/recordreplay/ipc/ChildIPC.cpp
@@ -59,16 +59,19 @@ static FileHandle gCheckpointReadFd;
 
 // Copy of the introduction message we got from the middleman. This is saved on
 // receipt and then processed during InitRecordingOrReplayingProcess.
 static IntroductionMessage* gIntroductionMessage;
 
 // When recording, whether developer tools server code runs in the middleman.
 static bool gDebuggerRunsInMiddleman;
 
+// Any response received to the last MiddlemanCallRequest message.
+static MiddlemanCallResponseMessage* gCallResponseMessage;
+
 // Processing routine for incoming channel messages.
 static void
 ChannelMessageHandler(Message* aMsg)
 {
   MOZ_RELEASE_ASSERT(MainThreadShouldPause() || aMsg->CanBeSentWhileUnpaused());
 
   switch (aMsg->mType) {
   case MessageType::Introduction: {
@@ -154,16 +157,24 @@ ChannelMessageHandler(Message* aMsg)
   }
   case MessageType::RunToPoint: {
     const RunToPointMessage& nmsg = (const RunToPointMessage&) *aMsg;
     PauseMainThreadAndInvokeCallback([=]() {
         navigation::RunToPoint(nmsg.mTarget);
       });
     break;
   }
+  case MessageType::MiddlemanCallResponse: {
+    MonitorAutoLock lock(*gMonitor);
+    MOZ_RELEASE_ASSERT(!gCallResponseMessage);
+    gCallResponseMessage = (MiddlemanCallResponseMessage*) aMsg;
+    aMsg = nullptr; // Avoid freeing the message below.
+    gMonitor->NotifyAll();
+    break;
+  }
   default:
     MOZ_CRASH();
   }
 
   free(aMsg);
 }
 
 // Main routine for a thread whose sole purpose is to listen to requests from
@@ -531,17 +542,17 @@ HitCheckpoint(size_t aId, bool aRecordin
         MOZ_RELEASE_ASSERT(duration > 0);
       }
       gChannel->SendMessage(HitCheckpointMessage(aId, aRecordingEndpoint, duration));
     });
   gLastCheckpointTime = time;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// Debugger Messages
+// Message Helpers
 ///////////////////////////////////////////////////////////////////////////////
 
 void
 RespondToRequest(const js::CharBuffer& aBuffer)
 {
   DebuggerResponseMessage* msg =
     DebuggerResponseMessage::New(aBuffer.begin(), aBuffer.length());
   gChannel->SendMessage(*msg);
@@ -555,11 +566,57 @@ HitBreakpoint(bool aRecordingEndpoint, c
   HitBreakpointMessage* msg =
     HitBreakpointMessage::New(aRecordingEndpoint, aBreakpoints, aNumBreakpoints);
   PauseMainThreadAndInvokeCallback([=]() {
       gChannel->SendMessage(*msg);
       free(msg);
     });
 }
 
+bool
+SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,
+                         InfallibleVector<char>* aOutputData)
+{
+  Thread* thread = Thread::Current();
+
+  // Middleman calls can only be made from the main and compositor threads.
+  // These two threads cannot simultaneously send call requests or other
+  // messages, as doing so will race both here and in Channel::SendMessage.
+  // CompositorCanPerformMiddlemanCalls() ensures that the main thread is
+  // not actively sending messages at times when the compositor performs
+  // middleman calls.
+  MOZ_RELEASE_ASSERT(thread->IsMainThread() || thread->Id() == gCompositorThreadId);
+
+  if (thread->Id() == gCompositorThreadId && !CompositorCanPerformMiddlemanCalls()) {
+    return false;
+  }
+
+  MonitorAutoLock lock(*gMonitor);
+
+  MOZ_RELEASE_ASSERT(!gCallResponseMessage);
+
+  MiddlemanCallRequestMessage* msg = MiddlemanCallRequestMessage::New(aInputData, aInputSize);
+  gChannel->SendMessage(*msg);
+  free(msg);
+
+  while (!gCallResponseMessage) {
+    gMonitor->Wait();
+  }
+
+  aOutputData->append(gCallResponseMessage->BinaryData(), gCallResponseMessage->BinaryDataSize());
+
+  free(gCallResponseMessage);
+  gCallResponseMessage = nullptr;
+
+  gMonitor->Notify();
+  return true;
+}
+
+void
+SendResetMiddlemanCalls()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  gChannel->SendMessage(ResetMiddlemanCallsMessage());
+}
+
 } // namespace child
 } // namespace recordreplay
 } // namespace mozilla
--- a/toolkit/recordreplay/ipc/ChildInternal.h
+++ b/toolkit/recordreplay/ipc/ChildInternal.h
@@ -2,18 +2,20 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_recordreplay_ChildInternal_h
 #define mozilla_recordreplay_ChildInternal_h
 
+#include "Channel.h"
 #include "ChildIPC.h"
 #include "JSControl.h"
+#include "MiddlemanCall.h"
 #include "Monitor.h"
 
 namespace mozilla {
 namespace recordreplay {
 
 // This file has internal definitions for communication between the main
 // record/replay infrastructure and child side IPC code.
 
@@ -111,14 +113,19 @@ void ReportFatalError(const char* aForma
 
 // Mark a time span when the main thread is idle.
 void BeginIdleTime();
 void EndIdleTime();
 
 // Whether the middleman runs developer tools server code.
 bool DebuggerRunsInMiddleman();
 
+// Send messages operating on middleman calls.
+bool SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,
+                              InfallibleVector<char>* aOutputData);
+void SendResetMiddlemanCalls();
+
 } // namespace child
 
 } // namespace recordreplay
 } // namespace mozilla
 
 #endif // mozilla_recordreplay_ChildInternal_h
--- a/toolkit/recordreplay/ipc/ChildProcess.cpp
+++ b/toolkit/recordreplay/ipc/ChildProcess.cpp
@@ -290,16 +290,17 @@ ChildProcessInfo::SendMessage(const Mess
 
   // Keep track of messages which affect the child's behavior.
   switch (aMsg.mType) {
   case MessageType::Resume:
   case MessageType::RestoreCheckpoint:
   case MessageType::RunToPoint:
   case MessageType::DebuggerRequest:
   case MessageType::SetBreakpoint:
+  case MessageType::MiddlemanCallResponse:
     mMessages.emplaceBack(aMsg.Clone());
     break;
   default:
     break;
   }
 
   // Keep track of the checkpoints the process will save.
   if (aMsg.mType == MessageType::SetSaveCheckpoint) {
@@ -402,18 +403,21 @@ ChildProcessInfo::OnIncomingRecoveryMess
       MOZ_RELEASE_ASSERT(nmsg.mCheckpointId == mLastCheckpoint);
       mRecoveryStage = RecoveryStage::PlayingMessages;
       SendNextRecoveryMessage();
     }
     break;
   }
   case MessageType::HitBreakpoint:
   case MessageType::DebuggerResponse:
+  case MessageType::MiddlemanCallRequest:
     SendNextRecoveryMessage();
     break;
+  case MessageType::ResetMiddlemanCalls:
+    break;
   default:
     MOZ_CRASH("Unexpected message during recovery");
   }
 }
 
 void
 ChildProcessInfo::SendNextRecoveryMessage()
 {
--- a/toolkit/recordreplay/ipc/ParentIPC.cpp
+++ b/toolkit/recordreplay/ipc/ParentIPC.cpp
@@ -221,16 +221,17 @@ PokeChildren()
     });
 }
 
 static void RecvHitCheckpoint(const HitCheckpointMessage& aMsg);
 static void RecvHitBreakpoint(const HitBreakpointMessage& aMsg);
 static void RecvDebuggerResponse(const DebuggerResponseMessage& aMsg);
 static void RecvRecordingFlushed();
 static void RecvAlwaysMarkMajorCheckpoints();
+static void RecvMiddlemanCallRequest(const MiddlemanCallRequestMessage& aMsg);
 
 // The role taken by the active child.
 class ChildRoleActive final : public ChildRole
 {
 public:
   ChildRoleActive()
     : ChildRole(Active)
   {}
@@ -262,16 +263,22 @@ public:
       RecvDebuggerResponse((const DebuggerResponseMessage&) aMsg);
       break;
     case MessageType::RecordingFlushed:
       RecvRecordingFlushed();
       break;
     case MessageType::AlwaysMarkMajorCheckpoints:
       RecvAlwaysMarkMajorCheckpoints();
       break;
+    case MessageType::MiddlemanCallRequest:
+      RecvMiddlemanCallRequest((const MiddlemanCallRequestMessage&) aMsg);
+      break;
+    case MessageType::ResetMiddlemanCalls:
+      ResetMiddlemanCalls();
+      break;
     default:
       MOZ_CRASH("Unexpected message");
     }
   }
 };
 
 bool
 ActiveChildIsRecording()
@@ -1194,11 +1201,23 @@ HitForcedPauseBreakpoints(bool aRecordin
     uint32_t* newBreakpoints = new uint32_t[breakpoints.length()];
     PodCopy(newBreakpoints, breakpoints.begin(), breakpoints.length());
     gMainThreadMessageLoop->PostTask(NewRunnableFunction("HitBreakpoint", HitBreakpoint,
                                                          newBreakpoints, breakpoints.length(),
                                                          aRecordingBoundary));
   }
 }
 
+static void
+RecvMiddlemanCallRequest(const MiddlemanCallRequestMessage& aMsg)
+{
+  InfallibleVector<char> outputData;
+  ProcessMiddlemanCall(aMsg.BinaryData(), aMsg.BinaryDataSize(), &outputData);
+
+  MiddlemanCallResponseMessage* response =
+    MiddlemanCallResponseMessage::New(outputData.begin(), outputData.length());
+  gActiveChild->SendMessage(*response);
+  free(response);
+}
+
 } // namespace parent
 } // namespace recordreplay
 } // namespace mozilla