Bug 939318 - Websockets act on network interface changes. r=mcmanus
☠☠ backed out by 11e4f1678eab ☠ ☠
authorDaniel Stenberg <daniel@haxx.se>
Sun, 24 Aug 2014 23:21:00 -0400
changeset 202355 4dcb889cf913c5c42c4646fa1d924ba499a703ae
parent 202354 2fe8c65c26c26eaeeba5c06cad279979d65c1174
child 202356 ecc0368d9283018519be5f05fd37159cb7122bd0
push id48402
push userryanvm@gmail.com
push dateFri, 29 Aug 2014 13:35:39 +0000
treeherdermozilla-inbound@de5cefa8e52e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus
bugs939318
milestone34.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 939318 - Websockets act on network interface changes. r=mcmanus If there's no outstanding PING, send a new to make sure the connection is still alive and fine.
netwerk/protocol/websocket/BaseWebSocketChannel.cpp
netwerk/protocol/websocket/BaseWebSocketChannel.h
netwerk/protocol/websocket/WebSocketChannel.cpp
netwerk/protocol/websocket/WebSocketChannel.h
--- a/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
@@ -19,16 +19,17 @@ PRLogModuleInfo *webSocketLog = nullptr;
 namespace mozilla {
 namespace net {
 
 BaseWebSocketChannel::BaseWebSocketChannel()
   : mEncrypted(0)
   , mWasOpened(0)
   , mClientSetPingInterval(0)
   , mClientSetPingTimeout(0)
+  , mPingForced(0)
   , mPingInterval(0)
   , mPingResponseTimeout(10000)
 {
 #if defined(PR_LOGGING)
   if (!webSocketLog)
     webSocketLog = PR_NewLogModule("nsWebSocket");
 #endif
 }
--- a/netwerk/protocol/websocket/BaseWebSocketChannel.h
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.h
@@ -64,16 +64,17 @@ class BaseWebSocketChannel : public nsIW
   nsCString                       mOrigin;
 
   nsCString                       mNegotiatedExtensions;
 
   uint32_t                        mEncrypted                 : 1;
   uint32_t                        mWasOpened                 : 1;
   uint32_t                        mClientSetPingInterval     : 1;
   uint32_t                        mClientSetPingTimeout      : 1;
+  uint32_t                        mPingForced                : 1;
 
   uint32_t                        mPingInterval;         /* milliseconds */
   uint32_t                        mPingResponseTimeout;  /* milliseconds */
 };
 
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -30,16 +30,18 @@
 #include "nsIDashboardEventNotifier.h"
 #include "nsIEventTarget.h"
 #include "nsIHttpChannel.h"
 #include "nsILoadGroup.h"
 #include "nsIProtocolHandler.h"
 #include "nsIRandomGenerator.h"
 #include "nsISocketTransport.h"
 #include "nsThreadUtils.h"
+#include "nsINetworkLinkService.h"
+#include "nsIObserverService.h"
 
 #include "nsAutoPtr.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
 #include "nsCRT.h"
 #include "nsThreadUtils.h"
 #include "nsError.h"
 #include "nsStringStream.h"
@@ -80,17 +82,18 @@ NS_IMPL_ISUPPORTS(WebSocketChannel,
                   nsIProtocolHandler,
                   nsIInputStreamCallback,
                   nsIOutputStreamCallback,
                   nsITimerCallback,
                   nsIDNSListener,
                   nsIProtocolProxyCallback,
                   nsIInterfaceRequestor,
                   nsIChannelEventSink,
-                  nsIThreadRetargetableRequest)
+                  nsIThreadRetargetableRequest,
+                  nsIObserver)
 
 // We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire.
 #define SEC_WEBSOCKET_VERSION "13"
 
 /*
  * About SSL unsigned certificates
  *
  * wss will not work to a host using an unsigned certificate unless there
@@ -1041,16 +1044,26 @@ WebSocketChannel::WebSocketChannel() :
   mFramePtr = mBuffer = static_cast<uint8_t *>(moz_xmalloc(mBufferSize));
 
   nsresult rv;
   mConnectionLogService = do_GetService("@mozilla.org/network/dashboard;1",&rv);
   if (NS_FAILED(rv))
     LOG(("Failed to initiate dashboard service."));
 
   mSerial = sSerialSeed++;
+
+  // Register for prefs change notifications
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (observerService) {
+    observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+  } else {
+    NS_WARNING("failed to get observer service");
+  }
+
 }
 
 WebSocketChannel::~WebSocketChannel()
 {
   LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this));
 
   if (mWasOpened) {
     MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called");
@@ -1099,16 +1112,65 @@ WebSocketChannel::~WebSocketChannel()
 
   if (mLoadGroup) {
     nsILoadGroup *forgettableGroup;
     mLoadGroup.forget(&forgettableGroup);
     NS_ProxyRelease(mainThread, forgettableGroup, false);
   }
 }
 
+NS_IMETHODIMP
+WebSocketChannel::Observe(nsISupports *subject,
+                          const char *topic,
+                          const char16_t *data)
+{
+  LOG(("WebSocketChannel::Observe [topic=\"%s\"]\n", topic));
+
+  if (strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0) {
+    nsCString converted = NS_ConvertUTF16toUTF8(data);
+    const char *state = converted.get();
+
+    if (strcmp(state, NS_NETWORK_LINK_DATA_CHANGED) == 0) {
+      LOG(("WebSocket: received network CHANGED event"));
+      if (mPingOutstanding) {
+        // If there's an outstanding ping that's expected to get a pong back
+        // we let that do its thing.
+        LOG(("WebSocket: pong already pending"));
+      } else if (!mSocketThread) {
+        // there has not been an asyncopen yet on the object and then we need
+        // no ping.
+        LOG(("WebSocket: early object, no ping needed"));
+      } else {
+        LOG(("nsWebSocketChannel:: Generating Ping as network changed\n"));
+
+        if (mPingForced) {
+          // avoid more than one
+          return NS_OK;
+        }
+        if (!mPingTimer) {
+          // The ping timer is only conditionally running already. If it
+          // wasn't already created do it here.
+          nsresult rv;
+          mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+          if (NS_FAILED(rv)) {
+            NS_WARNING("unable to create ping timer. Carrying on.");
+          } else {
+            mPingTimer->SetTarget(mSocketThread);
+          }
+        }
+        // Trigger the ping timeout asap to fire off a new ping. Wait just
+        // a little bit to better avoid multi-triggers.
+        mPingForced = 1;
+        mPingTimer->InitWithCallback(this, 200, nsITimer::TYPE_ONE_SHOT);
+      }
+    }
+  }
+  return NS_OK;
+}
+
 void
 WebSocketChannel::Shutdown()
 {
   nsWSAdmissionManager::Shutdown();
 }
 
 void
 WebSocketChannel::BeginOpen()
@@ -2618,21 +2680,26 @@ WebSocketChannel::Notify(nsITimer *timer
 
     if (mClientClosed || mServerClosed || mRequestedClose) {
       // no point in worrying about ping now
       mPingTimer = nullptr;
       return NS_OK;
     }
 
     if (!mPingOutstanding) {
-      LOG(("nsWebSocketChannel:: Generating Ping\n"));
-      mPingOutstanding = 1;
-      GeneratePing();
-      mPingTimer->InitWithCallback(this, mPingResponseTimeout,
-                                   nsITimer::TYPE_ONE_SHOT);
+      // Allow for the case where a PING was force-sent even though ping
+      // interval isn't enabled. Only issue a new PING if it truly is enabled.
+      if (mPingInterval || mPingForced) {
+        LOG(("nsWebSocketChannel:: Generating Ping\n"));
+        mPingOutstanding = 1;
+        mPingForced = 0;
+        GeneratePing();
+        mPingTimer->InitWithCallback(this, mPingResponseTimeout,
+                                     nsITimer::TYPE_ONE_SHOT);
+      }
     } else {
       LOG(("nsWebSocketChannel:: Timed out Ping\n"));
       mPingTimer = nullptr;
       AbortSession(NS_ERROR_NET_TIMEOUT);
     }
   } else if (timer == mLingeringCloseTimer) {
     LOG(("WebSocketChannel:: Lingering Close Timer"));
     CleanupConnection();
--- a/netwerk/protocol/websocket/WebSocketChannel.h
+++ b/netwerk/protocol/websocket/WebSocketChannel.h
@@ -9,16 +9,17 @@
 
 #include "nsISupports.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIStreamListener.h"
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsITimer.h"
 #include "nsIDNSListener.h"
+#include "nsIObserver.h"
 #include "nsIProtocolProxyCallback.h"
 #include "nsIChannelEventSink.h"
 #include "nsIHttpChannelInternal.h"
 #include "BaseWebSocketChannel.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsINetworkManager.h"
 #include "nsProxyRelease.h"
@@ -57,32 +58,34 @@ enum wsConnectingState {
 
 class WebSocketChannel : public BaseWebSocketChannel,
                          public nsIHttpUpgradeListener,
                          public nsIStreamListener,
                          public nsIInputStreamCallback,
                          public nsIOutputStreamCallback,
                          public nsITimerCallback,
                          public nsIDNSListener,
+                         public nsIObserver,
                          public nsIProtocolProxyCallback,
                          public nsIInterfaceRequestor,
                          public nsIChannelEventSink
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIHTTPUPGRADELISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSIOUTPUTSTREAMCALLBACK
   NS_DECL_NSITIMERCALLBACK
   NS_DECL_NSIDNSLISTENER
   NS_DECL_NSIPROTOCOLPROXYCALLBACK
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSICHANNELEVENTSINK
+  NS_DECL_NSIOBSERVER
 
   // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
   //
   NS_IMETHOD AsyncOpen(nsIURI *aURI,
                        const nsACString &aOrigin,
                        nsIWebSocketListener *aListener,
                        nsISupports *aContext);
   NS_IMETHOD Close(uint16_t aCode, const nsACString & aReason);
@@ -155,18 +158,18 @@ private:
   bool     IsPersistentFramePtr();
   nsresult ProcessInput(uint8_t *buffer, uint32_t count);
   bool UpdateReadBuffer(uint8_t *buffer, uint32_t count,
                         uint32_t accumulatedFragments,
                         uint32_t *available);
 
   inline void ResetPingTimer()
   {
+    mPingOutstanding = 0;
     if (mPingTimer) {
-      mPingOutstanding = 0;
       mPingTimer->SetDelay(mPingInterval);
     }
   }
 
   nsCOMPtr<nsIEventTarget>                 mSocketThread;
   nsCOMPtr<nsIHttpChannelInternal>         mChannel;
   nsCOMPtr<nsIHttpChannel>                 mHttpChannel;
   nsCOMPtr<nsICancelable>                  mCancelable;