Bug 711388 - 'WORKER_RUNTIME_HEAPSIZE too small for big PDFs in PD.JS'. r=sicking, a=keybl. CLOSED TREE
authorBen Turner <bent.mozilla@gmail.com>
Wed, 04 Jan 2012 11:11:32 -0800
changeset 81670 14f36ee6e58c0399fcd9af06a78e47ab3c9016cd
parent 81665 6010b20c7beada99a7d96fceef9ce1c8e2a5b88e
child 81671 15c3707ee5ae0a273e2c33ceec38aefa5c91a89e
push id16
push userbturner@mozilla.com
push dateFri, 17 Feb 2012 00:23:37 +0000
reviewerssicking, keybl
bugs711388
milestone10.0.2
Bug 711388 - 'WORKER_RUNTIME_HEAPSIZE too small for big PDFs in PD.JS'. r=sicking, a=keybl. CLOSED TREE
dom/workers/RuntimeService.cpp
dom/workers/RuntimeService.h
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -73,18 +73,18 @@ using namespace mozilla;
 
 USING_WORKERS_NAMESPACE
 
 using mozilla::MutexAutoLock;
 using mozilla::MutexAutoUnlock;
 using mozilla::Preferences;
 using namespace mozilla::xpconnect::memory;
 
-// The size of the worker runtime heaps in bytes.
-#define WORKER_RUNTIME_HEAPSIZE 32 * 1024 * 1024
+// The size of the worker runtime heaps in bytes. May be changed via pref.
+#define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
 
 // The C stack size. We use the same stack size on all platforms for
 // consistency.
 #define WORKER_STACK_SIZE 256 * sizeof(size_t) * 1024
 
 // The stack limit the JS engine will check. Half the size of the
 // actual C stack, to be safe.
 #define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024
@@ -149,16 +149,17 @@ PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gString
 enum {
   PREF_strict = 0,
   PREF_werror,
   PREF_relimit,
   PREF_tracejit,
   PREF_methodjit,
   PREF_jitprofiling,
   PREF_methodjit_always,
+  PREF_mem_max,
 
 #ifdef JS_GC_ZEAL
   PREF_gczeal,
 #endif
 
   PREF_COUNT
 };
 
@@ -166,17 +167,18 @@ enum {
 
 const char* gPrefsToWatch[] = {
   JS_OPTIONS_DOT_STR "strict",
   JS_OPTIONS_DOT_STR "werror",
   JS_OPTIONS_DOT_STR "relimit",
   JS_OPTIONS_DOT_STR "tracejit.content",
   JS_OPTIONS_DOT_STR "methodjit.content",
   JS_OPTIONS_DOT_STR "jitprofiling.content",
-  JS_OPTIONS_DOT_STR "methodjit_always"
+  JS_OPTIONS_DOT_STR "methodjit_always",
+  JS_OPTIONS_DOT_STR "mem.max"
 
 #ifdef JS_GC_ZEAL
   , PREF_WORKERS_GCZEAL
 #endif
 };
 
 PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gPrefsToWatch) == PREF_COUNT);
 
