Bug 813867 - 'Report memory for web workers that use ctypes'. r=khuey+jorendorff, a=jlebar.
authorBen Turner <bent.mozilla@gmail.com>
Sun, 30 Dec 2012 10:21:52 -0800
changeset 122486 a1d1e72afe821a332e61461cd795a448f1130bfc
parent 122485 1e245932118f43b9a4870476104f491d79ba95dd
child 122487 d14b0128b5aead75abbba849fd64feaa9ca3d5c6
push id1997
push userakeybl@mozilla.com
push dateMon, 07 Jan 2013 21:25:26 +0000
treeherdermozilla-beta@4baf45cdcf21 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey, jlebar
bugs813867
milestone19.0a2
Bug 813867 - 'Report memory for web workers that use ctypes'. r=khuey+jorendorff, a=jlebar.
dom/workers/ChromeWorkerScope.cpp
dom/workers/RuntimeService.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
js/src/ctypes/CTypes.cpp
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
--- a/dom/workers/ChromeWorkerScope.cpp
+++ b/dom/workers/ChromeWorkerScope.cpp
@@ -8,24 +8,23 @@
 #include "jsapi.h"
 
 #include "nsXPCOM.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsStringGlue.h"
 
 #include "WorkerPrivate.h"
 
-#define CTYPES_STR "ctypes"
-
 using namespace mozilla::dom;
 USING_WORKERS_NAMESPACE
 
 namespace {
 
 #ifdef BUILD_CTYPES
+
 char*
 UnicodeToNative(JSContext* aCx, const jschar* aSource, size_t aSourceLen)
 {
   nsDependentString unicode(aSource, aSourceLen);
 
   nsAutoCString native;
   if (NS_FAILED(NS_CopyUnicodeToNative(unicode, native))) {
     JS_ReportError(aCx, "Could not convert string to native charset!");
@@ -37,75 +36,38 @@ UnicodeToNative(JSContext* aCx, const js
     return nullptr;
   }
 
   memcpy(result, native.get(), native.Length());
   result[native.Length()] = 0;
   return result;
 }
 
-JSCTypesCallbacks gCTypesCallbacks = {
-  UnicodeToNative
-};
-
-JSBool
-CTypesLazyGetter(JSContext* aCx, JSHandleObject aObj, JSHandleId aId, JSMutableHandleValue 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;
-  if (!JS_DeletePropertyById(aCx, aObj, aId) ||
-      !JS_InitCTypesClass(aCx, aObj) ||
-      !JS_GetPropertyById(aCx, aObj, aId, &ctypes)) {
-    return false;
-  }
-  JS_SetCTypesCallbacks(JSVAL_TO_OBJECT(ctypes), &gCTypesCallbacks);
-  return JS_GetPropertyById(aCx, aObj, aId, aVp.address());
-}
-#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;
-}
+#endif // BUILD_CTYPES
 
 } // anonymous namespace
 
 BEGIN_WORKERS_NAMESPACE
 
 bool
 DefineChromeWorkerFunctions(JSContext* aCx, JSObject* aGlobal)
 {
   // Currently ctypes is the only special property given to ChromeWorkers.
-  return DefineCTypesLazyGetter(aCx, aGlobal);
+#ifdef BUILD_CTYPES
+  {
+    jsval ctypes;
+    if (!JS_InitCTypesClass(aCx, aGlobal) ||
+        !JS_GetProperty(aCx, aGlobal, "ctypes", &ctypes)) {
+      return false;
+    }
+
+    static JSCTypesCallbacks callbacks = {
+      UnicodeToNative
+    };
+
+    JS_SetCTypesCallbacks(JSVAL_TO_OBJECT(ctypes), &callbacks);
+  }
+#endif // BUILD_CTYPES
+
+  return true;
 }
 
 END_WORKERS_NAMESPACE
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -17,16 +17,17 @@
 #include "nsIJSContextStack.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISupportsPriority.h"
 #include "nsITimer.h"
 #include "nsPIDOMWindow.h"
 #include "nsLayoutStatics.h"
 
 #include "jsdbgapi.h"
+#include "jsfriendapi.h"
 #include "mozilla/dom/EventTargetBinding.h"
 #include "mozilla/Preferences.h"
 #include "nsContentUtils.h"
 #include "nsDOMJSUtils.h"
 #include <Navigator.h>
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
@@ -367,16 +368,37 @@ ContentSecurityPolicyAllows(JSContext* a
 
   if (!runnable->Dispatch(aCx)) {
     JS_ReportPendingException(aCx);
   }
 
   return false;
 }
 
