Bug 1497027 - Crash properly after record/replay initialization failures, r=froydnj.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 09 Oct 2018 14:20:10 -1000
changeset 440499 e36764125483b541b8647df406ef29ac2eb89876
parent 440498 8af8ea2d88b0159cce6e59317d0a4adf7b5b5bf0
child 440500 31c68d4d5e8b3e117d19795debe841a968797441
push id34821
push userryanvm@gmail.com
push dateWed, 10 Oct 2018 16:31:04 +0000
treeherdermozilla-central@1a9cbc785296 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1497027
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 1497027 - Crash properly after record/replay initialization failures, r=froydnj.
toolkit/recordreplay/Lock.cpp
toolkit/recordreplay/ProcessRecordReplay.cpp
toolkit/recordreplay/ProcessRecordReplay.h
toolkit/recordreplay/ProcessRewind.cpp
toolkit/recordreplay/Thread.cpp
toolkit/recordreplay/ipc/ChildIPC.cpp
--- a/toolkit/recordreplay/Lock.cpp
+++ b/toolkit/recordreplay/Lock.cpp
@@ -191,21 +191,26 @@ struct AtomicLock : public detail::Mutex
 // Lock which is held during code sections that run atomically. This is a
 // PRLock instead of an OffTheBooksMutex because the latter performs atomic
 // operations during initialization.
 static AtomicLock* gAtomicLock = nullptr;
 
 /* static */ void
 Lock::InitializeLocks()
 {
-  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
   gNumLocks = gAtomicLockId;
+  gAtomicLock = new AtomicLock();
+
+  AssertEventsAreNotPassedThrough();
 
-  gAtomicLock = new AtomicLock();
-  MOZ_RELEASE_ASSERT(!IsRecording() || gNumLocks == gAtomicLockId + 1);
+  // There should be exactly one recorded lock right now, unless we had an
+  // initialization failure and didn't record the lock just created.
+  MOZ_RELEASE_ASSERT(!IsRecording() ||
+                     gNumLocks == gAtomicLockId + 1 ||
+                     gInitializationFailureMessage);
 }
 
 /* static */ void
 Lock::LockAquiresUpdated(size_t aLockId)
 {
   LockAcquires* acquires = gLockAcquires.MaybeGet(aLockId);
   if (acquires && acquires->mAcquires && acquires->mNextOwner == LockAcquires::NoNextOwner) {
     acquires->ReadAndNotifyNextOwner(Thread::Current());
--- a/toolkit/recordreplay/ProcessRecordReplay.cpp
+++ b/toolkit/recordreplay/ProcessRecordReplay.cpp
@@ -115,24 +115,24 @@ RecordReplayInterface_Initialize(int aAr
   }
 
   gSnapshotMemoryPrefix = mktemp(strdup("/tmp/SnapshotMemoryXXXXXX"));
   gSnapshotStackPrefix = mktemp(strdup("/tmp/SnapshotStackXXXXXX"));
 
   InitializeCurrentTime();
 
   gRecordingFile = new File();
-  if (!gRecordingFile->Open(recordingFile.ref(), IsRecording() ? File::WRITE : File::READ)) {
+  if (gRecordingFile->Open(recordingFile.ref(), IsRecording() ? File::WRITE : File::READ)) {
+    InitializeRedirections();
+  } else {
     gInitializationFailureMessage = strdup("Bad recording file");
-    return;
   }
 
-  if (!InitializeRedirections()) {
-    MOZ_RELEASE_ASSERT(gInitializationFailureMessage);
-    return;
+  if (gInitializationFailureMessage) {
+    fprintf(stderr, "Initialization Failure: %s\n", gInitializationFailureMessage);
   }
 
   Thread::InitializeThreads();
 
   Thread* thread = Thread::GetById(MainThreadId);
   MOZ_ASSERT(thread->Id() == MainThreadId);
 
   thread->BindToCurrent();
--- a/toolkit/recordreplay/ProcessRecordReplay.h
+++ b/toolkit/recordreplay/ProcessRecordReplay.h
@@ -82,19 +82,29 @@ const char* ThreadEventName(ThreadEvent 
 class File;
 
 // File used during recording and replay.
 extern File* gRecordingFile;
 
 // Whether record/replay state has finished initialization.
 extern bool gInitialized;
 
-// If we failed to initialize, any associated message.
+// If we failed to initialize, any associated message. On an initialization
+// failure, events will be passed through until we have connected with the
+// middleman, reported the failure, and crashed.
 extern char* gInitializationFailureMessage;
 
+// For places where events will normally not be passed through, unless there
+// was an initialization failure.
+static inline void
+AssertEventsAreNotPassedThrough()
+{
+  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough() || gInitializationFailureMessage);
+}
+
 // Flush any new recording data to disk.
 void FlushRecording();
 
 // Called when any thread hits the end of its event stream.
 void HitEndOfRecording();
 
 // Called when the main thread hits the latest recording endpoint it knows
 // about.
--- a/toolkit/recordreplay/ProcessRewind.cpp
+++ b/toolkit/recordreplay/ProcessRewind.cpp
@@ -213,17 +213,17 @@ DisallowUnhandledDivergeFromRecording()
 {
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
   gUnhandledDivergeAllowed = false;
 }
 
 void
 EnsureNotDivergedFromRecording()
 {
-  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
+  AssertEventsAreNotPassedThrough();
   if (HasDivergedFromRecording()) {
     MOZ_RELEASE_ASSERT(gUnhandledDivergeAllowed);
     PrintSpew("Unhandled recording divergence, restoring checkpoint...\n");
     RestoreCheckpointAndResume(gRewindInfo->mSavedCheckpoints.back().mCheckpoint);
     Unreachable();
   }
 }
 
@@ -247,18 +247,18 @@ MainThreadShouldPause()
 {
   return gMainThreadShouldPause;
 }
 
 void
 PauseMainThreadAndServiceCallbacks()
 {
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
-  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
   MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
+  AssertEventsAreNotPassedThrough();
 
   // Whether there is a PauseMainThreadAndServiceCallbacks frame on the stack.
   static bool gMainThreadIsPaused = false;
 
   if (gMainThreadIsPaused) {
     return;
   }
   gMainThreadIsPaused = true;
--- a/toolkit/recordreplay/Thread.cpp
+++ b/toolkit/recordreplay/Thread.cpp
@@ -203,17 +203,17 @@ Thread::SpawnAllThreads()
 }
 
 // The number of non-recorded threads that have been spawned.
 static Atomic<size_t, SequentiallyConsistent, Behavior::DontPreserve> gNumNonRecordedThreads;
 
 /* static */ Thread*
 Thread::SpawnNonRecordedThread(Callback aStart, void* aArgument)
 {
-  if (IsMiddleman() || gInitializationFailureMessage) {
+  if (IsMiddleman()) {
     DirectSpawnThread(aStart, aArgument);
     return nullptr;
   }
 
   size_t id = MaxRecordedThreadId + ++gNumNonRecordedThreads;
   MOZ_RELEASE_ASSERT(id <= MaxThreadId);
 
   Thread* thread = GetById(id);
@@ -318,16 +318,23 @@ RecordReplayInterface_InternalEndPassThr
     Thread::Current()->SetPassThrough(false);
   }
 }
 
 MOZ_EXPORT bool
 RecordReplayInterface_InternalAreThreadEventsPassedThrough()
 {
   MOZ_ASSERT(IsRecordingOrReplaying());
+
+  // If initialization fails, pass through all thread events until we're able
+  // to report the problem to the middleman and die.
+  if (gInitializationFailureMessage) {
+    return true;
+  }
+
   Thread* thread = Thread::Current();
   return !thread || thread->PassThroughEvents();
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalBeginDisallowThreadEvents()
 {
   MOZ_ASSERT(IsRecordingOrReplaying());
--- a/toolkit/recordreplay/ipc/ChildIPC.cpp
+++ b/toolkit/recordreplay/ipc/ChildIPC.cpp
@@ -217,19 +217,22 @@ InitRecordingOrReplayingProcess(int* aAr
   Maybe<AutoPassThroughThreadEvents> pt;
   pt.emplace();
 
   gMonitor = new Monitor();
   gChannel = new Channel(channelID.ref(), /* aMiddlemanRecording = */ false, ChannelMessageHandler);
 
   pt.reset();
 
-  DirectCreatePipe(&gCheckpointWriteFd, &gCheckpointReadFd);
-
-  Thread::StartThread(ListenForCheckpointThreadMain, nullptr, false);
+  // N.B. We can't spawn recorded threads when replaying if there was an
+  // initialization failure.
+  if (!gInitializationFailureMessage) {
+    DirectCreatePipe(&gCheckpointWriteFd, &gCheckpointReadFd);
+    Thread::StartThread(ListenForCheckpointThreadMain, nullptr, false);
+  }
 
   pt.emplace();
 
   // Setup a mach port to receive the graphics shmem handle over.
   ReceivePort receivePort(nsPrintfCString("WebReplay.%d.%d", gMiddlemanPid, (int) channelID.ref()).get());
 
   MachSendMessage handshakeMessage(parent::GraphicsHandshakeMessageId);
   handshakeMessage.AddDescriptor(MachMsgPortDescriptor(receivePort.GetPort(), MACH_MSG_TYPE_COPY_SEND));
@@ -261,16 +264,22 @@ InitRecordingOrReplayingProcess(int* aAr
   AddInitialUntrackedMemoryRegion((uint8_t*) gGraphicsShmem, parent::GraphicsMemorySize);
 
   pt.reset();
 
   // We are ready to receive initialization messages from the middleman, pause
   // so they can be sent.
   HitCheckpoint(CheckpointId::Invalid, /* aRecordingEndpoint = */ false);
 
+  // If we failed to initialize then report it to the user.
+  if (gInitializationFailureMessage) {
+    ReportFatalError(Nothing(), "%s", gInitializationFailureMessage);
+    Unreachable();
+  }
+
   // Process the introduction message to fill in arguments.
   MOZ_RELEASE_ASSERT(gParentArgv.empty());
 
   gParentPid = gIntroductionMessage->mParentPid;
 
   // Record/replay the introduction message itself so we get consistent args
   // between recording and replaying.
   {
@@ -291,22 +300,16 @@ InitRecordingOrReplayingProcess(int* aAr
   // Some argument manipulation code expects a null pointer at the end.
   gParentArgv.append(nullptr);
 
   MOZ_RELEASE_ASSERT(*aArgc >= 1);
   MOZ_RELEASE_ASSERT(gParentArgv.back() == nullptr);
 
   *aArgc = gParentArgv.length() - 1; // For the trailing null.
   *aArgv = gParentArgv.begin();
-
-  // If we failed to initialize then report it to the user.
-  if (gInitializationFailureMessage) {
-    ReportFatalError(Nothing(), "%s", gInitializationFailureMessage);
-    Unreachable();
-  }
 }
 
 base::ProcessId
 MiddlemanProcessId()
 {
   return gMiddlemanPid;
 }