@@ -185,17 +187,25 @@ PrefCallback(const char* aPrefName, void
 {
   AssertIsOnMainThread();
 
   RuntimeService* rts = static_cast<RuntimeService*>(aClosure);
   NS_ASSERTION(rts, "This should never be null!");
 
   NS_NAMED_LITERAL_CSTRING(jsOptionStr, JS_OPTIONS_DOT_STR);
 
-  if(StringBeginsWith(nsDependentCString(aPrefName), jsOptionStr)) {
+  if (!strcmp(aPrefName, gPrefsToWatch[PREF_mem_max])) {
+    PRInt32 pref = Preferences::GetInt(aPrefName, -1);
+    PRUint32 maxBytes = (pref <= 0 || pref >= 0x1000) ?
+                        PRUint32(-1) :
+                        PRUint32(pref) * 1024 * 1024;
+    RuntimeService::SetDefaultJSRuntimeHeapSize(maxBytes);
+    rts->UpdateAllWorkerJSRuntimeHeapSize();
+  }
+  else if (StringBeginsWith(nsDependentCString(aPrefName), jsOptionStr)) {
     PRUint32 newOptions = kRequiredJSContextOptions;
     if (Preferences::GetBool(gPrefsToWatch[PREF_strict])) {
       newOptions |= JSOPTION_STRICT;
     }
     if (Preferences::GetBool(gPrefsToWatch[PREF_werror])) {
       newOptions |= JSOPTION_WERROR;
     }
     if (Preferences::GetBool(gPrefsToWatch[PREF_relimit])) {
@@ -241,22 +251,28 @@ OperationCallback(JSContext* aCx)
 }
 
 JSContext*
 CreateJSContextForWorker(WorkerPrivate* aWorkerPrivate)
 {
   aWorkerPrivate->AssertIsOnWorkerThread();
   NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!");
 
-  JSRuntime* runtime = JS_NewRuntime(WORKER_RUNTIME_HEAPSIZE);
+  // The number passed here doesn't matter, we're about to change it in the call
+  // to JS_SetGCParameter.
+  JSRuntime* runtime = JS_NewRuntime(WORKER_DEFAULT_RUNTIME_HEAPSIZE);
   if (!runtime) {
     NS_WARNING("Could not create new runtime!");
     return nsnull;
   }
 
+  // This is the real place where we set the max memory for the runtime.
+  JS_SetGCParameter(runtime, JSGC_MAX_BYTES,
+                    aWorkerPrivate->GetJSRuntimeHeapSize());
+
   JSContext* workerCx = JS_NewContext(runtime, 0);
   if (!workerCx) {
     JS_DestroyRuntime(runtime);
     NS_WARNING("Could not create new context!");
     return nsnull;
   }
 
   JS_SetContextPrivate(workerCx, aWorkerPrivate);
@@ -464,16 +480,19 @@ ResumeWorkersForWindow(JSContext* aCx, n
     runtime->ResumeWorkersForWindow(aCx, aWindow);
   }
 }
 
 END_WORKERS_NAMESPACE
 
 PRUint32 RuntimeService::sDefaultJSContextOptions = kRequiredJSContextOptions;
 
+PRUint32 RuntimeService::sDefaultJSRuntimeHeapSize =
+  WORKER_DEFAULT_RUNTIME_HEAPSIZE;
+
 PRInt32 RuntimeService::sCloseHandlerTimeoutSeconds = MAX_SCRIPT_RUN_TIME_SEC;
 
 #ifdef JS_GC_ZEAL
 PRUint8 RuntimeService::sDefaultGCZeal = 0;
 #endif
 
 RuntimeService::RuntimeService()
 : mMutex("RuntimeService::mMutex"), mObserved(false),
@@ -1135,16 +1154,37 @@ RuntimeService::UpdateAllWorkerJSContext
   if (!workers.IsEmpty()) {
     AutoSafeJSContext cx;
     for (PRUint32 index = 0; index < workers.Length(); index++) {
       workers[index]->UpdateJSContextOptions(cx, GetDefaultJSContextOptions());
     }
   }
 }
 
+void
+RuntimeService::UpdateAllWorkerJSRuntimeHeapSize()
+{
+  AssertIsOnMainThread();
+
+  nsAutoTArray<WorkerPrivate*, 100> workers;
+  {
+    MutexAutoLock lock(mMutex);
+
+    mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers);
+  }
+
+  if (!workers.IsEmpty()) {
+    AutoSafeJSContext cx;
+    for (PRUint32 index = 0; index < workers.Length(); index++) {
+      workers[index]->UpdateJSRuntimeHeapSize(cx,
+                                              GetDefaultJSRuntimeHeapSize());
+    }
+  }
+}
+
 #ifdef JS_GC_ZEAL
 void
 RuntimeService::UpdateAllWorkerGCZeal()
 {
   AssertIsOnMainThread();
 
   nsAutoTArray<WorkerPrivate*, 100> workers;
   {
--- a/dom/workers/RuntimeService.h
+++ b/dom/workers/RuntimeService.h
@@ -99,16 +99,17 @@ class RuntimeService : public nsIObserve
 
   // Only used on the main thread.
   nsCOMPtr<nsITimer> mIdleThreadTimer;
 
   nsCString mDetectorName;
   nsCString mSystemCharset;
 
   static PRUint32 sDefaultJSContextOptions;
+  static PRUint32 sDefaultJSRuntimeHeapSize;
   static PRInt32 sCloseHandlerTimeoutSeconds;
 
 #ifdef JS_GC_ZEAL
   static PRUint8 sDefaultGCZeal;
 #endif
 
 public:
   struct NavigatorStrings
@@ -186,16 +187,33 @@ public:
     AssertIsOnMainThread();
     sDefaultJSContextOptions = aOptions;
   }
 
   void
   UpdateAllWorkerJSContextOptions();
 
   static PRUint32
+  GetDefaultJSRuntimeHeapSize()
+  {
+    AssertIsOnMainThread();
+    return sDefaultJSRuntimeHeapSize;
+  }
+
+  static void
+  SetDefaultJSRuntimeHeapSize(PRUint32 aMaxBytes)
+  {
+    AssertIsOnMainThread();
+    sDefaultJSRuntimeHeapSize = aMaxBytes;
+  }
+
+  void
+  UpdateAllWorkerJSRuntimeHeapSize();
+
+  static PRUint32
   GetCloseHandlerTimeoutSeconds()
   {
     return sCloseHandlerTimeoutSeconds > 0 ? sCloseHandlerTimeoutSeconds : 0;
   }
 
 #ifdef JS_GC_ZEAL
   static PRUint8
   GetDefaultGCZeal()
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1342,16 +1342,35 @@ public:
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     aWorkerPrivate->UpdateJSContextOptionsInternal(aCx, mOptions);
     return true;
   }
 };
 
+class UpdateJSRuntimeHeapSizeRunnable : public WorkerControlRunnable
+{
+  PRUint32 mJSRuntimeHeapSize;
+
+public:
+  UpdateJSRuntimeHeapSizeRunnable(WorkerPrivate* aWorkerPrivate,
+                                  PRUint32 aJSRuntimeHeapSize)
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
+    mJSRuntimeHeapSize(aJSRuntimeHeapSize)
+  { }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  {
+    aWorkerPrivate->UpdateJSRuntimeHeapSizeInternal(aCx, mJSRuntimeHeapSize);
+    return true;
+  }
+};
+
 #ifdef JS_GC_ZEAL
 class UpdateGCZealRunnable : public WorkerControlRunnable
 {
   PRUint8 mGCZeal;
 
 public:
   UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate,
                        PRUint8 aGCZeal)
@@ -1697,17 +1716,18 @@ WorkerPrivateParent<Derived>::WorkerPriv
                                      nsCOMPtr<nsIScriptContext>& aScriptContext,
                                      nsCOMPtr<nsIURI>& aBaseURI,
                                      nsCOMPtr<nsIPrincipal>& aPrincipal,
                                      nsCOMPtr<nsIDocument>& aDocument)
 : mMutex("WorkerPrivateParent Mutex"),
   mCondVar(mMutex, "WorkerPrivateParent CondVar"),
   mJSObject(aObject), mParent(aParent), mParentJSContext(aParentJSContext),
   mScriptURL(aScriptURL), mDomain(aDomain), mBusyCount(0),