+void
+CTypesActivityCallback(JSContext* aCx,
+                       js::CTypesActivityType aType)
+{
+  WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
+  worker->AssertIsOnWorkerThread();
+
+  switch (aType) {
+    case js::CTYPES_CALL_BEGIN:
+      worker->BeginCTypesCall();
+      break;
+
+    case js::CTYPES_CALL_END:
+      worker->EndCTypesCall();
+      break;
+
+    default:
+      MOZ_NOT_REACHED("Unknown type flag!");
+  }
+}
+
 JSContext*
 CreateJSContextForWorker(WorkerPrivate* aWorkerPrivate)
 {
   aWorkerPrivate->AssertIsOnWorkerThread();
   NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!");
 
   // The number passed here doesn't matter, we're about to change it in the call
   // to JS_SetGCParameter.
@@ -413,16 +435,18 @@ CreateJSContextForWorker(WorkerPrivate* 
   }
 
   JS_SetContextPrivate(workerCx, aWorkerPrivate);
 
   JS_SetErrorReporter(workerCx, ErrorReporter);
 
   JS_SetOperationCallback(workerCx, OperationCallback);
 
+  js::SetCTypesActivityCallback(runtime, CTypesActivityCallback);
+
   NS_ASSERTION((aWorkerPrivate->GetJSContextOptions() &
                 kRequiredJSContextOptions) == kRequiredJSContextOptions,
                "Somehow we lost our required options!");
   JS_SetOptions(workerCx, aWorkerPrivate->GetJSContextOptions());
 
 #ifdef JS_GC_ZEAL
   {
     uint8_t zeal = aWorkerPrivate->GetGCZeal();
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1385,250 +1385,135 @@ public:
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
     return true;
   }
 };
 
-class CollectRuntimeStatsRunnable : public WorkerControlRunnable
+class WorkerJSRuntimeStats : public JS::RuntimeStats
 {
-  typedef mozilla::Mutex Mutex;
-  typedef mozilla::CondVar CondVar;
-
-  Mutex mMutex;
-  CondVar mCondVar;
-  volatile bool mDone;
-  bool mIsQuick;
-  void* mData;
-  bool* mSucceeded;
+  const nsACString& mRtPath;
 
 public:
-  CollectRuntimeStatsRunnable(WorkerPrivate* aWorkerPrivate, bool aIsQuick,
-                              void* aData, bool* aSucceeded)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
-    mMutex("CollectRuntimeStatsRunnable::mMutex"),
-    mCondVar(mMutex, "CollectRuntimeStatsRunnable::mCondVar"), mDone(false),
-    mIsQuick(aIsQuick), mData(aData), mSucceeded(aSucceeded)
+  WorkerJSRuntimeStats(const nsACString& aRtPath)
+  : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath)
   { }
 
-  bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-  {
-    AssertIsOnMainThread();
-    return true;
-  }
-
-  void
-  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult)
-  {
-    AssertIsOnMainThread();
-  }
-
-  bool
-  DispatchInternal()
+  ~WorkerJSRuntimeStats()
   {
-    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)
-  {
-    JSRuntime *rt = JS_GetRuntime(aCx);
-    if (mIsQuick) {
-      *static_cast<int64_t*>(mData) = JS::GetExplicitNonHeapForRuntime(rt, JsWorkerMallocSizeOf);
-      *mSucceeded = true;
-    } else {
-      *mSucceeded = JS::CollectRuntimeStats(rt, static_cast<JS::RuntimeStats*>(mData), nullptr);
-    }
-
-    {
-      MutexAutoLock lock(mMutex);
-      mDone = true;
-      mCondVar.Notify();
-    }
-
-    return true;
-  }
-};
-
-} /* anonymous namespace */
-
-struct WorkerJSRuntimeStats : public JS::RuntimeStats
-{
-  WorkerJSRuntimeStats(nsACString &aRtPath)
-   : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath) { }
-
-  ~WorkerJSRuntimeStats() {
     for (size_t i = 0; i != compartmentStatsVector.length(); i++) {
       free(compartmentStatsVector[i].extra1);
-      // no need to free |extra2|, because it's a static string
+      // No need to free |extra2| because it's a static string.
     }
   }
 
-  virtual void initExtraCompartmentStats(JSCompartment *c,
-                                         JS::CompartmentStats *cstats) MOZ_OVERRIDE
+  virtual void
+  initExtraCompartmentStats(JSCompartment* aCompartment,
+                            JS::CompartmentStats* aCompartmentStats)
+                            MOZ_OVERRIDE
   {
-    MOZ_ASSERT(!cstats->extra1);
-    MOZ_ASSERT(!cstats->extra2);
-    
-    // ReportJSRuntimeExplicitTreeStats expects that cstats->{extra1,extra2}
-    // are char pointers.
+    MOZ_ASSERT(!aCompartmentStats->extra1);
+    MOZ_ASSERT(!aCompartmentStats->extra2);
+
+    // ReportJSRuntimeExplicitTreeStats expects that
+    // aCompartmentStats->{extra1,extra2} are char pointers.
 
     // This is the |cJSPathPrefix|.  Each worker has exactly two compartments:
     // one for atoms, and one for everything else.
-    nsCString cJSPathPrefix(mRtPath);
-    cJSPathPrefix += js::IsAtomsCompartment(c)
+    nsAutoCString cJSPathPrefix(mRtPath);
+    cJSPathPrefix += js::IsAtomsCompartment(aCompartment)
                    ? NS_LITERAL_CSTRING("compartment(web-worker-atoms)/")
                    : NS_LITERAL_CSTRING("compartment(web-worker)/");
-    cstats->extra1 = strdup(cJSPathPrefix.get());
-
-    // This is the |cDOMPathPrefix|, which should never be used when reporting
-    // with workers (hence the "?!").
-    cstats->extra2 = (void *)"explicit/workers/?!/";
+    aCompartmentStats->extra1 = strdup(cJSPathPrefix.get());
+
+    // This should never be used when reporting with workers (hence the "?!").
+    static const char bogusMemoryReporterPath[] = "explicit/workers/?!/";
+    aCompartmentStats->extra2 = const_cast<char*>(bogusMemoryReporterPath);
   }
