Bug 1488808 Part 10 - Notify the record/replay system when a message pump thread blocks after diverging from the recording, r=froydnj.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 17 Oct 2018 10:05:02 -0600
changeset 490277 baaaaa544bb0da29752973de7ce72a7d8d5ee84a
parent 490276 0ce807f9d1f679c848341e4858df243a5c127bdf
child 490278 597cf7ce986a50e436398ac99491611dbeb0b30a
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersfroydnj
bugs1488808
milestone64.0a1
Bug 1488808 Part 10 - Notify the record/replay system when a message pump thread blocks after diverging from the recording, r=froydnj.
ipc/chromium/src/base/message_pump_default.cc
js/src/vm/HelperThreads.cpp
mfbt/RecordReplay.cpp
mfbt/RecordReplay.h
toolkit/recordreplay/Thread.cpp
toolkit/recordreplay/Thread.h
--- a/ipc/chromium/src/base/message_pump_default.cc
+++ b/ipc/chromium/src/base/message_pump_default.cc
@@ -55,16 +55,29 @@ void MessagePumpDefault::Run(Delegate* d
     if (did_work)
       continue;
 
     if (delayed_work_time_.is_null()) {
       hangMonitor.NotifyWait();
       AUTO_PROFILER_LABEL("MessagePumpDefault::Run:Wait", IDLE);
       {
         AUTO_PROFILER_THREAD_SLEEP;
+        // Some threads operating a message pump (i.e. the compositor thread)
+        // can diverge from Web Replay recordings, after which the thread needs
+        // to be explicitly notified in order to coordinate with the
+        // record/replay checkpoint system.
+        if (mozilla::recordreplay::IsRecordingOrReplaying()) {
+          // Workaround static analysis. Message pumps for which the lambda
+          // below might be called will not be destroyed.
+          void* thiz = this;
+
+          mozilla::recordreplay::MaybeWaitForCheckpointSave();
+          mozilla::recordreplay::NotifyUnrecordedWait([=]() { ((MessagePumpDefault*)thiz)->ScheduleWork(); },
+                                                      /* aOnlyWhenDiverged = */ true);
+        }
         event_.Wait();
       }
     } else {
       TimeDelta delay = delayed_work_time_ - TimeTicks::Now();
       if (delay > TimeDelta()) {
         hangMonitor.NotifyWait();
         AUTO_PROFILER_LABEL("MessagePumpDefault::Run:Wait", IDLE);
         {
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -157,17 +157,18 @@ MaybeWaitForRecordReplayCheckpointSave(A
             mozilla::recordreplay::MaybeWaitForCheckpointSave();
         }
 
         // For the std::function destructor invoked below.
         JS::AutoSuppressGCAnalysis nogc;
 
         // Now that we are holding the helper thread state lock again,
         // notify the record/replay system that we might block soon.
-        mozilla::recordreplay::NotifyUnrecordedWait(HelperThread::WakeupAll);
+        mozilla::recordreplay::NotifyUnrecordedWait(HelperThread::WakeupAll,
+                                                    /* aOnlyWhenDiverged = */ false);
     }
 }
 
 bool
 js::StartOffThreadWasmCompile(wasm::CompileTask* task, wasm::CompileMode mode)
 {
     AutoLockHelperThreadState lock;
 
--- a/mfbt/RecordReplay.cpp
+++ b/mfbt/RecordReplay.cpp
@@ -46,17 +46,18 @@ namespace recordreplay {
   Macro(InternalEndOrderedAtomicAccess, (), ())                 \
   Macro(InternalBeginPassThroughThreadEvents, (), ())           \
   Macro(InternalEndPassThroughThreadEvents, (), ())             \
   Macro(InternalBeginDisallowThreadEvents, (), ())              \
   Macro(InternalEndDisallowThreadEvents, (), ())                \
   Macro(InternalRecordReplayBytes,                              \
         (void* aData, size_t aSize), (aData, aSize))            \
   Macro(NotifyUnrecordedWait,                                   \
-        (const std::function<void()>& aCallback), (aCallback))  \
+        (const std::function<void()>& aCallback, bool aOnlyWhenDiverged), \
+        (aCallback, aOnlyWhenDiverged))                         \
   Macro(MaybeWaitForCheckpointSave, (), ())                     \
   Macro(InternalInvalidateRecording, (const char* aWhy), (aWhy)) \
   Macro(InternalDestroyPLDHashTableCallbacks,                   \
         (const PLDHashTableOps* aOps), (aOps))                  \
   Macro(InternalMovePLDHashTableContents,                       \
         (const PLDHashTableOps* aFirstOps, const PLDHashTableOps* aSecondOps), \
         (aFirstOps, aSecondOps))                                \
   Macro(SetWeakPointerJSRoot,                                   \
--- a/mfbt/RecordReplay.h
+++ b/mfbt/RecordReplay.h
@@ -253,22 +253,26 @@ static inline bool HasDivergedFromRecord
 // passed through) or an associated cvar --- this is handled automatically.
 //
 // Threads which block indefinitely on unrecorded resources must call
 // NotifyUnrecordedWait first.
 //
 // The callback passed to NotifyUnrecordedWait will be invoked at most once
 // by the main thread whenever the main thread is waiting for other threads to
 // become idle, and at most once after the call to NotifyUnrecordedWait if the
-// main thread is already waiting for other threads to become idle.
+// main thread is already waiting for other threads to become idle. If
+// aOnlyWhenDiverged is specified, the callback will only be invoked if the
+// thread has diverged from the recording (causing all resources to be treated
+// as unrecorded).
 //
 // The callback should poke the thread so that it is no longer blocked on the
 // resource. The thread must call MaybeWaitForCheckpointSave before blocking
 // again.
-MFBT_API void NotifyUnrecordedWait(const std::function<void()>& aCallback);
+MFBT_API void NotifyUnrecordedWait(const std::function<void()>& aCallback,
+                                   bool aOnlyWhenDiverged);
 MFBT_API void MaybeWaitForCheckpointSave();
 
 // API for debugging inconsistent behavior between recording and replay.
 // By calling Assert or AssertBytes a thread event will be inserted and any
 // inconsistent execution order of events will be detected (as for normal
 // thread events) and reported to the console.
 //
 // RegisterThing/UnregisterThing associate arbitrary pointers with indexes that
--- a/toolkit/recordreplay/Thread.cpp
+++ b/toolkit/recordreplay/Thread.cpp
@@ -384,28 +384,37 @@ Thread::WaitForIdleThreads()
     GetById(i)->mUnrecordedWaitNotified = false;
   }
   while (true) {
     bool done = true;
     for (size_t i = MainThreadId + 1; i <= MaxRecordedThreadId; i++) {
       Thread* thread = GetById(i);
       if (!thread->mIdle) {
         done = false;
-        if (thread->mUnrecordedWaitCallback && !thread->mUnrecordedWaitNotified) {
+
+        // Check if there is a callback we can invoke to get this thread to
+        // make progress. The mUnrecordedWaitOnlyWhenDiverged flag is used to
+        // avoid perturbing the behavior of threads that may or may not be
+        // waiting on an unrecorded resource, depending on whether they have
+        // diverged from the recording yet.
+        if (thread->mUnrecordedWaitCallback && !thread->mUnrecordedWaitNotified &&
+            (!thread->mUnrecordedWaitOnlyWhenDiverged ||
+             thread->WillDivergeFromRecordingSoon())) {
           // Set this flag before releasing the idle lock. Otherwise it's
           // possible the thread could call NotifyUnrecordedWait while we
           // aren't holding the lock, and we would set the flag afterwards
           // without first invoking the callback.
           thread->mUnrecordedWaitNotified = true;
 
           // Release the idle lock here to avoid any risk of deadlock.
+          std::function<void()> callback = thread->mUnrecordedWaitCallback;
           {
             MonitorAutoUnlock unlock(*gMonitor);
             AutoPassThroughThreadEvents pt;
-            thread->mUnrecordedWaitCallback();
+            callback();
           }
 
           // Releasing the global lock means that we need to start over
           // checking whether there are any idle threads. By marking this
           // thread as having been notified we have made progress, however.
           done = true;
           i = MainThreadId;
         }
@@ -433,29 +442,30 @@ Thread::ResumeIdleThreads()
   gThreadsShouldIdle = false;
 
   for (size_t i = MainThreadId + 1; i <= MaxRecordedThreadId; i++) {
     Notify(i);
   }
 }
 
 void
-Thread::NotifyUnrecordedWait(const std::function<void()>& aCallback)
+Thread::NotifyUnrecordedWait(const std::function<void()>& aCallback, bool aOnlyWhenDiverged)
 {
   MonitorAutoLock lock(*gMonitor);
   if (mUnrecordedWaitCallback) {
     // Per the documentation for NotifyUnrecordedWait, we need to call the
     // routine after a notify, even if the routine has been called already
     // since the main thread started to wait for idle replay threads.
     mUnrecordedWaitNotified = false;
   } else {
     MOZ_RELEASE_ASSERT(!mUnrecordedWaitNotified);
   }
 
   mUnrecordedWaitCallback = aCallback;
+  mUnrecordedWaitOnlyWhenDiverged = aOnlyWhenDiverged;
 
   // The main thread might be able to make progress now by calling the routine
   // if it is waiting for idle replay threads.
   if (gThreadsShouldIdle) {
     Notify(MainThreadId);
   }
 }
 
@@ -467,19 +477,20 @@ Thread::MaybeWaitForCheckpointSave()
     MonitorAutoUnlock unlock(*gMonitor);
     Wait();
   }
 }
 
 extern "C" {
 
 MOZ_EXPORT void
-RecordReplayInterface_NotifyUnrecordedWait(const std::function<void()>& aCallback)
+RecordReplayInterface_NotifyUnrecordedWait(const std::function<void()>& aCallback,
+                                           bool aOnlyWhenDiverged)
 {
-  Thread::Current()->NotifyUnrecordedWait(aCallback);
+  Thread::Current()->NotifyUnrecordedWait(aCallback, aOnlyWhenDiverged);
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_MaybeWaitForCheckpointSave()
 {
   Thread::MaybeWaitForCheckpointSave();
 }
 
--- a/toolkit/recordreplay/Thread.h
+++ b/toolkit/recordreplay/Thread.h
@@ -129,16 +129,17 @@ private:
 
   // Whether the thread is waiting on idlefd.
   Atomic<bool, SequentiallyConsistent, Behavior::DontPreserve> mIdle;
 
   // Any callback which should be invoked so the thread can make progress,
   // and whether the callback has been invoked yet while the main thread is
   // waiting for threads to become idle. Protected by the thread monitor.
   std::function<void()> mUnrecordedWaitCallback;
+  bool mUnrecordedWaitOnlyWhenDiverged;
   bool mUnrecordedWaitNotified;
 
 public:
 ///////////////////////////////////////////////////////////////////////////////
 // Public Routines
 ///////////////////////////////////////////////////////////////////////////////
 
   // Accessors for some members that never change.
@@ -272,17 +273,18 @@ public:
 
   // Wait indefinitely, until the process is rewound.
   static void WaitForever();
 
   // Wait indefinitely, without allowing this thread to be rewound.
   static void WaitForeverNoIdle();
 
   // See RecordReplay.h.
-  void NotifyUnrecordedWait(const std::function<void()>& aCallback);
+  void NotifyUnrecordedWait(const std::function<void()>& aCallback,
+                            bool aOnlyWhenDiverged);
   static void MaybeWaitForCheckpointSave();
 
   // Wait for all other threads to enter the idle state necessary for saving
   // or restoring a checkpoint. This may only be called on the main thread.
   static void WaitForIdleThreads();
 
   // After WaitForIdleThreads(), the main thread will call this to allow
   // other threads to resume execution.