-  mParentStatus(Pending), mJSObjectRooted(false), mParentSuspended(false),
+  mParentStatus(Pending), mJSContextOptions(0), mJSRuntimeHeapSize(0),
+  mGCZeal(0), mJSObjectRooted(false), mParentSuspended(false),
   mIsChromeWorker(aIsChromeWorker), mPrincipalIsSystem(false)
 {
   MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivateParent);
 
   if (aWindow) {
     NS_ASSERTION(aWindow->IsInnerWindow(), "Should have inner window here!");
   }
 
@@ -1718,24 +1738,31 @@ WorkerPrivateParent<Derived>::WorkerPriv
   mDocument.swap(aDocument);
 
   if (aParent) {
     aParent->AssertIsOnWorkerThread();
 
     NS_ASSERTION(JS_GetOptions(aCx) == aParent->GetJSContextOptions(),
                  "Options mismatch!");
     mJSContextOptions = aParent->GetJSContextOptions();
+
+    NS_ASSERTION(JS_GetGCParameter(JS_GetRuntime(aCx), JSGC_MAX_BYTES) ==
+                 aParent->GetJSRuntimeHeapSize(),
+                 "Runtime heap size mismatch!");
+    mJSRuntimeHeapSize = aParent->GetJSRuntimeHeapSize();
+
 #ifdef JS_GC_ZEAL
     mGCZeal = aParent->GetGCZeal();
 #endif
   }
   else {
     AssertIsOnMainThread();
 
     mJSContextOptions = RuntimeService::GetDefaultJSContextOptions();
+    mJSRuntimeHeapSize = RuntimeService::GetDefaultJSRuntimeHeapSize();
 #ifdef JS_GC_ZEAL
     mGCZeal = RuntimeService::GetDefaultGCZeal();
 #endif
   }
 }
 
 template <class Derived>
 WorkerPrivateParent<Derived>::~WorkerPrivateParent()
