Bug 1049091 - Console API in workers should not block the script waiting for the main-thread, r=ehsan
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 03 Apr 2015 20:37:52 +0100
changeset 237574 3aea48a423be9e726ccb22586ff6047439dd699a
parent 237573 8dce32099811ce6baa7bf141ba9b4c90a46ce635
child 237575 809e54954e6f9308ece0ba769a862d90158bc64f
push id28540
push userphilringnalda@gmail.com
push dateSat, 04 Apr 2015 17:42:19 +0000
treeherdermozilla-central@035959eef3f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs1049091
milestone40.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 1049091 - Console API in workers should not block the script waiting for the main-thread, r=ehsan
dom/base/Console.cpp
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -18,16 +18,17 @@
 #include "nsJSUtils.h"
 #include "nsPerformance.h"
 #include "ScriptSettings.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "xpcprivate.h"
 #include "nsContentUtils.h"
 #include "nsDocShell.h"
+#include "nsProxyRelease.h"
 
 #include "nsIConsoleAPIStorage.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsILoadContext.h"
 #include "nsIProgrammingLanguage.h"
 #include "nsIServiceManager.h"
 #include "nsISupportsPrimitives.h"
@@ -147,16 +148,18 @@ static const JSStructuredCloneCallbacks 
   ConsoleStructuredCloneCallbacksRead,
   ConsoleStructuredCloneCallbacksWrite,
   ConsoleStructuredCloneCallbacksError
 };
 
 class ConsoleCallData final
 {
 public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConsoleCallData)
+
   ConsoleCallData()
     : mMethodName(Console::MethodLog)
     , mPrivate(false)
     , mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
     , mIDType(eUnknown)
     , mOuterIDNumber(0)
     , mInnerIDNumber(0)
   { }
@@ -233,16 +236,20 @@ public:
   // lazily as possible.  Therefore, we have the following behavior:
   // 1)  mTopStackFrame is initialized whenever we have any JS on the stack
   // 2)  mReifiedStack is initialized if we're created in a worker.
   // 3)  mStack is set (possibly to null if there is no JS on the stack) if
   //     we're created on main thread.
   Maybe<ConsoleStackEntry> mTopStackFrame;
   Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
   nsCOMPtr<nsIStackFrame> mStack;
+
+private:
+  ~ConsoleCallData()
+  { }
 };
 
 // This class is used to clear any exception at the end of this method.
 class ClearException
 {
 public:
   explicit ClearException(JSContext* aCx)
     : mCx(aCx)
@@ -279,26 +286,23 @@ public:
     mWorkerPrivate->AssertIsOnWorkerThread();
 
     JSContext* cx = mWorkerPrivate->GetJSContext();
 
     if (!PreDispatch(cx)) {
       return false;
     }
 
-    AutoSyncLoopHolder syncLoop(mWorkerPrivate);
-    mSyncLoopTarget = syncLoop.EventTarget();
-
     if (NS_FAILED(NS_DispatchToMainThread(this))) {
       JS_ReportError(cx,
                      "Failed to dispatch to main thread for the Console API!");
       return false;
     }
 
-    return syncLoop.Run();
+    return true;
   }
 
 private:
   NS_IMETHOD Run()
   {
     AssertIsOnMainThread();
 
     // Walk up to our containing page
@@ -309,24 +313,16 @@ private:
 
     nsPIDOMWindow* window = wp->GetWindow();
     if (!window) {
       RunWindowless();
     } else {
       RunWithWindow(window);
     }
 
-    nsRefPtr<MainThreadStopSyncLoopRunnable> response =
-      new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
-                                         mSyncLoopTarget.forget(),
-                                         true);
-    if (!response->Dispatch(nullptr)) {
-      NS_WARNING("Failed to dispatch response!");
-    }
-
     return NS_OK;
   }
 
   void
   RunWithWindow(nsPIDOMWindow* aWindow)
   {
     AutoJSAPI jsapi;
     MOZ_ASSERT(aWindow);
@@ -383,35 +379,55 @@ protected:
   RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
              nsPIDOMWindow* aInnerWindow) = 0;
 
   WorkerPrivate* mWorkerPrivate;
 
   // Raw pointer because this method is async and this object is kept alive by
   // the caller.
   Console* mConsole;
