Bug 1124880 - Call PR_Close of UDP sockets on new threads. r=mcmanus
authorHonza Bambas <honzab.moz@firemni.cz>
Fri, 27 Mar 2015 09:24:00 -0400
changeset 266687 88b311e87678e6224ddf2bd1b1031df56db6ed66
parent 266686 222c2f9e3bc9cb74bafc07651471d6b200eb43f2
child 266688 44495ed6c6188f209b86943f544e6d3b6bd604da
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus
bugs1124880
milestone39.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 1124880 - Call PR_Close of UDP sockets on new threads. r=mcmanus
netwerk/base/nsUDPSocket.cpp
toolkit/components/telemetry/Histograms.json
--- a/netwerk/base/nsUDPSocket.cpp
+++ b/netwerk/base/nsUDPSocket.cpp
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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 "mozilla/Attributes.h"
 #include "mozilla/Endian.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Telemetry.h"
 
 #include "nsSocketTransport2.h"
 #include "nsUDPSocket.h"
 #include "nsProxyRelease.h"
 #include "nsAutoPtr.h"
 #include "nsError.h"
 #include "nsNetCID.h"
 #include "prnetdb.h"
@@ -81,16 +82,201 @@ public:
   }
 
 private:
   nsRefPtr<nsUDPSocket> mSocket;
   PRSocketOptionData    mOpt;
 };
 
 //-----------------------------------------------------------------------------
+// nsUDPSocketCloseThread
+//-----------------------------------------------------------------------------
+
+// A helper class carrying call to PR_Close on nsUDPSocket's FD to a separate
+// thread.  This may be a workaround for shutdown blocks that are caused by
+// serial calls to close on UDP sockets.
+// It starts an NSPR thread for each socket and also adds itself as an observer
+// to "xpcom-shutdown-threads" notification where we join the thread (if not
+// already done). During worktime of the thread the class is also
+// self-referenced, since observer service might throw the reference away
+// sooner than the thread is actually done.
+class nsUDPSocketCloseThread : public nsIObserver
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  static bool Close(PRFileDesc *aFd);
+
+private:
+  explicit nsUDPSocketCloseThread(PRFileDesc *aFd);
+  virtual ~nsUDPSocketCloseThread() { }
+
+  bool Begin();
+  void ThreadFunc();
+  void AddObserver();
+  void JoinAndRemove();
+
+  static void ThreadFunc(void *aClosure)
+    { static_cast<nsUDPSocketCloseThread*>(aClosure)->ThreadFunc(); }
+
+  // Socket to close.
+  PRFileDesc *mFd;
+  PRThread *mThread;
+
+  // Self reference, added before we create the thread, dropped
+  // after the thread is done (from the thread).  No races, since
+  // mSelf is assigned bofore the thread func is executed and
+  // released only on the thread func.
+  nsRefPtr<nsUDPSocketCloseThread> mSelf;
+
+  // Telemetry probes.
+  TimeStamp mBeforeClose;
+  TimeStamp mAfterClose;
+
+  // Active threads (roughly) counter, modified only on the main thread
+  // and used only for telemetry reports.
+  static uint32_t sActiveThreadsCount;
+
+  // Switches to true on "xpcom-shutdown-threads" notification and since
+  // then it makes the code fallback to a direct call to PR_Close().
+  static bool sPastShutdown;
+};
+
+uint32_t nsUDPSocketCloseThread::sActiveThreadsCount = 0;
+bool nsUDPSocketCloseThread::sPastShutdown = false;
+
+NS_IMPL_ISUPPORTS(nsUDPSocketCloseThread, nsIObserver);
+
+bool
+nsUDPSocketCloseThread::Close(PRFileDesc *aFd)
+{
+  if (sPastShutdown) {
+    return false;
+  }
+
+  nsRefPtr<nsUDPSocketCloseThread> t = new nsUDPSocketCloseThread(aFd);
+  return t->Begin();
+}
+
+nsUDPSocketCloseThread::nsUDPSocketCloseThread(PRFileDesc *aFd)
+  : mFd(aFd)
+  , mThread(nullptr)
+{
+}
+
+bool
+nsUDPSocketCloseThread::Begin()
+{
+  // Observer service must be worked with on the main thread only.
+  // This method is called usually on the socket thread.
+  // Register us before the thread starts.  It may happen the thread is
+  // done and posts removal event sooner then we would post this event
+  // after the thread creation.
+  nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(
+    this, &nsUDPSocketCloseThread::AddObserver);
+  if (event) {
+    NS_DispatchToMainThread(event);
+  }
+
+  // Keep us self-referenced during lifetime of the thread.
+  // Released after the thread is done.
+  mSelf = this;
+  mThread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this,
+                            PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+                            PR_JOINABLE_THREAD, 4 * 4096);
+  if (!mThread) {
+    // This doesn't join since there is no thread, just removes
+    // this class as an observer.
+    JoinAndRemove();
+    mSelf = nullptr;
+    return false;
+  }
+
+  return true;
+}
+
+void
+nsUDPSocketCloseThread::AddObserver()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ++sActiveThreadsCount;
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs) {
+    obs->AddObserver(this, "xpcom-shutdown-threads", false);
+  }
+}
+
+void
+nsUDPSocketCloseThread::JoinAndRemove()
+{
+  // Posted from the particular (this) UDP close socket when it's done
+  // or from "xpcom-shutdown-threads" notification.
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mThread) {
+    PR_JoinThread(mThread);
+    mThread = nullptr;
+
+    Telemetry::Accumulate(Telemetry::UDP_SOCKET_PARALLEL_CLOSE_COUNT, sActiveThreadsCount);
+    Telemetry::AccumulateTimeDelta(Telemetry::UDP_SOCKET_CLOSE_TIME, mBeforeClose, mAfterClose);
+
+    MOZ_ASSERT(sActiveThreadsCount > 0);
+    --sActiveThreadsCount;
+  }
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, "xpcom-shutdown-threads");
+  }
+}
+
+NS_IMETHODIMP
+nsUDPSocketCloseThread::Observe(nsISupports *aSubject,
+                                const char *aTopic,
+                                const char16_t *aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
+    sPastShutdown = true;
+    JoinAndRemove();
+    return NS_OK;
+  }
+
+  MOZ_CRASH("Unexpected observer topic");
+  return NS_OK;
+}
+
+void
+nsUDPSocketCloseThread::ThreadFunc()
+{
+  PR_SetCurrentThreadName("UDP socket close");
+
+  mBeforeClose = TimeStamp::Now();
+
+  PR_Close(mFd);
+  mFd = nullptr;
+
+  mAfterClose = TimeStamp::Now();
+
+  // Join and remove the observer on the main thread.
+  nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(
+    this, &nsUDPSocketCloseThread::JoinAndRemove);
+  if (event) {
+    NS_DispatchToMainThread(event);
+  }
+
+  // Thread's done, release the self-reference.
+  mSelf = nullptr;
+}
+
+//-----------------------------------------------------------------------------
 // nsUDPOutputStream impl
 //-----------------------------------------------------------------------------
 NS_IMPL_ISUPPORTS(nsUDPOutputStream, nsIOutputStream)
 
 nsUDPOutputStream::nsUDPOutputStream(nsUDPSocket* aSocket,
                                      PRFileDesc* aFD,
                                      PRNetAddr& aPrClientAddr)
   : mSocket(aSocket)
