Bug 679551 - 'Workers: Deadlock in WorkerPrivate::BlockAndCollectRuntimeStats if worker is blocked (LastPass extension)'. r=mrbkap.
☠☠ backed out by cc0753a23f8b ☠ ☠
authorBen Turner <bent.mozilla@gmail.com>
Thu, 08 Sep 2011 17:03:03 -0700
changeset 76796 58d026601240cec8a26acc78a7cfdb6dc0bb55ae
parent 76795 78e81382db7f7c33f116ffff0bd521bca2513ece
child 76797 c5a9a439d72ca0811d977e0c7103b96ba14c8289
push id21141
push usereakhgari@mozilla.com
push dateFri, 09 Sep 2011 14:06:30 +0000
treeherdermozilla-central@694520af9b18 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs679551
milestone9.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 679551 - 'Workers: Deadlock in WorkerPrivate::BlockAndCollectRuntimeStats if worker is blocked (LastPass extension)'. r=mrbkap.
dom/workers/ChromeWorkerScope.cpp
dom/workers/RuntimeService.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
dom/workers/test/chromeWorker_worker.js
--- a/dom/workers/ChromeWorkerScope.cpp
+++ b/dom/workers/ChromeWorkerScope.cpp
@@ -40,16 +40,20 @@
 
 #include "jsapi.h"
 #include "jscntxt.h"
 
 #include "nsXPCOM.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsStringGlue.h"
 
+#include "WorkerPrivate.h"
+
+#define CTYPES_STR "ctypes"
+
 USING_WORKERS_NAMESPACE
 
 namespace {
 
 #ifdef BUILD_CTYPES
 char*
 UnicodeToNative(JSContext* aCx, const jschar* aSource, size_t aSourceLen)
 {
@@ -69,34 +73,75 @@ UnicodeToNative(JSContext* aCx, const js
   memcpy(result, native.get(), native.Length());
   result[native.Length()] = 0;
   return result;
 }
 
 JSCTypesCallbacks gCTypesCallbacks = {
   UnicodeToNative
 };
+
+JSBool
+CTypesLazyGetter(JSContext* aCx, JSObject* aObj, jsid aId, jsval* aVp)
+{
+  NS_ASSERTION(JS_GetGlobalObject(aCx) == aObj, "Not a global object!");
+  NS_ASSERTION(JSID_IS_STRING(aId), "Bad id!");
+  NS_ASSERTION(JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(aId), CTYPES_STR),
+               "Bad id!");
+
+  WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
+  NS_ASSERTION(worker->IsChromeWorker(), "This should always be true!");
+
+  if (!worker->DisableMemoryReporter()) {
+    return false;
+  }
+
+  jsval ctypes;
+  return JS_DeletePropertyById(aCx, aObj, aId) &&
+         JS_InitCTypesClass(aCx, aObj) &&
+         JS_GetPropertyById(aCx, aObj, aId, &ctypes) &&
+         JS_SetCTypesCallbacks(aCx, JSVAL_TO_OBJECT(ctypes),
+                               &gCTypesCallbacks) &&
+         JS_GetPropertyById(aCx, aObj, aId, aVp);
+}
 #endif
 
+inline bool
+DefineCTypesLazyGetter(JSContext* aCx, JSObject* aGlobal)
+{
+#ifdef BUILD_CTYPES
+  {
+    JSString* ctypesStr = JS_InternString(aCx, CTYPES_STR);
+    if (!ctypesStr) {
+      return false;
+    }
+
+    jsid ctypesId = INTERNED_STRING_TO_JSID(aCx, ctypesStr);
+
+    // We use a lazy getter here to let us unregister the blocking memory
+    // reporter since ctypes can easily block the worker thread and we can
+    // deadlock. Remove once bug 673323 is fixed.
+    if (!JS_DefinePropertyById(aCx, aGlobal, ctypesId, JSVAL_VOID,
+                               CTypesLazyGetter, NULL, 0)) {
+      return false;
+    }
+  }
+#endif
+
+  return true;
+}
+
 } // anonymous namespace
 
 BEGIN_WORKERS_NAMESPACE
 
 namespace chromeworker {
 
 bool
 DefineChromeWorkerFunctions(JSContext* aCx, JSObject* aGlobal)
 {
-#ifdef BUILD_CTYPES
-  jsval ctypes;
-  if (!JS_InitCTypesClass(aCx, aGlobal) ||
-      !JS_GetProperty(aCx, aGlobal, "ctypes", &ctypes) ||
-      !JS_SetCTypesCallbacks(aCx, JSVAL_TO_OBJECT(ctypes), &gCTypesCallbacks)) {
-    return false;
-  }
-#endif
-
-  return true;
+  // Currently ctypes is the only special property given to ChromeWorkers.
+  return DefineCTypesLazyGetter(aCx, aGlobal);
 }
 
 } // namespace chromeworker
 
 END_WORKERS_NAMESPACE
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -41,23 +41,21 @@
 
 #include "nsIDOMChromeWindow.h"
 #include "nsIDocument.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIObserverService.h"
 #include "nsIPlatformCharset.h"
 #include "nsIPrincipal.h"
 #include "nsIJSContextStack.h"