-
-private:
-  nsCString mRtPath;
 };
-  
-BEGIN_WORKERS_NAMESPACE
-
-class WorkerMemoryReporter MOZ_FINAL : public nsIMemoryMultiReporter
+
+class MemoryReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
+  friend class WorkerPrivate;
+
   WorkerPrivate* mWorkerPrivate;
-  nsCString mAddressString;
   nsCString mRtPath;
 
 public:
   NS_DECL_ISUPPORTS
 
-  WorkerMemoryReporter(WorkerPrivate* aWorkerPrivate)
+  MemoryReporter(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];
-      uint32_t addressSize =
-        JS_snprintf(address, sizeof(address), "%p", aWorkerPrivate);
-      if (addressSize != uint32_t(-1)) {
-        mAddressString.Assign(address, addressSize);
-      }
-      else {
-        NS_WARNING("JS_snprintf failed!");
-        mAddressString.AssignLiteral("<unknown address>");
-      }
-    }
+    nsAutoCString addressString;
+    addressString.AppendPrintf("0x%p", static_cast<void*>(aWorkerPrivate));
 
     mRtPath = NS_LITERAL_CSTRING("explicit/workers/workers(") +
               escapedDomain + NS_LITERAL_CSTRING(")/worker(") +
-              escapedURL + NS_LITERAL_CSTRING(", ") + mAddressString +
+              escapedURL + NS_LITERAL_CSTRING(", ") + addressString +
               NS_LITERAL_CSTRING(")/");
   }
 
-  nsresult
-  CollectForRuntime(bool aIsQuick, void* aData)
+  ~MemoryReporter()
   {
-    AssertIsOnMainThread();
-
-    if (!mWorkerPrivate) {
-#ifdef DEBUG
-      nsAutoCString message("Unable to report memory for ");
-      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
-      *static_cast<int64_t*>(aData) = 0;
-      return NS_OK;
-    }
-
-    if (!mWorkerPrivate->BlockAndCollectRuntimeStats(aIsQuick, aData)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    return NS_OK;
+    mWorkerPrivate->NoteDeadMemoryReporter();
   }
 
-  NS_IMETHOD GetName(nsACString &aName)
+  NS_IMETHOD
+  GetName(nsACString& aName)
   {
     aName.AssignLiteral("workers");
     return NS_OK;
   }
 
   NS_IMETHOD
   CollectReports(nsIMemoryMultiReporterCallback* aCallback,
                  nsISupports* aClosure)
   {
     AssertIsOnMainThread();
 
     WorkerJSRuntimeStats rtStats(mRtPath);
-    nsresult rv = CollectForRuntime(/* isQuick = */false, &rtStats);
-    if (NS_FAILED(rv)) {
-      return rv;
+    if (!mWorkerPrivate->BlockAndCollectRuntimeStats(/* aIsQuick = */ false,
+                                                     &rtStats)) {
+      return NS_ERROR_FAILURE;
     }
 
     // Always report, even if we're disabled, so that we at least get an entry
     // in about::memory.
     return xpc::ReportJSRuntimeExplicitTreeStats(rtStats, mRtPath,
                                                  aCallback, aClosure);
   }
 
   NS_IMETHOD
-  GetExplicitNonHeap(int64_t *aAmount)
+  GetExplicitNonHeap(int64_t* aAmount)
   {
     AssertIsOnMainThread();
 
-    return CollectForRuntime(/* isQuick = */true, aAmount);
-  }
-
-  void Disable()
-  {
-#ifdef DEBUG
-    // Setting mWorkerPrivate to nullptr is safe only because we've locked the
-    // worker's mutex on the worker's thread, in the caller.  So we check that.
-    //
-    // Also, we may have already disabled the reporter (and thus set
-    // mWorkerPrivate to nullptr) due to the use of CTypes (see
-    // ChromeWorkerScope.cpp).  That's why the NULL check is necessary.
-    if (mWorkerPrivate) {
-        mWorkerPrivate->mMutex.AssertCurrentThreadOwns();
+    if (!mWorkerPrivate->BlockAndCollectRuntimeStats(/* aIsQuick = */ true,
+                                                     aAmount)) {
+      return NS_ERROR_FAILURE;
     }
-#endif
-    mWorkerPrivate = nullptr;
+
+    return NS_OK;
   }
 };
 
-END_WORKERS_NAMESPACE
-
-NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerMemoryReporter, nsIMemoryMultiReporter)
+} /* anonymous namespace */
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(MemoryReporter, nsIMemoryMultiReporter)
 
 #ifdef DEBUG
 void
 mozilla::dom::workers::AssertIsOnMainThread()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 }
 
