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 490273 5b5ae360b887bc49a765f43a8b38f400d52cb3cc
parent 490272 4478e865d77054f42534630aca3d85d41e810f44
child 490274 b6ca168b7e52bff9f36a6ca07c1dab6750c87882
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersmccr8
bugs1488808
milestone64.0a1
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