-#include "nsIMemoryReporter.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISupportsPriority.h"
 #include "nsITimer.h"
 #include "nsPIDOMWindow.h"
 
-#include "jsprf.h"
 #include "mozilla/Preferences.h"
 #include "nsContentUtils.h"
 #include "nsDOMJSUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOM.h"
@@ -282,71 +280,16 @@ CreateJSContextForWorker(WorkerPrivate* 
 
   if (aWorkerPrivate->IsChromeWorker()) {
     JS_SetVersion(workerCx, JSVERSION_LATEST);
   }
 
   return workerCx;
 }
 
-class WorkerMemoryReporter : public nsIMemoryMultiReporter
-{
-  WorkerPrivate* mWorkerPrivate;
-  nsCString mPathPrefix;
-
-public:
-  NS_DECL_ISUPPORTS
-
-  WorkerMemoryReporter(WorkerPrivate* aWorkerPrivate)
-  : mWorkerPrivate(aWorkerPrivate)
-  {
-    aWorkerPrivate->AssertIsOnWorkerThread();
-
-    nsCString escapedDomain(aWorkerPrivate->Domain());
-    escapedDomain.ReplaceChar('/', '\\');
-
-    NS_ConvertUTF16toUTF8 escapedURL(aWorkerPrivate->ScriptURL());
-    escapedURL.ReplaceChar('/', '\\');
-
-    // 64bit address plus '0x' plus null terminator.
-    char address[21];
-    JSUint32 addressSize =
-      JS_snprintf(address, sizeof(address), "0x%llx", aWorkerPrivate);
-    if (addressSize == JSUint32(-1)) {
-      NS_WARNING("JS_snprintf failed!");
-      address[0] = '\0';
-      addressSize = 0;
-    }
-
-    mPathPrefix = NS_LITERAL_CSTRING("explicit/dom/workers(") +
-                  escapedDomain + NS_LITERAL_CSTRING(")/worker(") +
-                  escapedURL + NS_LITERAL_CSTRING(", ") +
-                  nsDependentCString(address, addressSize) +
-                  NS_LITERAL_CSTRING(")/");
-  }
-
-  NS_IMETHOD
-  CollectReports(nsIMemoryMultiReporterCallback* aCallback,
-                 nsISupports* aClosure)
-  {
-    AssertIsOnMainThread();
-
-    IterateData data;
-    if (!mWorkerPrivate->BlockAndCollectRuntimeStats(&data)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    ReportJSRuntimeStats(data, mPathPrefix, aCallback, aClosure);
-
-    return NS_OK;
-  }
-};
-
-NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerMemoryReporter, nsIMemoryMultiReporter)
-
 class WorkerThreadRunnable : public nsRunnable
 {
   WorkerPrivate* mWorkerPrivate;
 
 public:
   WorkerThreadRunnable(WorkerPrivate* aWorkerPrivate)
   : mWorkerPrivate(aWorkerPrivate)
   {
@@ -363,35 +306,21 @@ public:
 
     JSContext* cx = CreateJSContextForWorker(workerPrivate);
     if (!cx) {
       // XXX need to fire an error at parent.
       NS_ERROR("Failed to create runtime and context!");
       return NS_ERROR_FAILURE;
     }
 
-    nsRefPtr<WorkerMemoryReporter> reporter =
-      new WorkerMemoryReporter(workerPrivate);
-    if (NS_FAILED(NS_RegisterMemoryMultiReporter(reporter))) {
-      NS_WARNING("Failed to register memory reporter!");
-      reporter = nsnull;
-    }
-
     {
       JSAutoRequest ar(cx);
       workerPrivate->DoRunLoop(cx);
     }
 
-    if (reporter) {
-      if (NS_FAILED(NS_UnregisterMemoryMultiReporter(reporter))) {
-        NS_WARNING("Failed to unregister memory reporter!");
-      }
-      reporter = nsnull;
-    }
-
     JSRuntime* rt = JS_GetRuntime(cx);
 
     // XXX Bug 666963 - CTypes can create another JSContext for use with
     // closures, and then it holds that context in a reserved slot on the CType
     // prototype object. We have to destroy that context before we can destroy
     // the runtime, and we also have to make sure that it isn't the last context
     // to be destroyed (otherwise it will assert). To accomplish this we create
     // an unused dummy context, destroy our real context, and then destroy the
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -40,28 +40,30 @@
 #include "WorkerPrivate.h"
 
 #include "nsIClassInfo.h"
 #include "nsIConsoleService.h"
 #include "nsIDOMFile.h"
 #include "nsIDocument.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIJSContextStack.h"
+#include "nsIMemoryReporter.h"
 #include "nsIScriptError.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsPIDOMWindow.h"
 #include "nsITextToSubURI.h"
 #include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsIXPConnect.h"
 
 #include "jscntxt.h"
 #include "jsdbgapi.h"
+#include "jsprf.h"
 #include "nsAlgorithm.h"
 #include "nsContentUtils.h"
 #include "nsDOMClassInfo.h"
 #include "nsDOMJSUtils.h"
 #include "nsGUIEvent.h"
 #include "nsJSEnvironment.h"
 #include "nsJSUtils.h"
 #include "nsNetUtil.h"
@@ -135,16 +137,97 @@ SwapToISupportsArray(SmartPtr<T>& aSrc,
   T* raw = nsnull;
   aSrc.swap(raw);
 
   nsISupports* rawSupports =
     static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw);
   dest->swap(rawSupports);
 }
 
+class WorkerMemoryReporter : public nsIMemoryMultiReporter
+{
+  WorkerPrivate* mWorkerPrivate;
+  nsCString mAddressString;
+  nsCString mPathPrefix;
+
+public:
+  NS_DECL_ISUPPORTS
+
+  WorkerMemoryReporter(WorkerPrivate* aWorkerPrivate)
+  : mWorkerPrivate(aWorkerPrivate)
+  {
+    aWorkerPrivate->AssertIsOnWorkerThread();
+
+    nsCString escapedDomain(aWorkerPrivate->Domain());
+    escapedDomain.ReplaceChar('/', '\\');
+
+    NS_ConvertUTF16toUTF8 escapedURL(aWorkerPrivate->ScriptURL());
+    escapedURL.ReplaceChar('/', '\\');
+
+    {
+      // 64bit address plus '0x' plus null terminator.
+      char address[21];
+      JSUint32 addressSize =
+        JS_snprintf(address, sizeof(address), "0x%llx", aWorkerPrivate);
+      if (addressSize != JSUint32(-1)) {
+        mAddressString.Assign(address, addressSize);
+      }
+      else {
+        NS_WARNING("JS_snprintf failed!");
+        mAddressString.AssignLiteral("<unknown address>");
+      }
+    }
+
+    mPathPrefix = NS_LITERAL_CSTRING("explicit/dom/workers(") +
+                  escapedDomain + NS_LITERAL_CSTRING(")/worker(") +
+                  escapedURL + NS_LITERAL_CSTRING(", ") + mAddressString +
+                  NS_LITERAL_CSTRING(")/");
+  }
+
+  NS_IMETHOD
+  CollectReports(nsIMemoryMultiReporterCallback* aCallback,
+                 nsISupports* aClosure)
+  {
+    AssertIsOnMainThread();
+
+    IterateData data;
+
+    if (mWorkerPrivate) {
+      bool disabled;
+      if (!mWorkerPrivate->BlockAndCollectRuntimeStats(&data, &disabled)) {
+        return NS_ERROR_FAILURE;
+      }
+
+      // Don't ever try to talk to the worker again.
+      if (disabled) {
+#ifdef DEBUG
+        {
+          nsCAutoString message("Unable to report memory for ");
+          if (mWorkerPrivate->IsChromeWorker()) {
+            message.AppendLiteral("Chrome");
+          }
+          message += NS_LITERAL_CSTRING("Worker (") + mAddressString +
+                     NS_LITERAL_CSTRING(")! It is either using ctypes or is in "
+                                        "the process of being destroyed");
+          NS_WARNING(message.get());
+        }
+#endif
+        mWorkerPrivate = nsnull;
+      }
+    }
+
+    // Always report, even if we're disabled, so that we at least get an entry
+    // in about::memory.
+    ReportJSRuntimeStats(data, mPathPrefix, aCallback, aClosure);
+    return NS_OK;
+  }
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerMemoryReporter, nsIMemoryMultiReporter)
+
 struct WorkerStructuredCloneCallbacks
 {
   static JSObject*
   Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32 aTag,
        uint32 aData, void* aClosure)
   {
     // See if object is a nsIDOMFile pointer.
     if (aTag == DOMWORKER_SCTAG_FILE) {
@@ -1277,29 +1360,29 @@ public:
 };
 #endif
 
 class CollectRuntimeStatsRunnable : public WorkerControlRunnable
 {
   typedef mozilla::Mutex Mutex;
   typedef mozilla::CondVar CondVar;
 
-  Mutex* mMutex;
-  CondVar* mCondVar;
-  volatile bool* mDoneFlag;
+  Mutex mMutex;
+  CondVar mCondVar;
+  volatile bool mDone;
   IterateData* mData;
-  volatile bool* mSucceeded;
+  bool* mSucceeded;
 
 public:
-  CollectRuntimeStatsRunnable(WorkerPrivate* aWorkerPrivate, Mutex* aMutex,
-                              CondVar* aCondVar, volatile bool* aDoneFlag,
-                              IterateData* aData, volatile bool* aSucceeded)
+  CollectRuntimeStatsRunnable(WorkerPrivate* aWorkerPrivate, IterateData* aData,
+                              bool* aSucceeded)
   : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
-    mMutex(aMutex), mCondVar(aCondVar), mDoneFlag(aDoneFlag), mData(aData),
-    mSucceeded(aSucceeded)
+    mMutex("CollectRuntimeStatsRunnable::mMutex"),
+    mCondVar(mMutex, "CollectRuntimeStatsRunnable::mCondVar"), mDone(false),
+    mData(aData), mSucceeded(aSucceeded)
   { }
 
   bool
   PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     AssertIsOnMainThread();
     return true;
   }
@@ -1307,29 +1390,46 @@ public:
   void
   PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                bool aDispatchResult)
   {
     AssertIsOnMainThread();
   }
 
   bool
+  DispatchInternal()
+  {
+    AssertIsOnMainThread();
+
+    if (!WorkerControlRunnable::DispatchInternal()) {
+      NS_WARNING("Failed to dispatch runnable!");
+      return false;
+    }
+
+    {
+      MutexAutoLock lock(mMutex);
+      while (!mDone) {
+        mCondVar.Wait();
+      }
+    }
+
+    return true;
+  }
+
+  bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     JSAutoSuspendRequest asr(aCx);
 
     *mSucceeded = CollectCompartmentStatsForRuntime(JS_GetRuntime(aCx), mData);
 
     {
-      MutexAutoLock lock(*mMutex);
-
-      NS_ASSERTION(!*mDoneFlag, "Should be false!");
-
-      *mDoneFlag = true;
-      mCondVar->Notify();
+      MutexAutoLock lock(mMutex);
+      mDone = true;
+      mCondVar.Notify();
     }
 
     return true;
   }
 };
 
 } /* anonymous namespace */
 
