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 500499 baaaaa544bb0da29752973de7ce72a7d8d5ee84a
parent 500498 0ce807f9d1f679c848341e4858df243a5c127bdf
child 500500 597cf7ce986a50e436398ac99491611dbeb0b30a
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
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 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.