@@ -270,17 +456,19 @@ nsUDPSocket::nsUDPSocket()
 
   mSts = gSocketTransportService;
   MOZ_COUNT_CTOR(nsUDPSocket);
 }
 
 nsUDPSocket::~nsUDPSocket()
 {
   if (mFD) {
-    PR_Close(mFD);
+    if (!nsUDPSocketCloseThread::Close(mFD)) {
+      PR_Close(mFD);
+    }
     mFD = nullptr;
   }
 
   MOZ_COUNT_DTOR(nsUDPSocket);
 }
 
 void
 nsUDPSocket::AddOutputBytes(uint64_t aBytes)
@@ -512,17 +700,19 @@ nsUDPSocket::OnSocketDetached(PRFileDesc
 {
   // force a failure condition if none set; maybe the STS is shutting down :-/
   if (NS_SUCCEEDED(mCondition))
     mCondition = NS_ERROR_ABORT;
 
   if (mFD)
   {
     NS_ASSERTION(mFD == fd, "wrong file descriptor");
-    PR_Close(mFD);
+    if (!nsUDPSocketCloseThread::Close(mFD)) {
+      PR_Close(mFD);
+    }
     mFD = nullptr;
   }
 
   if (mListener)
   {
     // need to atomically clear mListener.  see our Close() method.
     nsCOMPtr<nsIUDPSocketListener> listener;
     {
@@ -650,16 +840,18 @@ nsUDPSocket::Close()
   {
     MutexAutoLock lock(mLock);
     // we want to proxy the close operation to the socket thread if a listener
     // has been set.  otherwise, we should just close the socket here...
     if (!mListener)
     {
       if (mFD)
       {
+        // Here we want to go directly with closing the socket since some tests
+        // expects this happen synchronously.
         PR_Close(mFD);
         mFD = nullptr;
       }
       return NS_OK;
     }
   }
   return PostEvent(this, &nsUDPSocket::OnMsgClose);
 }
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -7708,16 +7708,30 @@
     "description": "Count slow script notices"
   },
   "PLUGIN_HANG_NOTICE_COUNT": {
     "alert_emails": ["perf-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "description": "Count plugin hang notices in e10s"
   },
+  "UDP_SOCKET_PARALLEL_CLOSE_COUNT": {
+    "expires_in_version": "41",
+    "kind": "linear",
+    "high": "20",
+    "n_buckets": 19,
+    "description": "Number of concurrent UDP socket closing threads"
+  },
+  "UDP_SOCKET_CLOSE_TIME": {
+    "expires_in_version": "45",
+    "kind": "exponential",
+    "high": "60000",
+    "n_buckets": 30,
+    "description": "Time PR_Close of a UDP socket taken (ms)"
+  },
   "SERVICE_WORKER_SPAWN_ATTEMPTS": {
     "expires_in_version": "50",
     "kind": "count",
     "description": "Count attempts to spawn a ServiceWorker for a domain"
   },
   "SERVICE_WORKER_WAS_SPAWNED": {
     "expires_in_version": "50",
     "kind": "count",