@@ -2096,17 +2196,18 @@ WorkerPrivate::WorkerPrivate(JSContext* 
                              nsCOMPtr<nsIDocument>& aDocument)
 : WorkerPrivateParent<WorkerPrivate>(aCx, aObject, aParent, aParentJSContext,
                                      aScriptURL, aIsChromeWorker, aDomain,
                                      aWindow, aParentScriptContext, aBaseURI,
                                      aPrincipal, aDocument),
   mJSContext(nsnull), mErrorHandlerRecursionCount(0), mNextTimeoutId(1),
   mStatus(Pending), mSuspended(false), mTimerRunning(false),
   mRunningExpiredTimeouts(false), mCloseHandlerStarted(false),
-  mCloseHandlerFinished(false)
+  mCloseHandlerFinished(false), mMemoryReporterRunning(false),
+  mMemoryReporterDisabled(false)
 {
   MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivate);
 }
 
 WorkerPrivate::~WorkerPrivate()
 {
   MOZ_COUNT_DTOR(mozilla::dom::workers::WorkerPrivate);
 }
@@ -2303,16 +2404,23 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
   {
     MutexAutoLock lock(mMutex);
     mJSContext = aCx;
 
     NS_ASSERTION(mStatus == Pending, "Huh?!");
     mStatus = Running;
   }
 