@@ -1896,16 +1781,17 @@ WorkerPrivateParent<Derived>::WorkerPriv
                                      nsCOMPtr<nsPIDOMWindow>& aWindow,
                                      nsCOMPtr<nsIScriptContext>& aScriptContext,
                                      nsCOMPtr<nsIURI>& aBaseURI,
                                      nsCOMPtr<nsIPrincipal>& aPrincipal,
                                      nsCOMPtr<nsIContentSecurityPolicy>& aCSP,
                                      bool aEvalAllowed)
 : EventTarget(aParent ? aCx : NULL), mMutex("WorkerPrivateParent Mutex"),
   mCondVar(mMutex, "WorkerPrivateParent CondVar"),
+  mMemoryReportCondVar(mMutex, "WorkerPrivateParent Memory Report CondVar"),
   mJSObject(aObject), mParent(aParent), mParentJSContext(aParentJSContext),
   mScriptURL(aScriptURL), mDomain(aDomain), mBusyCount(0),
   mParentStatus(Pending), mJSContextOptions(0), mJSRuntimeHeapSize(0),
   mGCZeal(0), mJSObjectRooted(false), mParentSuspended(false),
   mIsChromeWorker(aIsChromeWorker), mPrincipalIsSystem(false),
   mMainThreadObjectsForgotten(false), mEvalAllowed(aEvalAllowed)
 {
   MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivateParent);
@@ -2467,17 +2353,18 @@ WorkerPrivate::WorkerPrivate(JSContext* 
                              bool aXHRParamsAllowed)
 : WorkerPrivateParent<WorkerPrivate>(aCx, aObject, aParent, aParentJSContext,
                                      aScriptURL, aIsChromeWorker, aDomain,
                                      aWindow, aParentScriptContext, aBaseURI,
                                      aPrincipal, aCSP, aEvalAllowed),
   mJSContext(nullptr), mErrorHandlerRecursionCount(0), mNextTimeoutId(1),
   mStatus(Pending), mSuspended(false), mTimerRunning(false),
   mRunningExpiredTimeouts(false), mCloseHandlerStarted(false),
-  mCloseHandlerFinished(false), mMemoryReporterRunning(false),
+  mCloseHandlerFinished(false), mMemoryReporterAlive(false),
+  mMemoryReporterRunning(false), mBlockedForMemoryReporter(false),
   mXHRParamsAllowed(aXHRParamsAllowed)
 {
   MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivate);
 }
 
 WorkerPrivate::~WorkerPrivate()
 {
   MOZ_COUNT_DTOR(mozilla::dom::workers::WorkerPrivate);
@@ -2724,33 +2611,28 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
     normalGCEventTarget = new WorkerRunnableEventTarget(runnable);
 
     runnable = new GarbageCollectRunnable(this, true, false);
     idleGCEventTarget = new WorkerRunnableEventTarget(runnable);
 
     idleGCEvent = runnable;
   }
 
-  mMemoryReporter = new WorkerMemoryReporter(this);
-
-  if (NS_FAILED(NS_RegisterMemoryMultiReporter(mMemoryReporter))) {
-    NS_WARNING("Failed to register memory reporter!");
-    mMemoryReporter = nullptr;
-  }
+  EnableMemoryReporter();
 
   for (;;) {
     Status currentStatus;
     bool scheduleIdleGC;
 
     WorkerRunnable* event;
     {
       MutexAutoLock lock(mMutex);
 
       while (!mControlQueue.Pop(event) && !mQueue.Pop(event)) {
-        mCondVar.Wait();
+        WaitForWorkerEvents();
       }
 
       bool eventIsNotIdleGCEvent;
       currentStatus = mStatus;
 
       {
         MutexAutoUnlock unlock(mMutex);
 
@@ -2841,27 +2723,18 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
 
       // If we're supposed to die then we should exit the loop.
       if (currentStatus == Killing) {
         // Always make sure the timer is canceled.
         if (NS_FAILED(gcTimer->Cancel())) {
           NS_WARNING("Failed to cancel the GC timer!");
         }
 
-        // 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 = nullptr;
-        }
-
         StopAcceptingEvents();
         return;
       }
     }
   }
 
   NS_NOTREACHED("Shouldn't get here!");
 }
