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
--- 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();