+  mMemoryReporter = new WorkerMemoryReporter(this);
+
+  if (NS_FAILED(NS_RegisterMemoryMultiReporter(mMemoryReporter))) {
+    NS_WARNING("Failed to register memory reporter!");
+    mMemoryReporter = nsnull;
+  }
+
   for (;;) {
     Status currentStatus;
     nsIRunnable* event;
     {
       MutexAutoLock lock(mMutex);
 
       while (!mControlQueue.Pop(event) && !mQueue.Pop(event)) {
         mCondVar.Wait();
@@ -2353,16 +2461,27 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
         NS_ASSERTION(currentStatus == Killing, "Should have changed status!");
 #else
         currentStatus = Killing;
 #endif
       }
 
       // If we're supposed to die then we should exit the loop.
       if (currentStatus == Killing) {
+        // Call this before unregistering the reporter as we may be racing with
+        // the main thread.
+        DisableMemoryReporter();
+
+        if (mMemoryReporter) {
+          if (NS_FAILED(NS_UnregisterMemoryMultiReporter(mMemoryReporter))) {
+            NS_WARNING("Failed to unregister memory reporter!");
+          }
+          mMemoryReporter = nsnull;
+        }
+
         StopAcceptingEvents();
         return;
       }
     }
   }
 
   NS_NOTREACHED("Shouldn't get here!");
 }
