Bug 698882 - mozilla::net::PollableEvent r=dragana r=mayhemer, a=ritu
☠☠ backed out by c678a3c4169e ☠ ☠
authorPatrick McManus <mcmanus@ducksong.com>
Thu, 11 Feb 2016 15:00:22 -0500
changeset 324013 efa0fb87773ed6e6850dcaf794d53651429decca
parent 324012 8210de2a2b4fdb1701d3dadf65184bda51d6fbe7
child 324014 dc8dd10e7a95e36e73b1a3c0a9fd6599047ff6a7
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana, mayhemer, ritu
bugs698882
milestone47.0a2
Bug 698882 - mozilla::net::PollableEvent r=dragana r=mayhemer, a=ritu
netwerk/base/PollableEvent.cpp
netwerk/base/PollableEvent.h
netwerk/base/moz.build
netwerk/base/nsSocketTransportService2.cpp
netwerk/base/nsSocketTransportService2.h
netwerk/standalone/moz.build
new file mode 100644
--- /dev/null
+++ b/netwerk/base/PollableEvent.cpp
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "nsSocketTransportService2.h"
+#include "PollableEvent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "prerror.h"
+#include "prio.h"
+#include "private/pprio.h"
+#include "prnetdb.h"
+
+#ifdef XP_WIN
+#include "ShutdownLayer.h"
+#else
+#include <fcntl.h>
+#define USEPIPE 1
+#endif
+
+namespace mozilla {
+namespace net {
+
+#ifndef USEPIPE
+static PRDescIdentity sPollableEventLayerIdentity;
+static PRIOMethods    sPollableEventLayerMethods;
+static PRIOMethods   *sPollableEventLayerMethodsPtr = nullptr;
+
+static void LazyInitSocket()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  if (sPollableEventLayerMethodsPtr) {
+    return;
+  }
+  sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer");
+  sPollableEventLayerMethods = *PR_GetDefaultIOMethods();
+  sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods;
+}
+
+static bool NewTCPSocketPair(PRFileDesc *fd[])
+{
+  // this is a replacement for PR_NewTCPSocketPair that manually
+  // sets the recv buffer to 64K. A windows bug (1248358)
+  // can result in using an incompatible rwin and window
+  // scale option on localhost pipes if not set before connect.
+
+  PRFileDesc *listener = nullptr;
+  PRFileDesc *writer = nullptr;
+  PRFileDesc *reader = nullptr;
+  PRSocketOptionData recvBufferOpt;
+  recvBufferOpt.option = PR_SockOpt_RecvBufferSize;
+  recvBufferOpt.value.recv_buffer_size = 65535;
+
+  PRSocketOptionData nodelayOpt;
+  nodelayOpt.option = PR_SockOpt_NoDelay;
+  nodelayOpt.value.no_delay = true;
+
+  PRSocketOptionData noblockOpt;
+  noblockOpt.option = PR_SockOpt_Nonblocking;
+  noblockOpt.value.non_blocking = true;
+
+  listener = PR_OpenTCPSocket(PR_AF_INET);
+  if (!listener) {
+    goto failed;
+  }
+  PR_SetSocketOption(listener, &recvBufferOpt);
+  PR_SetSocketOption(listener, &nodelayOpt);
+  PR_SetSocketOption(listener, &noblockOpt);
+  PRNetAddr listenAddr;
+  memset(&listenAddr, 0, sizeof(listenAddr));
+  if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) ||
+      (PR_Bind(listener, &listenAddr) == PR_FAILURE) ||
+      (PR_GetSockName(listener, &listenAddr) == PR_FAILURE) || // learn the dynamic port
+      (PR_Listen(listener, 5) == PR_FAILURE)) {
+    goto failed;
+  }
+
+  writer = PR_OpenTCPSocket(PR_AF_INET);
+  if (!writer) {
+    goto failed;
+  }
+  PR_SetSocketOption(writer, &recvBufferOpt);
+  PR_SetSocketOption(writer, &nodelayOpt);
+  PR_SetSocketOption(writer, &noblockOpt);
+  PRNetAddr writerAddr;
+  if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port), &writerAddr) == PR_FAILURE) {
+    goto failed;
+  }
+
+  if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) {
+    if ((PR_GetError() != PR_IN_PROGRESS_ERROR) ||
+        (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) {
+      goto failed;
+    }
+  }
+
+  reader = PR_Accept(listener, &listenAddr, PR_INTERVAL_NO_TIMEOUT);
+  if (!reader) {
+    goto failed;
+  }
+  PR_SetSocketOption(reader, &recvBufferOpt);
+  PR_SetSocketOption(reader, &nodelayOpt);
+  PR_SetSocketOption(reader, &noblockOpt);
+  PR_Close(listener);
+
+  fd[0] = reader;
+  fd[1] = writer;
+  return true;
+
+failed:
+  if (listener) {
+    PR_Close(listener);
+  }
+  if (reader) {
+    PR_Close(reader);
+  }
+  if (writer) {
+    PR_Close(writer);
+  }
+  return false;
+}
+
+#endif
+
+PollableEvent::PollableEvent()
+  : mWriteFD(nullptr)
+  , mReadFD(nullptr)
+  , mSignaled(false)
+{
+  MOZ_COUNT_CTOR(PollableEvent);
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  // create pair of prfiledesc that can be used as a poll()ble
+  // signal. on windows use a localhost socket pair, and on
+  // unix use a pipe.
+#ifdef USEPIPE
+  SOCKET_LOG(("PollableEvent() using pipe\n"));
+  if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) {
+    // make the pipe non blocking. NSPR asserts at
+    // trying to use SockOpt here
+    PROsfd fd = PR_FileDesc2NativeHandle(mReadFD);
+    int flags = fcntl(fd, F_GETFL, 0);
+    (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+    fd = PR_FileDesc2NativeHandle(mWriteFD);
+    flags = fcntl(fd, F_GETFL, 0);
+    (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+  } else {
+    mReadFD = nullptr;
+    mWriteFD = nullptr;
+    SOCKET_LOG(("PollableEvent() pipe failed\n"));
+  }
+#else
+  SOCKET_LOG(("PollableEvent() using socket pair\n"));
+  PRFileDesc *fd[2];
+  LazyInitSocket();
+  if (NewTCPSocketPair(fd)) {
+    mReadFD = fd[0];
+    mWriteFD = fd[1];
+
+    // compatibility with LSPs such as McAfee that assume a NSPR
+    // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a nop.
+    PRFileDesc *topLayer =
+      PR_CreateIOLayerStub(sPollableEventLayerIdentity,
+                           sPollableEventLayerMethodsPtr);
+    if (topLayer) {
+      if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) {
+        topLayer->dtor(topLayer);
+      } else {
+        SOCKET_LOG(("PollableEvent() nspr layer ok\n"));
+        mReadFD = topLayer;
+      }
+    }
+
+  } else {
+    SOCKET_LOG(("PollableEvent() socketpair failed\n"));
+  }
+#endif
+
+  if (mReadFD && mWriteFD) {
+    // prime the system to deal with races invovled in [dc]tor cycle
+    SOCKET_LOG(("PollableEvent() ctor ok\n"));
+    mSignaled = true;
+    PR_Write(mWriteFD, "I", 1);
+  }
+}
+
+PollableEvent::~PollableEvent()
+{
+  MOZ_COUNT_DTOR(PollableEvent);
+  if (mWriteFD) {
+#if defined(XP_WIN)
+    AttachShutdownLayer(mWriteFD);
+#endif
+    PR_Close(mWriteFD);
+  }
+  if (mReadFD) {
+#if defined(XP_WIN)
+    AttachShutdownLayer(mReadFD);
+#endif
+    PR_Close(mReadFD);
+  }
+}
+
+// we do not record signals on the socket thread
+// because the socket thread can reliably look at its
+// own runnable queue before selecting a poll time
+// this is the "service the network without blocking" comment in
+// nsSocketTransportService2.cpp
+bool
+PollableEvent::Signal()
+{
+  SOCKET_LOG(("PollableEvent::Signal\n"));
+
+  if (!mWriteFD) {
+    SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n"));
+    return false;
+  }
+  if (PR_GetCurrentThread() == gSocketThread) {
+    SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n"));
+    return true;
+  }
+  if (mSignaled) {
+    return true;
+  }
+  mSignaled = true;
+  int32_t status = PR_Write(mWriteFD, "M", 1);
+  SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status));
+  if (status != 1) {
+    NS_WARNING("PollableEvent::Signal Failed\n");
+    SOCKET_LOG(("PollableEvent::Signal Failed\n"));
+  }
+  return (status == 1);
+}
+
+bool
+PollableEvent::Clear()
+{
+  // necessary because of the "dont signal on socket thread" optimization
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  SOCKET_LOG(("PollableEvent::Clear\n"));
+  mSignaled = false;
+  if (!mReadFD) {
+    SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n"));
+    return false;
+  }
+  char buf[2048];
+  int32_t status = PR_Read(mReadFD, buf, 2048);
+  SOCKET_LOG(("PollableEvent::Signal PR_Read %d\n", status));
+
+  if (status == 1) {
+    return true;
+  }
+  if (status == 0) {
+    SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
+    return false;
+  }
+  if (status > 1) {
+    MOZ_ASSERT(false);
+    SOCKET_LOG(("PollableEvent::Clear Unexpected events\n"));
+    Clear();
+    return true;
+  }
+  PRErrorCode code = PR_GetError();
+  if (code == PR_WOULD_BLOCK_ERROR) {
+    return true;
+  }
+  SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
+  return false;
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/base/PollableEvent.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef PollableEvent_h__
+#define PollableEvent_h__
+
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+// class must be called locked
+class PollableEvent
+{
+public:
+  PollableEvent();
+  ~PollableEvent();
+
+  // Signal/Clear return false only if they fail
+  bool Signal();
+  bool Clear();
+  bool Valid() { return mWriteFD && mReadFD; }
+
+  PRFileDesc *PollableFD() { return mReadFD; }
+
+private:
+  PRFileDesc *mWriteFD;
+  PRFileDesc *mReadFD;
+  bool        mSignaled;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -244,16 +244,17 @@ UNIFIED_SOURCES += [
     'nsSyncStreamListener.cpp',
     'nsTemporaryFileInputStream.cpp',
     'nsTransportUtils.cpp',
     'nsUDPSocket.cpp',
     'nsUnicharStreamLoader.cpp',
     'nsURLHelper.cpp',
     'nsURLParsers.cpp',
     'OfflineObserver.cpp',
+    'PollableEvent.cpp',
     'Predictor.cpp',
     'ProxyAutoConfig.cpp',
     'RedirectChannelRegistrar.cpp',
     'SchedulingContextService.cpp',
     'SimpleBuffer.cpp',
     'StreamingProtocolService.cpp',
     'Tickler.cpp',
     'TLSServerSocket.cpp',
--- a/netwerk/base/nsSocketTransportService2.cpp
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -82,23 +82,23 @@ DebugMutexAutoLock::~DebugMutexAutoLock(
   SOCKET_LOG(("Released lock on thread %p", PR_GetCurrentThread()));
 }
 
 //-----------------------------------------------------------------------------
 // ctor/dtor (called on the main/UI thread by the service manager)
 
 nsSocketTransportService::nsSocketTransportService()
     : mThread(nullptr)
-    , mThreadEvent(nullptr)
     , mAutodialEnabled(false)
     , mLock("nsSocketTransportService::mLock")
     , mInitialized(false)
     , mShuttingDown(false)
     , mOffline(false)
     , mGoingOffline(false)
+    , mRawThread(nullptr)
     , mActiveListSize(SOCKET_LIMIT_MIN)
     , mIdleListSize(SOCKET_LIMIT_MIN)
     , mActiveCount(0)
     , mIdleCount(0)
     , mSentBytesCount(0)
     , mReceivedBytesCount(0)
     , mEventQueueLock("nsSocketTransportService::mEventQueueLock")
     , mPendingSocketQ(mEventQueueLock)
@@ -126,19 +126,16 @@ nsSocketTransportService::nsSocketTransp
     NS_ASSERTION(!gSocketTransportService, "must not instantiate twice");
     gSocketTransportService = this;
 }
 
 nsSocketTransportService::~nsSocketTransportService()
 {
     NS_ASSERTION(NS_IsMainThread(), "wrong thread");
     NS_ASSERTION(!mInitialized, "not shutdown properly");
-    
-    if (mThreadEvent)
-        PR_DestroyPollableEvent(mThreadEvent);
 
     free(mActiveList);
     free(mIdleList);
     free(mPollList);
     gSocketTransportService = nullptr;
 }
 
 //-----------------------------------------------------------------------------
@@ -430,43 +427,46 @@ nsSocketTransportService::PollTimeout()
         SOCKET_LOG(("poll timeout: none\n"));
         return NS_SOCKET_POLL_TIMEOUT;
     }
     SOCKET_LOG(("poll timeout: %lu\n", minR));
     return PR_SecondsToInterval(minR);
 }
 
 int32_t