-
-private:
-  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
 };
 
 // This runnable appends a CallData object into the Console queue running on
 // the main-thread.
 class ConsoleCallDataRunnable final : public ConsoleRunnable
 {
 public:
   ConsoleCallDataRunnable(Console* aConsole,
                           ConsoleCallData* aCallData)
     : ConsoleRunnable(aConsole)
     , mCallData(aCallData)
   { }
 
 private:
   ~ConsoleCallDataRunnable()
-  { }
+  {
+    class ReleaseCallData final : public nsRunnable
+    {
+    public:
+      explicit ReleaseCallData(nsRefPtr<ConsoleCallData>& aCallData)
+      {
+        mCallData.swap(aCallData);
+      }
+
+      NS_IMETHOD Run() override
+      {
+        mCallData = nullptr;
+        return NS_OK;
+      }
+
+    private:
+      nsRefPtr<ConsoleCallData> mCallData;
+    };
+
+    nsRefPtr<ReleaseCallData> runnable = new ReleaseCallData(mCallData);
+    if(NS_FAILED(NS_DispatchToMainThread(runnable))) {
+      NS_WARNING("Failed to dispatch a ReleaseCallData runnable. Leaking.");
+    }
+  }
 
   bool
   PreDispatch(JSContext* aCx) override
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
 
     ClearException ce(aCx);
     JSAutoCompartment ac(aCx, mCallData->mGlobal);
@@ -509,17 +525,17 @@ private:
     }
 
     MOZ_ASSERT(mCallData->mArguments.Length() == length);
 
     mCallData->mGlobal = JS::CurrentGlobalOrNull(aCx);
     mConsole->ProcessCallData(mCallData);
   }
 
-  ConsoleCallData* mCallData;
+  nsRefPtr<ConsoleCallData> mCallData;
 
   JSAutoStructuredCloneBuffer mArguments;
   ConsoleStructuredCloneData mData;
 };
 
 // This runnable calls ProfileMethod() on the console on the main-thread.
 class ConsoleProfileRunnable final : public ConsoleRunnable
 {
@@ -561,16 +577,17 @@ private:
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
 
     if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mData)) {
       return false;
     }
 
+    mArguments.Clear();
     return true;
   }
 
   void
   RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
              nsPIDOMWindow* aInnerWindow) override
   {
     ClearException ce(aCx);
@@ -954,17 +971,17 @@ public:
 };
 
 // Queue a call to a console method. See the CALL_DELAY constant.
 void
 Console::Method(JSContext* aCx, MethodName aMethodName,
                 const nsAString& aMethodString,
                 const Sequence<JS::Value>& aData)
 {
-  nsAutoPtr<ConsoleCallData> callData(new ConsoleCallData());
+  nsRefPtr<ConsoleCallData> callData(new ConsoleCallData());
 
   ClearException ce(aCx);
 
   callData->Initialize(aCx, aMethodName, aMethodString, aData);
 
   if (mWindow) {
     nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
     if (!webNav) {
@@ -1073,18 +1090,16 @@ Console::Method(JSContext* aCx, MethodNa
   }
 
   if (NS_IsMainThread()) {
     callData->SetIDs(mOuterID, mInnerID);
     ProcessCallData(callData);
     return;
   }
 
-  // Note: we can pass the reference of callData because this runnable calls
-  // ProcessCallData() synchronously.
   nsRefPtr<ConsoleCallDataRunnable> runnable =
     new ConsoleCallDataRunnable(this, callData);
   runnable->Dispatch();
 }
 
 // We store information to lazily compute the stack in the reserved slots of
 // LazyStackGetter.  The first slot always stores a JS object: it's either the
 // JS wrapper of the nsIStackFrame or the actual reified stack representation.