@@ -2371,31 +2490,17 @@ bool
 WorkerPrivate::OperationCallback(JSContext* aCx)
 {
   AssertIsOnWorkerThread();
 
   bool mayContinue = true;
 
   for (;;) {
     // Run all control events now.
-    for (;;) {
-      nsIRunnable* event;
-      {
-        MutexAutoLock lock(mMutex);
-        if (!mControlQueue.Pop(event)) {
-          break;
-        }
-      }
-
-      if (NS_FAILED(event->Run())) {
-        mayContinue = false;
-      }
-
-      NS_RELEASE(event);
-    }
+    mayContinue = ProcessAllControlRunnables();
 
     if (!mayContinue || !mSuspended) {
       break;
     }
 
     // Clean up before suspending.
     JS_FlushCaches(aCx);
     JS_GC(aCx);
@@ -2452,46 +2557,97 @@ WorkerPrivate::ScheduleDeletion(bool aWa
       new TopLevelWorkerFinishedRunnable(this, currentThread);
     if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) {
       NS_WARNING("Failed to dispatch runnable!");
     }
   }
 }
 
 bool
-WorkerPrivate::BlockAndCollectRuntimeStats(IterateData* aData)
+WorkerPrivate::BlockAndCollectRuntimeStats(IterateData* aData, bool* aDisabled)
 {
   AssertIsOnMainThread();
-  mMutex.AssertNotCurrentThreadOwns();
   NS_ASSERTION(aData, "Null data!");
 
-  mozilla::Mutex mutex("BlockAndCollectRuntimeStats mutex");
-  mozilla::CondVar condvar(mutex, "BlockAndCollectRuntimeStats condvar");
-  volatile bool doneFlag = false;
-  volatile bool succeeded = false;
+  {
+    MutexAutoLock lock(mMutex);
+
+    if (mMemoryReporterDisabled) {
+      *aDisabled = true;
+      return true;
+    }
+
+    *aDisabled = false;
+    mMemoryReporterRunning = true;
+  }
+
+  bool succeeded;
 
   nsRefPtr<CollectRuntimeStatsRunnable> runnable =
-    new CollectRuntimeStatsRunnable(this, &mutex, &condvar, &doneFlag, aData,
-                                    &succeeded);
+    new CollectRuntimeStatsRunnable(this, aData, &succeeded);
   if (!runnable->Dispatch(nsnull)) {
     NS_WARNING("Failed to dispatch runnable!");
-    return false;
+    succeeded = false;
   }
 
   {
-    MutexAutoLock lock(mutex);
-    while (!doneFlag) {
-      condvar.Wait();
-    }
+    MutexAutoLock lock(mMutex);
+    mMemoryReporterRunning = false;
   }
 
   return succeeded;
 }
 
 bool
