Bug 1281874 P1 Hold the worker alive while performing web crypto async work. r=khuey
authorBen Kelly <ben@wanderview.com>
Fri, 01 Jul 2016 06:49:45 -0700
changeset 303421 00f81b0e199303e9eeef69c6f5dbb25713d925b7
parent 303420 6bf1de063f92f982cc1e1ef075cadb9eb17ab9af
child 303422 28f16fa4bc48177695fa7b44c8031dd2bacbd526
push id30388
push usercbook@mozilla.com
push dateSat, 02 Jul 2016 09:15:23 +0000
treeherdermozilla-central@39dffbba7642 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs1281874
milestone50.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 1281874 P1 Hold the worker alive while performing web crypto async work. r=khuey
dom/crypto/WebCryptoTask.cpp
dom/crypto/WebCryptoTask.h
dom/crypto/moz.build
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -4,26 +4,29 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "pk11pub.h"
 #include "cryptohi.h"
 #include "secerr.h"
 #include "ScopedNSSTypes.h"
 #include "nsNSSComponent.h"
+#include "nsProxyRelease.h"
 
 #include "jsapi.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/dom/CryptoBuffer.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mozilla/dom/KeyAlgorithmProxy.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/dom/WebCryptoCommon.h"
 #include "mozilla/dom/WebCryptoTask.h"
 #include "mozilla/dom/WebCryptoThreadPool.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/workers/bindings/WorkerHolder.h"
 
 // Template taken from security/nss/lib/util/templates.c
 // This (or SGN_EncodeDigestInfo) would ideally be exported
 // by NSS and until that happens we have to keep our own copy.
 const SEC_ASN1Template SGN_DigestInfoTemplate[] = {
     { SEC_ASN1_SEQUENCE,
       0, NULL, sizeof(SGNDigestInfo) },
     { SEC_ASN1_INLINE,
@@ -32,16 +35,21 @@ const SEC_ASN1Template SGN_DigestInfoTem
     { SEC_ASN1_OCTET_STRING,
       offsetof(SGNDigestInfo,digest) },
     { 0, }
 };
 
 namespace mozilla {
 namespace dom {
 
+using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
+using mozilla::dom::workers::Status;
+using mozilla::dom::workers::WorkerHolder;
+using mozilla::dom::workers::WorkerPrivate;
+
 // Pre-defined identifiers for telemetry histograms
 
 enum TelemetryMethod {
   TM_ENCRYPT      = 0,
   TM_DECRYPT      = 1,
   TM_SIGN         = 2,
   TM_VERIFY       = 3,
   TM_DIGEST       = 4,
@@ -128,16 +136,56 @@ public:
   {
     JS_ClearPendingException(mCx);
   }
 
 private:
   JSContext* mCx;
 };
 
+class WebCryptoTask::InternalWorkerHolder final : public WorkerHolder
+{
+  InternalWorkerHolder()
+  { }
+
+  ~InternalWorkerHolder()
+  {
+    NS_ASSERT_OWNINGTHREAD(InternalWorkerHolder);
+    // Nothing to do here since the parent destructor releases the
+    // worker automatically.
+  }
+
+public:
+  static already_AddRefed<InternalWorkerHolder>
+  Create()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(workerPrivate);
+    RefPtr<InternalWorkerHolder> ref = new InternalWorkerHolder();
+    if (NS_WARN_IF(!ref->HoldWorker(workerPrivate))) {
+      return nullptr;
+    }
+    return ref.forget();
+  }
+
+  virtual bool
+  Notify(Status aStatus) override
+  {
+    NS_ASSERT_OWNINGTHREAD(InternalWorkerHolder);
+    // Do nothing here.  Since WebCryptoTask dispatches back to
+    // the worker thread using nsThread::Dispatch() instead of
+    // WorkerRunnable it will always be able to execute its
+    // runnables.
+    return true;
+  }
+
+  NS_INLINE_DECL_REFCOUNTING(WebCryptoTask::InternalWorkerHolder)
+};
+
 template<class OOS>
 static nsresult
 GetAlgorithmName(JSContext* aCx, const OOS& aAlgorithm, nsString& aName)
 {
   ClearException ce(aCx);
 
   if (aAlgorithm.IsString()) {
     // If string, then treat as algorithm name
@@ -330,18 +378,34 @@ WebCryptoTask::DispatchWithPromise(Promi
 
   // Skip NSS if we're already done, or launch a CryptoTask
   if (mEarlyComplete) {
     CallCallback(mEarlyRv);
     Skip();
     return;
   }
 
-  // Store calling thread and dispatch to thread pool.
+  // Store calling thread
   mOriginalThread = NS_GetCurrentThread();
+
+  // If we are running on a worker thread we must hold the worker
+  // alive while we work on the thread pool.  Otherwise the worker
+  // private may get torn down before we dispatch back to complete
+  // the transaction.
+  if (!NS_IsMainThread()) {
+    mWorkerHolder = InternalWorkerHolder::Create();
+    // If we can't register a holder then the worker is already
+    // shutting down.  Don't start new work.
+    if (!mWorkerHolder) {
+      mEarlyRv = NS_BINDING_ABORTED;
+    }
+  }
+  MAYBE_EARLY_FAIL(mEarlyRv);
+
+  // dispatch to thread pool
   mEarlyRv = WebCryptoThreadPool::Dispatch(this);
   MAYBE_EARLY_FAIL(mEarlyRv)
 }
 
 NS_IMETHODIMP
 WebCryptoTask::Run()
 {
   // Run heavy crypto operations on the thread pool, off the original thread.
@@ -363,30 +427,43 @@ WebCryptoTask::Run()
 
   // Release NSS resources now, before calling CallCallback, so that
   // WebCryptoTasks have consistent behavior regardless of whether NSS is shut
   // down between CalculateResult being called and CallCallback being called.
   virtualDestroyNSSReference();
 
   CallCallback(mRv);
 
+  // Stop holding the worker thread alive now that the async work has
+  // been completed.
+  mWorkerHolder = nullptr;
+
+  return NS_OK;
+}
+
+nsresult
+WebCryptoTask::Cancel()
+{
+  MOZ_ASSERT(IsOnOriginalThread());
+  FailWithError(NS_BINDING_ABORTED);
   return NS_OK;
 }
 
 void
 WebCryptoTask::FailWithError(nsresult aRv)
 {
   MOZ_ASSERT(IsOnOriginalThread());
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_RESOLVED, false);
 
   // Blindly convert nsresult to DOMException
   // Individual tasks must ensure they pass the right values
   mResultPromise->MaybeReject(aRv);
   // Manually release mResultPromise while we're on the main thread
   mResultPromise = nullptr;
+  mWorkerHolder = nullptr;
   Cleanup();
 }
 
 nsresult
 WebCryptoTask::CalculateResult()
 {
   MOZ_ASSERT(!IsOnOriginalThread());
 
@@ -3626,10 +3703,33 @@ WebCryptoTask::CreateUnwrapKeyTask(nsIGl
     return new UnwrapKeyTask<RsaOaepTask>(aCx, aWrappedKey,
                                       aUnwrappingKey, aUnwrapAlgorithm,
                                       importTask);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
+WebCryptoTask::WebCryptoTask()
+  : mEarlyRv(NS_OK)
+  , mEarlyComplete(false)
+  , mOriginalThread(nullptr)
+  , mReleasedNSSResources(false)
+  , mRv(NS_ERROR_NOT_INITIALIZED)
+{
+}
+
+WebCryptoTask::~WebCryptoTask()
+{
+  MOZ_ASSERT(mReleasedNSSResources);
+
+  nsNSSShutDownPreventionLock lock;
+  if (!isAlreadyShutDown()) {
+    shutdown(calledFromObject);
+  }
+
+  if (mWorkerHolder) {
+    NS_ProxyRelease(mOriginalThread, mWorkerHolder.forget());
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/crypto/WebCryptoTask.h
+++ b/dom/crypto/WebCryptoTask.h
@@ -164,33 +164,18 @@ public:
                           bool aExtractable,
                           const Sequence<nsString>& aKeyUsages);
 
 protected:
   RefPtr<Promise> mResultPromise;
   nsresult mEarlyRv;
   bool mEarlyComplete;
 
-  WebCryptoTask()
-    : mEarlyRv(NS_OK)
-    , mEarlyComplete(false)
-    , mOriginalThread(nullptr)
-    , mReleasedNSSResources(false)
-    , mRv(NS_ERROR_NOT_INITIALIZED)
-  {}
-
-  virtual ~WebCryptoTask()
-  {
-    MOZ_ASSERT(mReleasedNSSResources);
-
-    nsNSSShutDownPreventionLock lock;
-    if (!isAlreadyShutDown()) {
-      shutdown(calledFromObject);
-    }
-  }
+  WebCryptoTask();
+  virtual ~WebCryptoTask();
 
   bool IsOnOriginalThread() {
     return !mOriginalThread || NS_GetCurrentThread() == mOriginalThread;
   }
 
   // For things that need to happen on the main thread
   // either before or after CalculateResult
   virtual nsresult BeforeCrypto() { return NS_OK; }
@@ -206,29 +191,33 @@ protected:
   virtual void ReleaseNSSResources() {}
 
   virtual nsresult CalculateResult() final;
 
   virtual void CallCallback(nsresult rv) final;
 
 private:
   NS_IMETHOD Run() override final;
+  nsresult Cancel() override final;
 
   virtual void
   virtualDestroyNSSReference() override final
   {
     MOZ_ASSERT(IsOnOriginalThread());
 
     if (!mReleasedNSSResources) {
       mReleasedNSSResources = true;
       ReleaseNSSResources();
     }
   }
 
+  class InternalWorkerHolder;
+
   nsCOMPtr<nsIThread> mOriginalThread;
+  RefPtr<InternalWorkerHolder> mWorkerHolder;
   bool mReleasedNSSResources;
   nsresult mRv;
 };
 
 // XXX This class is declared here (unlike others) to enable reuse by WebRTC.
 class GenerateAsymmetricKeyTask : public WebCryptoTask
 {
 public:
--- a/dom/crypto/moz.build
+++ b/dom/crypto/moz.build
@@ -21,14 +21,15 @@ UNIFIED_SOURCES += [
     'WebCryptoThreadPool.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
+    '/dom/workers',
     '/security/manager/ssl',
     '/security/pkix/include',
     '/xpcom/build',
 ]
 
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']