-nsSocketTransportService::Poll(bool wait, uint32_t *interval,
+nsSocketTransportService::Poll(uint32_t *interval,
                                TimeDuration *pollDuration)
 {
     PRPollDesc *pollList;
     uint32_t pollCount;
     PRIntervalTime pollTimeout;
     *pollDuration = 0;
 
+    // If there are pending events for this thread then
+    // DoPollIteration() should service the network without blocking.
+    bool pendingEvents = false;
+    mRawThread->HasPendingEvents(&pendingEvents);
+
     if (mPollList[0].fd) {
         mPollList[0].out_flags = 0;
         pollList = mPollList;
         pollCount = mActiveCount + 1;
-        pollTimeout = PollTimeout();
+        pollTimeout = pendingEvents ? PR_INTERVAL_NO_WAIT : PollTimeout();
     }
     else {
         // no pollable event, so busy wait...
         pollCount = mActiveCount;
         if (pollCount)
             pollList = &mPollList[1];
         else
             pollList = nullptr;
-        pollTimeout = PR_MillisecondsToInterval(25);
+        pollTimeout =
+            pendingEvents ? PR_INTERVAL_NO_WAIT : PR_MillisecondsToInterval(25);
     }
 
-    if (!wait)
-        pollTimeout = PR_INTERVAL_NO_WAIT;
-
     PRIntervalTime ts = PR_IntervalNow();
 
     TimeStamp pollStart;
     if (mTelemetryEnabledPref) {
         pollStart = TimeStamp::NowLoRes();
     }
 
     SOCKET_LOG(("    timeout = %i milliseconds\n",
@@ -508,34 +508,16 @@ nsSocketTransportService::Init()
     }
 
     if (mInitialized)
         return NS_OK;
 
     if (mShuttingDown)
         return NS_ERROR_UNEXPECTED;
 
-    if (!mThreadEvent) {
-        mThreadEvent = PR_NewPollableEvent();
-        //
-        // NOTE: per bug 190000, this failure could be caused by Zone-Alarm
-        // or similar software.
-        //
-        // NOTE: per bug 191739, this failure could also be caused by lack
-        // of a loopback device on Windows and OS/2 platforms (NSPR creates
-        // a loopback socket pair on these platforms to implement a pollable
-        // event object).  if we can't create a pollable event, then we'll
-        // have to "busy wait" to implement the socket event queue :-(
-        //
-        if (!mThreadEvent) {
-            NS_WARNING("running socket transport thread without a pollable event");
-            SOCKET_LOG(("running socket transport thread without a pollable event"));
-        }
-    }
-
     nsCOMPtr<nsIThread> thread;
     nsresult rv = NS_NewThread(getter_AddRefs(thread), this);
     if (NS_FAILED(rv)) return rv;
     
     {
         DebugMutexAutoLock lock(mLock);
         // Install our mThread, protecting against concurrent readers
         thread.swap(mThread);
@@ -579,19 +561,19 @@ nsSocketTransportService::Shutdown()
         return NS_ERROR_UNEXPECTED;
 
     {
         DebugMutexAutoLock lock(mLock);
 
         // signal the socket thread to shutdown
         mShuttingDown = true;
 
-        if (mThreadEvent)
-            PR_SetPollableEvent(mThreadEvent);
-        // else wait for Poll timeout
+        if (mPollableEvent) {
+            mPollableEvent->Signal();
+        }
     }
 
     // join with thread
     mThread->Shutdown();
     {
         DebugMutexAutoLock lock(mLock);
         // Drop our reference to mThread and make sure that any concurrent
         // readers are excluded
@@ -630,18 +612,19 @@ nsSocketTransportService::SetOffline(boo
     if (!mOffline && offline) {
         // signal the socket thread to go offline, so it will detach sockets
         mGoingOffline = true;
         mOffline = true;
     }
     else if (mOffline && !offline) {
         mOffline = false;
     }
-    if (mThreadEvent)
-        PR_SetPollableEvent(mThreadEvent);
+    if (mPollableEvent) {
+        mPollableEvent->Signal();
+    }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSocketTransportService::GetKeepaliveIdleTime(int32_t *aKeepaliveIdleTimeS)
 {
     MOZ_ASSERT(aKeepaliveIdleTimeS);
@@ -744,19 +727,29 @@ nsSocketTransportService::SetAutodialEna
 {
     mAutodialEnabled = value;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSocketTransportService::OnDispatchedEvent(nsIThreadInternal *thread)
 {
+    if (PR_GetCurrentThread() == gSocketThread) {
+        // this check is redundant to one done inside ::Signal(), but
+        // we can do it here and skip obtaining the lock - given that
+        // this is a relatively common occurance its worth the
+        // redundant code
+        SOCKET_LOG(("OnDispatchedEvent Same Thread Skip Signal\n"));
+        return NS_OK;
+    }
+
     DebugMutexAutoLock lock(mLock);
-    if (mThreadEvent)
-        PR_SetPollableEvent(mThreadEvent);
+    if (mPollableEvent) {
+        mPollableEvent->Signal();
+    }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSocketTransportService::OnProcessNextEvent(nsIThreadInternal *thread,
                                              bool mayWait)
 {
     return NS_OK;
@@ -791,25 +784,44 @@ nsSocketTransportService::Run()
 #endif
 
     SOCKET_LOG(("STS thread init\n"));
 
     psm::InitializeSSLServerCertVerificationThreads();
 
     gSocketThread = PR_GetCurrentThread();
 
-    // add thread event to poll list (mThreadEvent may be nullptr)
-    mPollList[0].fd = mThreadEvent;
-    mPollList[0].in_flags = PR_POLL_READ;
-    mPollList[0].out_flags = 0;
+    {
+        DebugMutexAutoLock lock(mLock);
+        mPollableEvent.reset(new PollableEvent());
+        //
+        // NOTE: per bug 190000, this failure could be caused by Zone-Alarm
+        // or similar software.
+        //
+        // NOTE: per bug 191739, this failure could also be caused by lack
+        // of a loopback device on Windows and OS/2 platforms (it creates
+        // a loopback socket pair on these platforms to implement a pollable
+        // event object).  if we can't create a pollable event, then we'll
+        // have to "busy wait" to implement the socket event queue :-(
+        //
+        if (!mPollableEvent->Valid()) {
+            mPollableEvent = nullptr;
+            NS_WARNING("running socket transport thread without a pollable event");
+            SOCKET_LOG(("running socket transport thread without a pollable event"));
+        }
 
-    nsIThread *thread = NS_GetCurrentThread();
+        mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr;
+        mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
+        mPollList[0].out_flags = 0;
+    }
+
+    mRawThread = NS_GetCurrentThread();
 
     // hook ourselves up to observe event processing for this thread
-    nsCOMPtr<nsIThreadInternal> threadInt = do_QueryInterface(thread);
+    nsCOMPtr<nsIThreadInternal> threadInt = do_QueryInterface(mRawThread);
     threadInt->SetObserver(this);
 
     // make sure the pseudo random number generator is seeded on this thread
     srand(static_cast<unsigned>(PR_Now()));
 
     // For the calculation of the duration of the last cycle (i.e. the last for-loop
     // iteration before shutdown).
     TimeStamp startOfCycleForLastCycleCalc;
@@ -828,50 +840,43 @@ nsSocketTransportService::Run()
 
     // If there is too many pending events queued, we will run some poll()
     // between them and the following variable is cumulative time spent
     // blocking in poll().
     TimeDuration pollDuration;
 
     for (;;) {
         bool pendingEvents = false;
-        thread->HasPendingEvents(&pendingEvents);
 
         numberOfPendingEvents = 0;
         numberOfPendingEventsLastCycle = 0;
         if (mTelemetryEnabledPref) {
             startOfCycleForLastCycleCalc = TimeStamp::NowLoRes();
             startOfNextIteration = TimeStamp::NowLoRes();
         }
         pollDuration = 0;
 
         do {
             if (mTelemetryEnabledPref) {
                 pollCycleStart = TimeStamp::NowLoRes();
             }
 
-            // If there are pending events for this thread then
-            // DoPollIteration() should service the network without blocking.
-            DoPollIteration(!pendingEvents, &singlePollDuration);
+            DoPollIteration(&singlePollDuration);
 
             if (mTelemetryEnabledPref && !pollCycleStart.IsNull()) {
                 Telemetry::Accumulate(Telemetry::STS_POLL_BLOCK_TIME,
                                       singlePollDuration.ToMilliseconds());
                 Telemetry::AccumulateTimeDelta(
                     Telemetry::STS_POLL_CYCLE,
                     pollCycleStart + singlePollDuration,
                     TimeStamp::NowLoRes());
                 pollDuration += singlePollDuration;
             }
 
-            // If nothing was pending before the poll, it might be now
-            if (!pendingEvents) {
-                thread->HasPendingEvents(&pendingEvents);
-            }
-
+            mRawThread->HasPendingEvents(&pendingEvents);
             if (pendingEvents) {
                 if (!mServingPendingQueue) {
                     nsresult rv = Dispatch(NS_NewRunnableMethod(this,
                         &nsSocketTransportService::MarkTheLastElementOfPendingQueue),
                         nsIEventTarget::DISPATCH_NORMAL);
                     if (NS_FAILED(rv)) {
                         NS_WARNING("Could not dispatch a new event on the "
                                    "socket thread.");
@@ -885,20 +890,20 @@ nsSocketTransportService::Run()
                         // be served in the next iteration. If no even
                         // arrives, startOfNextIteration will be reset at the
                         // beginning of each for-loop.
                         startOfNextIteration = TimeStamp::NowLoRes();
                     }
                 }
                 TimeStamp eventQueueStart = TimeStamp::NowLoRes();
                 do {
-                    NS_ProcessNextEvent(thread);
+                    NS_ProcessNextEvent(mRawThread);
                     numberOfPendingEvents++;
                     pendingEvents = false;
-                    thread->HasPendingEvents(&pendingEvents);
+                    mRawThread->HasPendingEvents(&pendingEvents);
                 } while (pendingEvents && mServingPendingQueue &&
                          ((TimeStamp::NowLoRes() -
                            eventQueueStart).ToMilliseconds() <
                           mMaxTimePerPollIter));
 
                 if (mTelemetryEnabledPref && !mServingPendingQueue &&
                     !startOfIteration.IsNull()) {
                     Telemetry::AccumulateTimeDelta(
@@ -946,17 +951,17 @@ nsSocketTransportService::Run()
 
     SOCKET_LOG(("STS shutting down thread\n"));
 
     // detach all sockets, including locals
     Reset(false);
 
     // Final pass over the event queue. This makes sure that events posted by
     // socket detach handlers get processed.
-    NS_ProcessPendingEvents(thread);
+    NS_ProcessPendingEvents(mRawThread);
 
     gSocketThread = nullptr;
 
     psm::StopSSLServerCertVerificationThreads();
 
     SOCKET_LOG(("STS thread exit\n"));
     return NS_OK;
 }
@@ -985,22 +990,21 @@ nsSocketTransportService::Reset(bool aGu
         DetachSocketWithGuard(aGuardLocals, mActiveList, i);
     }
     for (i = mIdleCount - 1; i >= 0; --i) {
         DetachSocketWithGuard(aGuardLocals, mIdleList, i);
     }
 }
 
 nsresult
-nsSocketTransportService::DoPollIteration(bool wait, TimeDuration *pollDuration)
+nsSocketTransportService::DoPollIteration(TimeDuration *pollDuration)
 {
-    SOCKET_LOG(("STS poll iter [%d]\n", wait));
+    SOCKET_LOG(("STS poll iter\n"));
 
     int32_t i, count;
-
     //
     // poll loop
     //
     // walk active list backwards to see if any sockets should actually be
     // idle, then walk the idle list backwards to see if any idle sockets
     // should become active.  take care to check only idle sockets that
     // were idle to begin with ;-)
     //
@@ -1049,17 +1053,17 @@ nsSocketTransportService::DoPollIteratio
 #endif
 
     // Measures seconds spent while blocked on PR_Poll
     uint32_t pollInterval = 0;
     int32_t n = 0;
     *pollDuration = 0;
     if (!gIOService->IsNetTearingDown()) {
         // Let's not do polling during shutdown.
-        n = Poll(wait, &pollInterval, pollDuration);
+        n = Poll(&pollInterval, pollDuration);
     }
 
     if (n < 0) {
         SOCKET_LOG(("  PR_Poll error [%d] os error [%d]\n", PR_GetError(),
                     PR_GetOSError()));
     }
     else {
         //
@@ -1105,39 +1109,38 @@ nsSocketTransportService::DoPollIteratio
         // check for "dead" sockets and remove them (need to do this in
         // reverse order obviously).
         //
         for (i=mActiveCount-1; i>=0; --i) {
             if (NS_FAILED(mActiveList[i].mHandler->mCondition))
                 DetachSocket(mActiveList, &mActiveList[i]);
         }
 
-        if (n != 0 && mPollList[0].out_flags == PR_POLL_READ) {
-            // acknowledge pollable event (wait should not block)
-            if (PR_WaitForPollableEvent(mThreadEvent) != PR_SUCCESS) {
+        if (n != 0 && (mPollList[0].out_flags & (PR_POLL_READ | PR_POLL_EXCEPT))) {
+            DebugMutexAutoLock lock(mLock);
+
+            // acknowledge pollable event (should not block)
+            if (mPollableEvent &&
+                ((mPollList[0].out_flags & PR_POLL_EXCEPT) ||
+                 !mPollableEvent->Clear())) {
                 // On Windows, the TCP loopback connection in the
                 // pollable event may become broken when a laptop
                 // switches between wired and wireless networks or
                 // wakes up from hibernation.  We try to create a
                 // new pollable event.  If that fails, we fall back
                 // on "busy wait".
-                {
-                    DebugMutexAutoLock lock(mLock);
-                    PR_DestroyPollableEvent(mThreadEvent);
-                    mThreadEvent = PR_NewPollableEvent();
+                NS_WARNING("Trying to repair mPollableEvent");
+                mPollableEvent.reset(new PollableEvent());
+                if (!mPollableEvent->Valid()) {
+                    mPollableEvent = nullptr;
                 }
-                if (!mThreadEvent) {
-                    NS_WARNING("running socket transport thread without "
-                               "a pollable event");
-                    SOCKET_LOG(("running socket transport thread without "
-                         "a pollable event"));
-                }
-                mPollList[0].fd = mThreadEvent;
-                // mPollList[0].in_flags was already set to PR_POLL_READ
-                // in Run().
+                SOCKET_LOG(("running socket transport thread without "
+                            "a pollable event now valid=%d", mPollableEvent->Valid()));
+                mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr;
+                mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
                 mPollList[0].out_flags = 0;
             }
         }
     }
 
     return NS_OK;
 }
 
--- a/netwerk/base/nsSocketTransportService2.h
+++ b/netwerk/base/nsSocketTransportService2.h
@@ -14,16 +14,18 @@
 #include "prinrval.h"
 #include "mozilla/Logging.h"
 #include "prinit.h"
 #include "nsIObserver.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/net/DashboardTypes.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "PollableEvent.h"
 
 class nsASocketHandler;
 struct PRPollDesc;
 
 //-----------------------------------------------------------------------------
 
 //
 // set NSPR_LOG_MODULES=nsSocketTransport:5
@@ -119,24 +121,17 @@ protected:
 
 private:
 
     //-------------------------------------------------------------------------
     // misc (any thread)
     //-------------------------------------------------------------------------
 
     nsCOMPtr<nsIThread> mThread;    // protected by mLock
-    PRFileDesc *mThreadEvent;
-                            // protected by mLock.  mThreadEvent may change
-                            // if the old pollable event is broken.  only
-                            // the socket thread may change mThreadEvent;
-                            // it needs to lock mLock only when it changes
-                            // mThreadEvent.  other threads don't change
-                            // mThreadEvent; they need to lock mLock
-                            // whenever they access mThreadEvent.
+    mozilla::UniquePtr<mozilla::net::PollableEvent> mPollableEvent;
     bool        mAutodialEnabled;
                             // pref to control autodial code
 
     // Returns mThread, protecting the get-and-addref with mLock
     already_AddRefed<nsIThread> GetThreadSafely();
 
     //-------------------------------------------------------------------------
     // initialization and shutdown (any thread)
@@ -168,16 +163,17 @@ private:
     {
         PRFileDesc       *mFD;
         nsASocketHandler *mHandler;
         uint16_t          mElapsedTime;  // time elapsed w/o activity
     };
 
     SocketContext *mActiveList;                   /* mListSize entries */
     SocketContext *mIdleList;                     /* mListSize entries */
+    nsIThread     *mRawThread;
 
     uint32_t mActiveListSize;
     uint32_t mIdleListSize;
     uint32_t mActiveCount;
     uint32_t mIdleCount;
 
     nsresult DetachSocket(SocketContext *, SocketContext *);
     nsresult AddToIdleList(SocketContext *);
@@ -192,28 +188,26 @@ private:
     void   InitMaxCount();
 
     // Total bytes number transfered through all the sockets except active ones
     uint64_t mSentBytesCount;
     uint64_t mReceivedBytesCount;
     //-------------------------------------------------------------------------
     // poll list (socket thread only)
     //
-    // first element of the poll list is mThreadEvent (or null if the pollable
+    // first element of the poll list is mPollableEvent (or null if the pollable
     // event cannot be created).
     //-------------------------------------------------------------------------
 
     PRPollDesc *mPollList;                        /* mListSize + 1 entries */
 
     PRIntervalTime PollTimeout();            // computes ideal poll timeout
-    nsresult       DoPollIteration(bool wait,
-                                   mozilla::TimeDuration *pollDuration);
+    nsresult       DoPollIteration(mozilla::TimeDuration *pollDuration);
                                              // perfoms a single poll iteration
-    int32_t        Poll(bool wait,
-                        uint32_t *interval,
+    int32_t        Poll(uint32_t *interval,
                         mozilla::TimeDuration *pollDuration);
                                              // calls PR_Poll.  the out param
                                              // interval indicates the poll
                                              // duration in seconds.
                                              // pollDuration is used only for
                                              // telemetry
 
     //-------------------------------------------------------------------------
--- a/netwerk/standalone/moz.build
+++ b/netwerk/standalone/moz.build
@@ -7,16 +7,17 @@
 if CONFIG['OS_TARGET'] != 'WINNT' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
     Library('necko_standalone')
 
 src_list = [
     'nsNetModuleStandalone.cpp',
 ]
 
 netwerk_base_src = [
+    'PollableEvent.cpp',
     'nsDNSPrefetch.cpp',
     'nsNetAddr.cpp',
     'nsSocketTransportService2.cpp',
     'nsURLHelper.cpp',
 ]
 src_list += [
     '/netwerk/base/%s' % s for s in netwerk_base_src
 ]