+WorkerPrivate::DisableMemoryReporter()
+{
+  AssertIsOnWorkerThread();
+
+  bool result = true;
+
+  {
+    MutexAutoLock lock(mMutex);
+
+    mMemoryReporterDisabled = true;
+
+    while (mMemoryReporterRunning) {
+      MutexAutoUnlock unlock(mMutex);
+      result = ProcessAllControlRunnables() && result;
+    }
+  }
+
+  return result;
+}
+
+bool
+WorkerPrivate::ProcessAllControlRunnables()
+{
+  AssertIsOnWorkerThread();
+
+  bool result = true;
+
+  for (;;) {
+    nsIRunnable* event;
+    {
+      MutexAutoLock lock(mMutex);
+      if (!mControlQueue.Pop(event)) {
+        break;
+      }
+    }
+
+    if (NS_FAILED(event->Run())) {
+      result = false;
+    }
+
+    NS_RELEASE(event);
+  }
+  return result;
+}
+
+bool
 WorkerPrivate::Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue)
 {
   nsRefPtr<WorkerRunnable> event(aEvent);
 
   {
     MutexAutoLock lock(mMutex);
 
     if (mStatus == Dead) {
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -58,16 +58,17 @@
 
 #include "EventTarget.h"
 #include "Queue.h"
 #include "WorkerFeature.h"
 
 class JSAutoStructuredCloneBuffer;
 class nsIDocument;
 class nsIPrincipal;
+class nsIMemoryMultiReporter;
 class nsIScriptContext;
 class nsIURI;
 class nsPIDOMWindow;
 class nsITimer;
 
 namespace mozilla {
 namespace xpconnect {
 namespace memory {
@@ -507,26 +508,29 @@ class WorkerPrivate : public WorkerPriva
   JSContext* mJSContext;
 
   // Things touched on worker thread only.
   nsTArray<ParentType*> mChildWorkers;
   nsTArray<WorkerFeature*> mFeatures;
   nsTArray<nsAutoPtr<TimeoutInfo> > mTimeouts;
 
   nsCOMPtr<nsITimer> mTimer;
+  nsCOMPtr<nsIMemoryMultiReporter> mMemoryReporter;
 
   mozilla::TimeStamp mKillTime;
   PRUint32 mErrorHandlerRecursionCount;
   PRUint32 mNextTimeoutId;
   Status mStatus;
   bool mSuspended;
   bool mTimerRunning;
   bool mRunningExpiredTimeouts;
   bool mCloseHandlerStarted;
   bool mCloseHandlerFinished;
+  bool mMemoryReporterRunning;
+  bool mMemoryReporterDisabled;
 
 #ifdef DEBUG
   nsCOMPtr<nsIThread> mThread;
 #endif
 
 public:
   ~WorkerPrivate();
 
@@ -649,17 +653,21 @@ public:
 
   void
   UpdateJSContextOptionsInternal(JSContext* aCx, PRUint32 aOptions);
 
   void
   ScheduleDeletion(bool aWasPending);
 
   bool
-  BlockAndCollectRuntimeStats(mozilla::xpconnect::memory::IterateData* aData);
+  BlockAndCollectRuntimeStats(mozilla::xpconnect::memory::IterateData* aData,
+                              bool* aDisabled);
+
+  bool
+  DisableMemoryReporter();
 
 #ifdef JS_GC_ZEAL
   void
   UpdateGCZealInternal(JSContext* aCx, PRUint8 aGCZeal);
 #endif
 
   JSContext*
   GetJSContext() const
@@ -738,16 +746,19 @@ private:
     mozilla::MutexAutoLock lock(mMutex);
 
     mStatus = Dead;
     mJSContext = nsnull;
 
     ClearQueue(&mQueue);
     ClearQueue(&mControlQueue);
   }
+
+  bool
+  ProcessAllControlRunnables();
 };
 
 WorkerPrivate*
 GetWorkerPrivateFromContext(JSContext* aCx);
 
 enum WorkerStructuredDataType
 {
   DOMWORKER_SCTAG_FILE = JS_SCTAG_USER_MIN + 0x1000,
--- a/dom/workers/test/chromeWorker_worker.js
+++ b/dom/workers/test/chromeWorker_worker.js
@@ -1,15 +1,20 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 if (!("ctypes" in self)) {
   throw "No ctypes!";
 }
 
+// Go ahead and verify that the ctypes lazy getter actually works.
+if (ctypes.toString() != "[object ctypes]") {
+  throw "Bad ctypes object: " + ctypes.toString();
+}
+
 onmessage = function(event) {
   let worker = new ChromeWorker("chromeWorker_subworker.js");
   worker.onmessage = function(event) {
     postMessage(event.data);
   }
   worker.postMessage(event.data);
 }