@@ -2885,17 +2758,17 @@ WorkerPrivate::OperationCallback(JSConte
     JS_GC(JS_GetRuntime(aCx));
 
     while ((mayContinue = MayContinueRunning())) {
       MutexAutoLock lock(mMutex);
       if (!mControlQueue.IsEmpty()) {
         break;
       }
 
-      mCondVar.Wait(PR_MillisecondsToInterval(RemainingRunTimeMS()));
+      WaitForWorkerEvents(PR_MillisecondsToInterval(RemainingRunTimeMS()));
     }
   }
 
   if (!mayContinue) {
     // We want only uncatchable exceptions here.
     NS_ASSERTION(!JS_IsExceptionPending(aCx),
                  "Should not have an exception set here!");
     return false;
@@ -2941,83 +2814,242 @@ WorkerPrivate::ScheduleDeletion(bool aWa
 }
 
 bool
 WorkerPrivate::BlockAndCollectRuntimeStats(bool aIsQuick, void* aData)
 {
   AssertIsOnMainThread();
   NS_ASSERTION(aData, "Null data!");
 
+  JSRuntime* rt;
+
   {
     MutexAutoLock lock(mMutex);
+
+    // If the worker is being torn down then we just bail out now.
+    if (!mMemoryReporter) {
+      if (aIsQuick) {
+        *static_cast<int64_t*>(aData) = 0;
+      }
+      return true;
+    }
+
+    // This signals the worker that it should block itself as soon as possible.
     mMemoryReporterRunning = true;
+
+    NS_ASSERTION(mJSContext, "This must never be null!");
+    rt = JS_GetRuntime(mJSContext);
+
+    // If the worker is not already blocked (e.g. waiting for a worker event or
+    // currently in a ctypes call) then we need to trigger the operation
+    // callback to trap the worker.
+    if (!mBlockedForMemoryReporter) {
+      JS_TriggerOperationCallback(rt);
+
+      // Wait until the worker actually blocks.
+      while (!mBlockedForMemoryReporter) {
+        mMemoryReportCondVar.Wait();
+      }
+    }
   }
 
   bool succeeded;
 
-  nsRefPtr<CollectRuntimeStatsRunnable> runnable =
-    new CollectRuntimeStatsRunnable(this, aIsQuick, aData, &succeeded);
-  if (!runnable->Dispatch(nullptr)) {
-    NS_WARNING("Failed to dispatch runnable!");
-    succeeded = false;
+  // Now do the actual report.
+  if (aIsQuick) {
+    *static_cast<int64_t*>(aData) =
+      JS::GetExplicitNonHeapForRuntime(rt, JsWorkerMallocSizeOf);
+    succeeded = true;
+  } else {
+    succeeded =
+      JS::CollectRuntimeStats(rt, static_cast<JS::RuntimeStats*>(aData),
+                              nullptr);
   }
 
-  {
-    MutexAutoLock lock(mMutex);
-    mMemoryReporterRunning = false;
-  }
+  MutexAutoLock lock(mMutex);
+
+  NS_ASSERTION(mMemoryReporterRunning, "This isn't possible!");
+  NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!");
+
+  // Tell the worker that it can now continue its execution.
+  mMemoryReporterRunning = false;
+
+  // The worker may be waiting so we must notify.
+  mMemoryReportCondVar.Notify();
 
   return succeeded;
 }
 
-bool
+void
+WorkerPrivate::NoteDeadMemoryReporter()
+{
+  // This may happen on the worker thread or the main thread.
+  MutexAutoLock lock(mMutex);
+
+  NS_ASSERTION(mMemoryReporterAlive, "Must be alive!");
+
+  // Tell the worker that the memory reporter has died.
+  mMemoryReporterAlive = false;
+
+  // The worker may be waiting so we must notify.
+  mMemoryReportCondVar.Notify();
+}
+
+void
+WorkerPrivate::EnableMemoryReporter()
+{
+  AssertIsOnWorkerThread();
+
+  NS_ASSERTION(!mMemoryReporterAlive, "This isn't possible!");
+
+  // No need to lock here since the main thread can't race until we've
+  // successfully registered the reporter.
+  mMemoryReporterAlive = true;
+  mMemoryReporter = new MemoryReporter(this);
+
+  if (NS_FAILED(NS_RegisterMemoryMultiReporter(mMemoryReporter))) {
+    NS_WARNING("Failed to register memory reporter!");
+    // No need to lock here since a failed registration means our memory
+    // reporter can't start running. Just clean up.
+    mMemoryReporter = nullptr;
+
+    // That should have killed the memory reporter.
+    NS_ASSERTION(!mMemoryReporterAlive, "Must be dead!");
+
+    return;
+  }
+}
+
+void
 WorkerPrivate::DisableMemoryReporter()
 {
   AssertIsOnWorkerThread();
 
-  bool result = true;
-
+  // First swap out our memory reporter so that the main thread stops trying to
+  // signal us when it's reporting memory.
+  nsCOMPtr<nsIMemoryMultiReporter> memoryReporter;
   {
     MutexAutoLock lock(mMutex);
-
-    while (mMemoryReporterRunning) {
-      MutexAutoUnlock unlock(mMutex);
-      result = ProcessAllControlRunnables() && result;
-    }
-
-    if (mMemoryReporter) {
-      mMemoryReporter->Disable();
-    }
+    mMemoryReporter.swap(memoryReporter);
+  }
+
+  // The main thread could immediately try to report memory again here but it
+  // will not actually do anything since we've cleared mMemoryReporter.
+
+  // If we never registered the memory reporter (e.g. some kind of error in
+  // NS_RegisterMemoryMultiReporter) then there's nothing else we need to do
+  // here.
+  if (!memoryReporter) {
+    return;
+  }
+
+  NS_ASSERTION(mMemoryReporterAlive, "Huh?!");
+
+  // Unregister.
+  if (NS_FAILED(NS_UnregisterMemoryMultiReporter(memoryReporter))) {
+    // If this fails then the memory reporter will probably never die and we'll
+    // hang below waiting for it. If this ever happens we need to fix
+    // NS_UnregisterMemoryMultiReporter.
+    MOZ_NOT_REACHED("Failed to unregister memory reporter! Worker is going to "
+                    "hang!");
   }
 
-  return result;
+  // Now drop our reference to the memory reporter. The main thread could be
+  // holding another reference so it may not die immediately.
+  memoryReporter = nullptr;
+
+  MutexAutoLock lock(mMutex);
+
+  // Wait once again to make sure that the main thread is done with the memory
+  // reporter.
+  while (mMemoryReporterAlive) {
+    mMemoryReportCondVar.Wait();
+  }
+}
+
+void
+WorkerPrivate::WaitForWorkerEvents(PRIntervalTime aInterval)
+{
+  AssertIsOnWorkerThread();
+  mMutex.AssertCurrentThreadOwns();
+
+  NS_ASSERTION(!mBlockedForMemoryReporter,
+                "Can't be blocked in more than one place at the same time!");
+
+  // Let the main thread know that the worker is blocked and that memory
+  // reporting may proceed.
+  mBlockedForMemoryReporter = true;
+
+  // The main thread may be waiting so we must notify.
+  mMemoryReportCondVar.Notify();
+
+  // Now wait for an actual worker event.
+  mCondVar.Wait(aInterval);
+
+  // We've gotten some kind of signal but we can't continue until the memory
+  // reporter has finished. Wait again.
+  while (mMemoryReporterRunning) {
+    mMemoryReportCondVar.Wait();
+  }
+
+  NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!");
+
+  // No need to notify here as the main thread isn't watching for this state.
+  mBlockedForMemoryReporter = false;
 }
 
 bool
 WorkerPrivate::ProcessAllControlRunnables()
 {
   AssertIsOnWorkerThread();
 
   bool result = true;
 
   for (;;) {
     WorkerRunnable* event;
     {
       MutexAutoLock lock(mMutex);
+
+      // Block here if the memory reporter is trying to run.
+      if (mMemoryReporterRunning) {
+        NS_ASSERTION(!mBlockedForMemoryReporter,
+                     "Can't be blocked in more than one place at the same "
+                     "time!");
+
+        // Let the main thread know that we've received the block request and
+        // that memory reporting may proceed.
+        mBlockedForMemoryReporter = true;
+
+        // The main thread is almost certainly waiting so we must notify here.
+        mMemoryReportCondVar.Notify();
+
+        // Wait for the memory report to finish.
+        while (mMemoryReporterRunning) {
+          mMemoryReportCondVar.Wait();
+        }
+
+        NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!");
+
+        // No need to notify here as the main thread isn't watching for this
+        // state.
+        mBlockedForMemoryReporter = false;
+      }
+
       if (!mControlQueue.Pop(event)) {
         break;
       }
     }
 
     if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
       result = false;
     }
 
     NS_RELEASE(event);
   }
+
   return result;
 }
 
 bool
 WorkerPrivate::CheckXHRParamsAllowed(nsPIDOMWindow* aWindow)
 {
   AssertIsOnMainThread();
   NS_ASSERTION(aWindow, "Wrong cannot be null");
@@ -3358,17 +3390,17 @@ WorkerPrivate::RunSyncLoop(JSContext* aC
   SyncQueue* syncQueue = mSyncQueues[aSyncLoopKey].get();
 
   for (;;) {
     WorkerRunnable* event;
     {
       MutexAutoLock lock(mMutex);
 
       while (!mControlQueue.Pop(event) && !syncQueue->mQueue.Pop(event)) {
-        mCondVar.Wait();
+        WaitForWorkerEvents();
       }
     }
 
 #ifdef EXTRA_GC
     // Find GC bugs...
     JS_GC(mJSContext);
 #endif
 
@@ -4106,16 +4138,56 @@ WorkerPrivate::GetContentSecurityPolicy(
     NS_ERROR("CSP: Failed to get CSP from principal.");
     return false;
   }
 
   csp.forget(aCSP);
   return true;
 }
 
+void
+WorkerPrivate::BeginCTypesCall()
+{
+  AssertIsOnWorkerThread();
+
+  MutexAutoLock lock(mMutex);
+
+  NS_ASSERTION(!mBlockedForMemoryReporter,
+               "Can't be blocked in more than one place at the same time!");
+
+  // Let the main thread know that the worker is effectively blocked while in
+  // this ctypes call. It isn't technically true (obviously the call could do
+  // non-blocking things), but we're assuming that ctypes can't call back into
+  // JSAPI here and therefore any work the ctypes call does will not alter the
+  // data structures of this JS runtime.
+  mBlockedForMemoryReporter = true;
+
+  // The main thread may be waiting on us so it must be notified.
+  mMemoryReportCondVar.Notify();
+}
+
+void
+WorkerPrivate::EndCTypesCall()
+{
+  AssertIsOnWorkerThread();
+
+  MutexAutoLock lock(mMutex);
+
+  NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!");
+
+  // Don't continue until the memory reporter has finished.
+  while (mMemoryReporterRunning) {
+    mMemoryReportCondVar.Wait();
+  }
+
+  // No need to notify the main thread here as it shouldn't be waiting to see
+  // this state.
+  mBlockedForMemoryReporter = false;
+}
+
 BEGIN_WORKERS_NAMESPACE
 
 // Force instantiation.
 template class WorkerPrivateParent<WorkerPrivate>;
 
 WorkerPrivate*
 GetWorkerPrivateFromContext(JSContext* aCx)
 {
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -27,26 +27,26 @@
 #include "StructuredCloneTags.h"
 
 #include "EventTarget.h"
 #include "Queue.h"
 #include "WorkerFeature.h"
 
 class JSAutoStructuredCloneBuffer;
 class nsIDocument;
+class nsIMemoryMultiReporter;
 class nsIPrincipal;
 class nsIScriptContext;
 class nsIURI;
 class nsPIDOMWindow;
 class nsITimer;
 class nsIXPCScriptNotify;
 
 BEGIN_WORKERS_NAMESPACE
 
-class WorkerMemoryReporter;
 class WorkerPrivate;
 
 class WorkerRunnable : public nsIRunnable
 {
 public:
   enum Target { ParentThread, WorkerThread };
   enum BusyBehavior { ModifyBusyCount, UnchangedBusyCount };
   enum ClearingBehavior { SkipWhenClearing, RunWhenClearing };
@@ -162,16 +162,17 @@ public:
     nsCString mPathname;
     nsCString mSearch;
     nsCString mHash;
   };
 
 protected:
   mozilla::Mutex mMutex;
   mozilla::CondVar mCondVar;
+  mozilla::CondVar mMemoryReportCondVar;
 
 private:
   JSObject* mJSObject;
   WorkerPrivate* mParent;
   JSContext* mParentJSContext;
   nsString mScriptURL;
   nsCString mDomain;
   LocationInfo mLocationInfo;
@@ -513,17 +514,16 @@ public:
   AssertInnerWindowIsCorrect() const
   { }
 #endif
 };
 
 class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
 {
   friend class WorkerPrivateParent<WorkerPrivate>;
-  friend class WorkerMemoryReporter;
   typedef WorkerPrivateParent<WorkerPrivate> ParentType;
 
   struct TimeoutInfo;
 
   typedef Queue<WorkerRunnable*, 50> EventQueue;
   EventQueue mQueue;
   EventQueue mControlQueue;
 
@@ -553,28 +553,30 @@ class WorkerPrivate : public WorkerPriva
   nsRefPtr<WorkerCrossThreadDispatcher> mCrossThreadDispatcher;
 
   // Things touched on worker thread only.
   nsTArray<ParentType*> mChildWorkers;
   nsTArray<WorkerFeature*> mFeatures;
   nsTArray<nsAutoPtr<TimeoutInfo> > mTimeouts;
 
   nsCOMPtr<nsITimer> mTimer;
-  nsRefPtr<WorkerMemoryReporter> mMemoryReporter;
+  nsCOMPtr<nsIMemoryMultiReporter> mMemoryReporter;
 
   mozilla::TimeStamp mKillTime;
   uint32_t mErrorHandlerRecursionCount;
   uint32_t mNextTimeoutId;
   Status mStatus;
   bool mSuspended;
   bool mTimerRunning;
   bool mRunningExpiredTimeouts;
   bool mCloseHandlerStarted;
   bool mCloseHandlerFinished;
+  bool mMemoryReporterAlive;
   bool mMemoryReporterRunning;
+  bool mBlockedForMemoryReporter;
   bool mXHRParamsAllowed;
 
 #ifdef DEBUG
   nsCOMPtr<nsIThread> mThread;
 #endif
 
 public:
   ~WorkerPrivate();
@@ -705,20 +707,20 @@ public:
 
   void
   UpdateJSRuntimeHeapSizeInternal(JSContext* aCx, uint32_t aJSRuntimeHeapSize);
 
   void
   ScheduleDeletion(bool aWasPending);
 
   bool
-  BlockAndCollectRuntimeStats(bool isQuick, void* aData);
+  BlockAndCollectRuntimeStats(bool aIsQuick, void* aData);
 
-  bool
-  DisableMemoryReporter();
+  void
+  NoteDeadMemoryReporter();
 
   bool
   XHRParamsAllowed() const
   {
     return mXHRParamsAllowed;
   }
 
   void
@@ -756,16 +758,24 @@ public:
   void
   AssertIsOnWorkerThread() const
   { }
 #endif
 
   WorkerCrossThreadDispatcher*
   GetCrossThreadDispatcher();
 
+  // This may block!
+  void
+  BeginCTypesCall();
+
+  // This may block!
+  void
+  EndCTypesCall();
+
 private:
   WorkerPrivate(JSContext* aCx, JSObject* aObject, WorkerPrivate* aParent,
                 JSContext* aParentJSContext, const nsAString& aScriptURL,
                 bool aIsChromeWorker, const nsACString& aDomain,
                 nsCOMPtr<nsPIDOMWindow>& aWindow,
                 nsCOMPtr<nsIScriptContext>& aScriptContext,
                 nsCOMPtr<nsIURI>& aBaseURI, nsCOMPtr<nsIPrincipal>& aPrincipal,
                 nsCOMPtr<nsIContentSecurityPolicy>& aCSP, bool aEvalAllowed,
@@ -825,16 +835,25 @@ private:
 
     ClearQueue(&mControlQueue);
     ClearQueue(&mQueue);
   }
 
   bool
   ProcessAllControlRunnables();
 
+  void
+  EnableMemoryReporter();
+
+  void
+  DisableMemoryReporter();
+
+  void
+  WaitForWorkerEvents(PRIntervalTime interval = PR_INTERVAL_NO_TIMEOUT);
+
   static bool
   CheckXHRParamsAllowed(nsPIDOMWindow* aWindow);
 };
 
 WorkerPrivate*
 GetWorkerPrivateFromContext(JSContext* aCx);
 
 enum WorkerStructuredDataType
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -5762,16 +5762,21 @@ FunctionType::Call(JSContext* cx,
   AutoValue returnValue;
   TypeCode typeCode = CType::GetTypeCode(fninfo->mReturnType);
   if (typeCode != TYPE_void_t &&
       !returnValue.SizeToType(cx, fninfo->mReturnType)) {
     JS_ReportAllocationOverflow(cx);
     return false;
   }
 
+  // Let the runtime callback know that we are about to call into C.
+  js::CTypesActivityCallback activityCallback = cx->runtime->ctypesActivityCallback;
+  if (activityCallback)
+    activityCallback(cx, js::CTYPES_CALL_BEGIN);
+
   uintptr_t fn = *reinterpret_cast<uintptr_t*>(CData::GetData(obj));
 
 #if defined(XP_WIN)
   int32_t lastErrorStatus; // The status as defined by |GetLastError|
   int32_t savedLastError = GetLastError();
   SetLastError(0);
 #endif //defined(XP_WIN)
   int errnoStatus;         // The status as defined by |errno|
@@ -5789,16 +5794,19 @@ FunctionType::Call(JSContext* cx,
   errnoStatus = errno;
 #if defined(XP_WIN)
   lastErrorStatus = GetLastError();
   SetLastError(savedLastError);
 #endif // defined(XP_WIN)
 
   errno = savedErrno;
 
+  if (activityCallback)
+    activityCallback(cx, js::CTYPES_CALL_END);
+
   // Store the error value for later consultation with |ctypes.getStatus|
   JSObject *objCTypes = CType::GetGlobalCTypes(cx, typeObj);
   if (!objCTypes)
     return false;
 
   JS_SetReservedSlot(objCTypes, SLOT_ERRNO, INT_TO_JSVAL(errnoStatus));
 #if defined(XP_WIN)
   JS_SetReservedSlot(objCTypes, SLOT_LASTERROR, INT_TO_JSVAL(lastErrorStatus));
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -874,16 +874,17 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     inOOMReport(0),
     jitHardening(false),
     ionTop(NULL),
     ionJSContext(NULL),
     ionStackLimit(0),
     ionActivation(NULL),
     ionPcScriptCache(NULL),
     ionReturnOverride_(MagicValue(JS_ARG_POISON)),
+    ctypesActivityCallback(NULL),
     useHelperThreads_(useHelperThreads)
 {
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&debuggerList);
     JS_INIT_CLIST(&onNewGlobalObjectWatchers);
 
     PodZero(&debugHooks);
     PodZero(&atomState);
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1017,16 +1017,18 @@ struct JSRuntime : js::RuntimeFriendFiel
     }
 
     // This points to the most recent Ion activation running on the thread.
     js::ion::IonActivation  *ionActivation;
 
     // Cache for ion::GetPcScript().
     js::ion::PcScriptCache *ionPcScriptCache;
 
+    js::CTypesActivityCallback  ctypesActivityCallback;
+
   private:
     // In certain cases, we want to optimize certain opcodes to typed instructions,
     // to avoid carrying an extra register to feed into an unbox. Unfortunately,
     // that's not always possible. For example, a GetPropertyCacheT could return a
     // typed double, but if it takes its out-of-line path, it could return an
     // object, and trigger invalidation. The invalidation bailout will consider the
     // return value to be a double, and create a garbage Value.
     //
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -997,9 +997,15 @@ GetListBaseHandlerFamily()
 }
 
 uint32_t
 GetListBaseExpandoSlot()
 {
     return gListBaseExpandoSlot;
 }
 
+JS_FRIEND_API(void)
+js::SetCTypesActivityCallback(JSRuntime *rt, CTypesActivityCallback cb)
+{
+    rt->ctypesActivityCallback = cb;
+}
+
 } // namespace js
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1540,11 +1540,26 @@ IdToValue(jsid id)
 }
 
 static JS_ALWAYS_INLINE jsval
 IdToJsval(jsid id)
 {
     return IdToValue(id);
 }
 
+enum CTypesActivityType {
+    CTYPES_CALL_BEGIN,
+    CTYPES_CALL_END
+};
+
+typedef void
+(* CTypesActivityCallback)(JSContext *cx, CTypesActivityType type);
+
+/*
+ * Sets a callback that is run whenever js-ctypes is about to be used when
+ * calling into C.
+ */
+JS_FRIEND_API(void)
+SetCTypesActivityCallback(JSRuntime *rt, CTypesActivityCallback cb);
+
 } /* namespace js */
 
-#endif /* jsfriendapi_h___ */
+#endif /* jsfriendapi_h___ */
\ No newline at end of file