@@ -2066,16 +2093,33 @@ WorkerPrivateParent<Derived>::UpdateJSCo
   nsRefPtr<UpdateJSContextOptionsRunnable> runnable =
     new UpdateJSContextOptionsRunnable(ParentAsWorkerPrivate(), aOptions);
   if (!runnable->Dispatch(aCx)) {
     NS_WARNING("Failed to update worker context options!");
     JS_ClearPendingException(aCx);
   }
 }
 
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateJSRuntimeHeapSize(JSContext* aCx,
+                                                      PRUint32 aMaxBytes)
+{
+  AssertIsOnParentThread();
+
+  mJSRuntimeHeapSize = aMaxBytes;
+
+  nsRefPtr<UpdateJSRuntimeHeapSizeRunnable> runnable =
+    new UpdateJSRuntimeHeapSizeRunnable(ParentAsWorkerPrivate(), aMaxBytes);
+  if (!runnable->Dispatch(aCx)) {
+    NS_WARNING("Failed to update worker heap size!");
+    JS_ClearPendingException(aCx);
+  }
+}
+
 #ifdef JS_GC_ZEAL
 template <class Derived>
 void
 WorkerPrivateParent<Derived>::UpdateGCZeal(JSContext* aCx, PRUint8 aGCZeal)
 {
   AssertIsOnParentThread();
 
   mGCZeal = aGCZeal;
@@ -3179,22 +3223,16 @@ WorkerPrivate::ReportError(JSContext* aC
                "Bad recursion logic!");
 
   JS_ClearPendingException(aCx);
 
   nsString message, filename, line;
   PRUint32 lineNumber, columnNumber, flags, errorNumber;
 
   if (aReport) {
-    // Can't do anything here if we're out of memory.
-    if (aReport->errorNumber == JSMSG_OUT_OF_MEMORY) {
-      NS_WARNING("Out of memory!");
-      return;
-    }
-
     if (aReport->ucmessage) {
       message = aReport->ucmessage;
     }
     filename = NS_ConvertUTF8toUTF16(aReport->filename);
     line = aReport->uclinebuf;
     lineNumber = aReport->lineno;
     columnNumber = aReport->uctokenptr - aReport->uclinebuf;
     flags = aReport->flags;
@@ -3207,19 +3245,20 @@ WorkerPrivate::ReportError(JSContext* aC
 
   if (message.IsEmpty()) {
     message = NS_ConvertUTF8toUTF16(aMessage);
   }
 
   mErrorHandlerRecursionCount++;
 
   // Don't want to run the scope's error handler if this is a recursive error or
-  // if there was an error in the close handler.
+  // if there was an error in the close handler or if we ran out of memory.
   bool fireAtScope = mErrorHandlerRecursionCount == 1 &&
-                     !mCloseHandlerStarted;
+                     !mCloseHandlerStarted &&
+                     errorNumber != JSMSG_OUT_OF_MEMORY;
 
   if (!ReportErrorRunnable::ReportError(aCx, this, fireAtScope, nsnull, message,
                                         filename, line, lineNumber,
                                         columnNumber, flags, errorNumber, 0)) {
     JS_ReportPendingException(aCx);
   }
 
   mErrorHandlerRecursionCount--;
@@ -3521,16 +3560,29 @@ WorkerPrivate::UpdateJSContextOptionsInt
 
   JS_SetOptions(aCx, aOptions);
 
   for (PRUint32 index = 0; index < mChildWorkers.Length(); index++) {
     mChildWorkers[index]->UpdateJSContextOptions(aCx, aOptions);
   }
 }
 
+void
+WorkerPrivate::UpdateJSRuntimeHeapSizeInternal(JSContext* aCx,
+                                               PRUint32 aMaxBytes)
+{
+  AssertIsOnWorkerThread();
+
+  JS_SetGCParameter(JS_GetRuntime(aCx), JSGC_MAX_BYTES, aMaxBytes);
+
+  for (PRUint32 index = 0; index < mChildWorkers.Length(); index++) {
+    mChildWorkers[index]->UpdateJSRuntimeHeapSize(aCx, aMaxBytes);
+  }
+}
+
 #ifdef JS_GC_ZEAL
 void
 WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, PRUint8 aGCZeal)
 {
   AssertIsOnWorkerThread();
 
   PRUint32 frequency = aGCZeal <= 2 ? JS_DEFAULT_ZEAL_FREQ : 1;
   JS_SetGCZeal(aCx, aGCZeal, frequency, false);
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -211,16 +211,17 @@ private:
   nsCOMPtr<nsIDocument> mDocument;
 
   // Only used for top level workers.
   nsTArray<nsRefPtr<WorkerRunnable> > mQueuedRunnables;
 
   PRUint64 mBusyCount;
   Status mParentStatus;
   PRUint32 mJSContextOptions;
+  PRUint32 mJSRuntimeHeapSize;
   PRUint8 mGCZeal;
   bool mJSObjectRooted;
   bool mParentSuspended;
   bool mIsChromeWorker;
   bool mPrincipalIsSystem;
 
 protected:
   WorkerPrivateParent(JSContext* aCx, JSObject* aObject, WorkerPrivate* aParent,
@@ -302,16 +303,19 @@ public:
   PostMessage(JSContext* aCx, jsval aMessage);
 
   PRUint64
   GetInnerWindowId();
 
   void
   UpdateJSContextOptions(JSContext* aCx, PRUint32 aOptions);
 
+  void
+  UpdateJSRuntimeHeapSize(JSContext* aCx, PRUint32 aJSRuntimeHeapSize);
+
 #ifdef JS_GC_ZEAL
   void
   UpdateGCZeal(JSContext* aCx, PRUint8 aGCZeal);
 #endif
 
   using events::EventTarget::GetEventListenerOnEventTarget;
   using events::EventTarget::SetEventListenerOnEventTarget;
 
@@ -438,16 +442,22 @@ public:
   }
 
   PRUint32
   GetJSContextOptions() const
   {
     return mJSContextOptions;
   }
 
+  PRUint32
+  GetJSRuntimeHeapSize() const
+  {
+    return mJSRuntimeHeapSize;
+  }
+
 #ifdef JS_GC_ZEAL
   PRUint8
   GetGCZeal() const
   {
     return mGCZeal;
   }
 #endif
 
@@ -652,16 +662,19 @@ public:
     AssertIsOnWorkerThread();
     mCloseHandlerFinished = true;
   }
 
   void
   UpdateJSContextOptionsInternal(JSContext* aCx, PRUint32 aOptions);
 
   void
+  UpdateJSRuntimeHeapSizeInternal(JSContext* aCx, PRUint32 aJSRuntimeHeapSize);
+
+  void
   ScheduleDeletion(bool aWasPending);
 
   bool
   BlockAndCollectRuntimeStats(mozilla::xpconnect::memory::IterateData* aData,
                               bool* aDisabled);
 
   bool
   DisableMemoryReporter();