Bug 1382329: Part 3 - Wait for pending parse tasks to finish before freeing scripts. r?erahm,nbp draft
authorKris Maglione <maglione.k@gmail.com>
Tue, 18 Jul 2017 14:40:57 -0700
changeset 611645 89491b1bda252e5bbf9e0390a6aa10fb473c26d5
parent 611521 be3b9390fed01785718ad7370fa41651368ff5c4
child 611646 e5b81397e9fe17a23a1606053a595ef8fefe4762
push id69284
push usermaglione.k@gmail.com
push dateThu, 20 Jul 2017 00:17:58 +0000
reviewerserahm, nbp
bugs1382329
milestone56.0a1
Bug 1382329: Part 3 - Wait for pending parse tasks to finish before freeing scripts. r?erahm,nbp Off-thread parse tasks depend on the memory allocated by the main-thread script preloader, so that memory needs to be kept alive until they finish. In normal use cases, this is guaranteed, but when the browser shuts down very quickly (as sometimes happens in tests), or we invalidate the startup caches in the middle of the startup process, they can sometimes be freed too early. MozReview-Commit-ID: GQmkVbWgTH9
js/xpconnect/loader/ScriptPreloader.cpp
js/xpconnect/loader/ScriptPreloader.h
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -261,35 +261,51 @@ ScriptPreloader::Cleanup()
         // the main thread, or we will deadlock.
         MOZ_RELEASE_ASSERT(!mBlockedOnSyncDispatch);
 
         while (!mSaveComplete && mSaveThread) {
             mal.Wait();
         }
     }
 
+    // Wait for any pending parses to finish before clearing the mScripts
+    // hashtable, since the parse tasks depend on memory allocated by those
+    // scripts.
+    {
+        MonitorAutoLock mal(mMonitor);
+        FinishPendingParses(mal);
+    }
+
     mScripts.Clear();
 
     AutoSafeJSAPI jsapi;
     JS_RemoveExtraGCRootsTracer(jsapi.cx(), TraceOp, this);
 
     UnregisterWeakMemoryReporter(this);
 }
 
 void
 ScriptPreloader::InvalidateCache()
 {
     mMonitor.AssertNotCurrentThreadOwns();
     MonitorAutoLock mal(mMonitor);
 
     mCacheInvalidated = true;
 
-    mParsingScripts.clearAndFree();
-    while (auto script = mPendingScripts.getFirst())
-        script->remove();
+    // Wait for pending off-thread parses to finish, since they depend on the
+    // memory allocated by our CachedScripts, and can't be canceled
+    // asynchronously.
+    FinishPendingParses(mal);
+
+    // Pending scripts should have been cleared by the above, and new parses
+    // should not have been queued.
+    MOZ_ASSERT(mParsingScripts.isEmpty());
+    MOZ_ASSERT(mParsingSources.isEmpty());
+    MOZ_ASSERT(mPendingScripts.isEmpty());
+
     for (auto& script : IterHash(mScripts))
         script.Remove();
 
     // If we've already finished saving the cache at this point, start a new
     // delayed save operation. This will write out an empty cache file in place
     // of any cache file we've already written out this session, which will
     // prevent us from falling back to the current session's cache file on the
     // next startup.
@@ -848,16 +864,30 @@ ScriptPreloader::OffThreadDecodeCallback
         NS_DispatchToMainThread(
           NewRunnableMethod("ScriptPreloader::DoFinishOffThreadDecode",
                             cache,
                             &ScriptPreloader::DoFinishOffThreadDecode));
     }
 }
 
 void
+ScriptPreloader::FinishPendingParses(MonitorAutoLock& aMal)
+{
+    mMonitor.AssertCurrentThreadOwns();
+
+    mPendingScripts.clear();
+
+    FinishOffThreadDecode();
+    while (!mParsingScripts.empty()) {
+        aMal.Wait();
+        FinishOffThreadDecode();
+    }
+}
+
+void
 ScriptPreloader::DoFinishOffThreadDecode()
 {
     mFinishDecodeRunnablePending = false;
     FinishOffThreadDecode();
 }
 
 void
 ScriptPreloader::FinishOffThreadDecode()
--- a/js/xpconnect/loader/ScriptPreloader.h
+++ b/js/xpconnect/loader/ScriptPreloader.h
@@ -355,16 +355,17 @@ private:
     // thread for quite as long.
     static constexpr int MAX_MAINTHREAD_DECODE_SIZE = 50 * 1024;
 
     ScriptPreloader();
 
     void ForceWriteCacheFile();
     void Cleanup();
 
+    void FinishPendingParses(MonitorAutoLock& aMal);
     void InvalidateCache();
 
     // Opens the cache file for reading.
     Result<Ok, nsresult> OpenCache();
 
     // Writes a new cache file to disk. Must not be called on the main thread.
     Result<Ok, nsresult> WriteCache();