Bug 849364 - Provide per-websocket way to enable keepalive pings. r=mcmanus
authorJason Duell <jduell.mcbugs@gmail.com>
Thu, 28 Mar 2013 15:52:16 -0700
changeset 137865 6f264e8f22cf605602bc1ac3c167dbed4efb0d8b
parent 137864 1a5385e4ee447dd05bddc802e16b57de10a01f97
child 137866 a92c968b29ae53249bb193772a9d43a4fd9f941b
push id336
push userakeybl@mozilla.com
push dateMon, 17 Jun 2013 22:53:19 +0000
treeherdermozilla-release@574a39cdf657 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus
bugs849364
milestone22.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 849364 - Provide per-websocket way to enable keepalive pings. r=mcmanus
netwerk/protocol/websocket/BaseWebSocketChannel.cpp
netwerk/protocol/websocket/BaseWebSocketChannel.h
netwerk/protocol/websocket/PWebSocket.ipdl
netwerk/protocol/websocket/WebSocketChannel.cpp
netwerk/protocol/websocket/WebSocketChannel.h
netwerk/protocol/websocket/WebSocketChannelChild.cpp
netwerk/protocol/websocket/WebSocketChannelParent.cpp
netwerk/protocol/websocket/WebSocketChannelParent.h
netwerk/protocol/websocket/nsIWebSocketChannel.idl
--- a/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
@@ -15,17 +15,22 @@
 #if defined(PR_LOGGING)
 PRLogModuleInfo *webSocketLog = nullptr;
 #endif
 
 namespace mozilla {
 namespace net {
 
 BaseWebSocketChannel::BaseWebSocketChannel()
-  : mEncrypted(false)
+  : mEncrypted(0)
+  , mWasOpened(0)
+  , mClientSetPingInterval(0)
+  , mClientSetPingTimeout(0)
+  , mPingInterval(0)
+  , mPingResponseTimeout(10000)
 {
 #if defined(PR_LOGGING)
   if (!webSocketLog)
     webSocketLog = PR_NewLogModule("nsWebSocket");
 #endif
 }
 
 //-----------------------------------------------------------------------------
@@ -110,16 +115,56 @@ BaseWebSocketChannel::GetProtocol(nsACSt
 NS_IMETHODIMP
 BaseWebSocketChannel::SetProtocol(const nsACString &aProtocol)
 {
   LOG(("BaseWebSocketChannel::SetProtocol() %p\n", this));
   mProtocol = aProtocol;                        /* the sub protocol */
   return NS_OK;
 }
 
+NS_IMETHODIMP
+BaseWebSocketChannel::GetPingInterval(uint32_t *aMilliSeconds)
+{
+  *aMilliSeconds = mPingInterval;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetPingInterval(uint32_t aMilliSeconds)
+{
+  if (mWasOpened) {
+    return NS_ERROR_IN_PROGRESS;
+  }
+
+  mPingInterval = aMilliSeconds;
+  mClientSetPingInterval = 1;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetPingTimeout(uint32_t *aMilliSeconds)
+{
+  *aMilliSeconds = mPingResponseTimeout;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetPingTimeout(uint32_t aMilliSeconds)
+{
+  if (mWasOpened) {
+    return NS_ERROR_IN_PROGRESS;
+  }
+
+  mPingResponseTimeout = aMilliSeconds;
+  mClientSetPingTimeout = 1;
+
+  return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // BaseWebSocketChannel::nsIProtocolHandler
 //-----------------------------------------------------------------------------
 
 
 NS_IMETHODIMP
 BaseWebSocketChannel::GetScheme(nsACString &aScheme)
 {
--- a/netwerk/protocol/websocket/BaseWebSocketChannel.h
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.h
@@ -37,28 +37,39 @@ class BaseWebSocketChannel : public nsIW
   NS_IMETHOD GetURI(nsIURI **aURI);
   NS_IMETHOD GetNotificationCallbacks(nsIInterfaceRequestor **aNotificationCallbacks);
   NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor *aNotificationCallbacks);
   NS_IMETHOD GetLoadGroup(nsILoadGroup **aLoadGroup);
   NS_IMETHOD SetLoadGroup(nsILoadGroup *aLoadGroup);
   NS_IMETHOD GetExtensions(nsACString &aExtensions);
   NS_IMETHOD GetProtocol(nsACString &aProtocol);
   NS_IMETHOD SetProtocol(const nsACString &aProtocol);
+  NS_IMETHOD GetPingInterval(uint32_t *aMilliSeconds);
+  NS_IMETHOD SetPingInterval(uint32_t aMilliSeconds);
+  NS_IMETHOD GetPingTimeout(uint32_t *aMilliSeconds);
+  NS_IMETHOD SetPingTimeout(uint32_t aMilliSeconds);
 
  protected:
   nsCOMPtr<nsIURI>                mOriginalURI;
   nsCOMPtr<nsIURI>                mURI;
   nsCOMPtr<nsIWebSocketListener>  mListener;
   nsCOMPtr<nsISupports>           mContext;
   nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
   nsCOMPtr<nsILoadGroup>          mLoadGroup;
 
   nsCString                       mProtocol;
   nsCString                       mOrigin;
 
-  bool                            mEncrypted;
   nsCString                       mNegotiatedExtensions;
+
+  uint32_t                        mEncrypted                 : 1;
+  uint32_t                        mWasOpened                 : 1;
+  uint32_t                        mClientSetPingInterval     : 1;
+  uint32_t                        mClientSetPingTimeout      : 1;
+
+  uint32_t                        mPingInterval;         /* milliseconds */
+  uint32_t                        mPingResponseTimeout;  /* milliseconds */
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_BaseWebSocketChannel_h
--- a/netwerk/protocol/websocket/PWebSocket.ipdl
+++ b/netwerk/protocol/websocket/PWebSocket.ipdl
@@ -22,17 +22,22 @@ async protocol PWebSocket
 {
   manager PNecko;
 
 parent:
   // Forwarded methods corresponding to methods on nsIWebSocketChannel
   AsyncOpen(URIParams aURI,
             nsCString aOrigin,
             nsCString aProtocol,
-            bool aSecure);
+            bool aSecure,
+            // ping values only meaningful if client set them
+            uint32_t aPingInterval,
+            bool aClientSetPingInterval,
+            uint32_t aPingTimeout,
+            bool aClientSetPingTimeout);
   Close(uint16_t code, nsCString reason);
   SendMsg(nsCString aMsg);
   SendBinaryMsg(nsCString aMsg);
   SendBinaryStream(InputStreamParams aStream, uint32_t aLength);
 
   DeleteSelf();
 
 child:
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -916,32 +916,29 @@ private:
 
 uint32_t WebSocketChannel::sSerialSeed = 0;
 
 WebSocketChannel::WebSocketChannel() :
   mPort(0),
   mCloseTimeout(20000),
   mOpenTimeout(20000),
   mConnecting(NOT_CONNECTING),
-  mPingTimeout(0),
-  mPingResponseTimeout(10000),
   mMaxConcurrentConnections(200),
   mGotUpgradeOK(0),
   mRecvdHttpUpgradeTransport(0),
   mRequestedClose(0),
   mClientClosed(0),
   mServerClosed(0),
   mStopped(0),
   mCalledOnStop(0),
   mPingOutstanding(0),
   mAllowCompression(1),
   mAutoFollowRedirects(0),
   mReleaseOnTransmit(0),
   mTCPClosed(0),
-  mWasOpened(0),
   mOpenedHttpChannel(0),
   mDataStarted(0),
   mIncrementedSessionCount(0),
   mDecrementedSessionCount(0),
   mMaxMessageSize(INT32_MAX),
   mStopOnClose(NS_OK),
   mServerCloseCode(CLOSE_ABNORMAL),
   mScriptCloseCode(0),
@@ -1148,25 +1145,21 @@ WebSocketChannel::UpdateReadBuffer(uint8
 }
 
 nsresult
 WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
 {
   LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered));
   NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
 
-  // reset the ping timer
-  if (mPingTimer) {
-    // The purpose of ping/pong is to actively probe the peer so that an
-    // unreachable peer is not mistaken for a period of idleness. This
-    // implementation accepts any application level read activity as a sign of
-    // life, it does not necessarily have to be a pong.
-    mPingOutstanding = 0;
-    mPingTimer->SetDelay(mPingTimeout);
-  }
+  // The purpose of ping/pong is to actively probe the peer so that an
+  // unreachable peer is not mistaken for a period of idleness. This
+  // implementation accepts any application level read activity as a sign of
+  // life, it does not necessarily have to be a pong.
+  ResetPingTimer();
 
   uint32_t avail;
 
   if (!mBuffered) {
     // Most of the time we can process right off the stack buffer without
     // having to accumulate anything
     mFramePtr = buffer;
     avail = count;
@@ -2228,16 +2221,30 @@ WebSocketChannel::StartWebsocketData()
   sWebSocketAdmissions->OnConnected(this);
 
   LOG(("WebSocketChannel::StartWebsocketData Notifying Listener %p\n",
        mListener.get()));
 
   if (mListener)
     mListener->OnStart(mContext);
 
+  // Start keepalive ping timer, if we're using keepalive.
+  if (mPingInterval) {
+    nsresult rv;
+    mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("unable to create ping timer. Carrying on.");
+    } else {
+      LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
+           mPingInterval));
+      mPingTimer->SetTarget(mSocketThread);
+      mPingTimer->InitWithCallback(this, mPingInterval, nsITimer::TYPE_ONE_SHOT);
+    }
+  }
+
   return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
 }
 
 void
 WebSocketChannel::ReportConnectionTelemetry()
 { 
   // 3 bits are used. high bit is for wss, middle bit for failed,
   // and low bit for proxy..
@@ -2502,16 +2509,17 @@ WebSocketChannel::Notify(nsITimer *timer
     CleanupConnection();
   } else {
     NS_ABORT_IF_FALSE(0, "Unknown Timer");
   }
 
   return NS_OK;
 }
 
+// nsIWebSocketChannel
 
 NS_IMETHODIMP
 WebSocketChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
 {
   LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this));
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
 
   if (mTransport) {
@@ -2571,22 +2579,22 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI
       mCloseTimeout = clamped(intpref, 1, 1800) * 1000;
     }
     rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref);
     if (NS_SUCCEEDED(rv)) {
       mOpenTimeout = clamped(intpref, 1, 1800) * 1000;
     }
     rv = prefService->GetIntPref("network.websocket.timeout.ping.request",
                                  &intpref);
-    if (NS_SUCCEEDED(rv)) {
-      mPingTimeout = clamped(intpref, 0, 86400) * 1000;
+    if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) {
+      mPingInterval = clamped(intpref, 0, 86400) * 1000;
     }
     rv = prefService->GetIntPref("network.websocket.timeout.ping.response",
                                  &intpref);
-    if (NS_SUCCEEDED(rv)) {
+    if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) {
       mPingResponseTimeout = clamped(intpref, 1, 3600) * 1000;
     }
     rv = prefService->GetBoolPref("network.websocket.extensions.stream-deflate",
                                   &boolpref);
     if (NS_SUCCEEDED(rv)) {
       mAllowCompression = boolpref ? 1 : 0;
     }
     rv = prefService->GetBoolPref("network.websocket.auto-follow-http-redirects",
@@ -2612,28 +2620,16 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI
          mMaxConcurrentConnections,
          sWebSocketAdmissions->SessionCount()));
 
     // WebSocket connections are expected to be long lived, so return
     // an error here instead of queueing
     return NS_ERROR_SOCKET_CREATE_FAILED;
   }
 
-  if (mPingTimeout) {
-    mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("unable to create ping timer. Carrying on.");
-    } else {
-      LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
-           mPingTimeout));
-      mPingTimer->SetTarget(mSocketThread);
-      mPingTimer->InitWithCallback(this, mPingTimeout, nsITimer::TYPE_ONE_SHOT);
-    }
-  }
-
   mOriginalURI = aURI;
   mURI = mOriginalURI;
   mOrigin = aOrigin;
 
   nsCOMPtr<nsIURI> localURI;
   nsCOMPtr<nsIChannel> localChannel;
 
   mURI->Clone(getter_AddRefs(localURI));
@@ -2796,16 +2792,18 @@ WebSocketChannel::SendMsgCommon(const ns
     aStream ? new OutboundEnqueuer(this, new OutboundMessage(aStream, aLength))
             : new OutboundEnqueuer(this,
                      new OutboundMessage(aIsBinary ? kMsgTypeBinaryString
                                                    : kMsgTypeString,
                                          new nsCString(*aMsg))),
     nsIEventTarget::DISPATCH_NORMAL);
 }
 
+// nsIHttpUpgradeListener
+
 NS_IMETHODIMP
 WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
                                        nsIAsyncInputStream *aSocketIn,
                                        nsIAsyncOutputStream *aSocketOut)
 {
   if (!NS_IsMainThread()) {
     return NS_DispatchToMainThread(new CallOnTransportAvailable(this,
                                                                 aTransport,
--- a/netwerk/protocol/websocket/WebSocketChannel.h
+++ b/netwerk/protocol/websocket/WebSocketChannel.h
@@ -146,16 +146,23 @@ private:
   void ApplyMask(uint32_t mask, uint8_t *data, uint64_t len);
 
   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()
+  {
+    if (mPingTimer) {
+      mPingOutstanding = 0;
+      mPingTimer->SetDelay(mPingInterval);
+    }
+  }
 
   nsCOMPtr<nsIEventTarget>                 mSocketThread;
   nsCOMPtr<nsIHttpChannelInternal>         mChannel;
   nsCOMPtr<nsIHttpChannel>                 mHttpChannel;
   nsCOMPtr<nsICancelable>                  mDNSRequest;
   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
   nsCOMPtr<nsIRandomGenerator>             mRandomGenerator;
 
@@ -174,18 +181,16 @@ private:
   uint32_t                        mCloseTimeout;  /* milliseconds */
 
   nsCOMPtr<nsITimer>              mOpenTimer;
   uint32_t                        mOpenTimeout;  /* milliseconds */
   wsConnectingState               mConnecting;   /* 0 if not connecting */
   nsCOMPtr<nsITimer>              mReconnectDelayTimer;
 
   nsCOMPtr<nsITimer>              mPingTimer;
-  uint32_t                        mPingTimeout;  /* milliseconds */
-  uint32_t                        mPingResponseTimeout;  /* milliseconds */
 
   nsCOMPtr<nsITimer>              mLingeringCloseTimer;
   const static int32_t            kLingeringCloseTimeout =   1000;
   const static int32_t            kLingeringCloseThreshold = 50;
 
   int32_t                         mMaxConcurrentConnections;
 
   uint32_t                        mGotUpgradeOK              : 1;
@@ -195,17 +200,16 @@ private:
   uint32_t                        mServerClosed              : 1;
   uint32_t                        mStopped                   : 1;
   uint32_t                        mCalledOnStop              : 1;
   uint32_t                        mPingOutstanding           : 1;
   uint32_t                        mAllowCompression          : 1;
   uint32_t                        mAutoFollowRedirects       : 1;
   uint32_t                        mReleaseOnTransmit         : 1;
   uint32_t                        mTCPClosed                 : 1;
-  uint32_t                        mWasOpened                 : 1;
   uint32_t                        mOpenedHttpChannel         : 1;
   uint32_t                        mDataStarted               : 1;
   uint32_t                        mIncrementedSessionCount   : 1;
   uint32_t                        mDecrementedSessionCount   : 1;
 
   int32_t                         mMaxMessageSize;
   nsresult                        mStopOnClose;
   uint16_t                        mServerCloseCode;
--- a/netwerk/protocol/websocket/WebSocketChannelChild.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
@@ -339,24 +339,27 @@ WebSocketChannelChild::AsyncOpen(nsIURI 
   URIParams uri;
   SerializeURI(aURI, uri);
 
   // Corresponding release in DeallocPWebSocket
   AddIPDLReference();
 
   gNeckoChild->SendPWebSocketConstructor(this, tabChild,
                                          IPC::SerializedLoadContext(this));
-  if (!SendAsyncOpen(uri, nsCString(aOrigin), mProtocol, mEncrypted))
+  if (!SendAsyncOpen(uri, nsCString(aOrigin), mProtocol, mEncrypted,
+                     mPingInterval, mClientSetPingInterval,
+                     mPingResponseTimeout, mClientSetPingTimeout))
     return NS_ERROR_UNEXPECTED;
 
   mOriginalURI = aURI;
   mURI = mOriginalURI;
   mListener = aListener;
   mContext = aContext;
   mOrigin = aOrigin;
+  mWasOpened = 1;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketChannelChild::Close(uint16_t code, const nsACString & reason)
 {
   LOG(("WebSocketChannelChild::Close() %p\n", this));
--- a/netwerk/protocol/websocket/WebSocketChannelParent.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
@@ -46,17 +46,21 @@ WebSocketChannelParent::RecvDeleteSelf()
   mAuthProvider = nullptr;
   return mIPCOpen ? Send__delete__(this) : true;
 }
 
 bool
 WebSocketChannelParent::RecvAsyncOpen(const URIParams& aURI,
                                       const nsCString& aOrigin,
                                       const nsCString& aProtocol,
-                                      const bool& aSecure)
+                                      const bool& aSecure,
+                                      const uint32_t& aPingInterval,
+                                      const bool& aClientSetPingInterval,
+                                      const uint32_t& aPingTimeout,
+                                      const bool& aClientSetPingTimeout)
 {
   LOG(("WebSocketChannelParent::RecvAsyncOpen() %p\n", this));
 
   nsresult rv;
   nsCOMPtr<nsIURI> uri;
 
   if (aSecure) {
     mChannel =
@@ -77,16 +81,24 @@ WebSocketChannelParent::RecvAsyncOpen(co
     goto fail;
 
   uri = DeserializeURI(aURI);
   if (!uri) {
     rv = NS_ERROR_FAILURE;
     goto fail;
   }
 
+  // only use ping values from child if they were overridden by client code.
+  if (aClientSetPingInterval) {
+    mChannel->SetPingInterval(aPingInterval);
+  }
+  if (aClientSetPingTimeout) {
+    mChannel->SetPingTimeout(aPingTimeout);
+  }
+
   rv = mChannel->AsyncOpen(uri, aOrigin, this, nullptr);
   if (NS_FAILED(rv))
     goto fail;
 
   return true;
 
 fail:
   mChannel = nullptr;
--- a/netwerk/protocol/websocket/WebSocketChannelParent.h
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.h
@@ -33,17 +33,21 @@ class WebSocketChannelParent : public PW
   WebSocketChannelParent(nsIAuthPromptProvider* aAuthProvider,
                          nsILoadContext* aLoadContext,
                          PBOverrideStatus aOverrideStatus);
 
  private:
   bool RecvAsyncOpen(const URIParams& aURI,
                      const nsCString& aOrigin,
                      const nsCString& aProtocol,
-                     const bool& aSecure);
+                     const bool& aSecure,
+                     const uint32_t& aPingInterval,
+                     const bool& aClientSetPingInterval,
+                     const uint32_t& aPingTimeout,
+                     const bool& aClientSetPingTimeout);
   bool RecvClose(const uint16_t & code, const nsCString & reason);
   bool RecvSendMsg(const nsCString& aMsg);
   bool RecvSendBinaryMsg(const nsCString& aMsg);
   bool RecvSendBinaryStream(const InputStreamParams& aStream,
                             const uint32_t& aLength);
   bool RecvDeleteSelf();
 
   void ActorDestroy(ActorDestroyReason why);
--- a/netwerk/protocol/websocket/nsIWebSocketChannel.idl
+++ b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
@@ -128,9 +128,31 @@ interface nsIWebSocketChannel : nsISuppo
 
     /** 
      * Use to send a binary stream (Blob) to Websocket peer.
      *
      * @param aStream The input stream to be sent.  
      */
     void sendBinaryStream(in nsIInputStream aStream, 
                           in unsigned long length);
+
+    /**
+     * This value determines how often (in milliseconds) websocket keepalive
+     * pings are sent.  If set to 0 (the default), no pings are ever sent.
+     *
+     * This value can currently only be set before asyncOpen is called, else 
+     * NS_ERROR_IN_PROGRESS is thrown.
+     *
+     * Be careful using this setting: ping traffic can consume lots of power and
+     * bandwidth over time.
+     */
+    attribute unsigned long pingInterval;
+
+    /**
+     * This value determines how long (in milliseconds) the websocket waits for
+     * the server to reply to a ping that has been sent.
+     *
+     * This value can currently only be set before asyncOpen is called, else 
+     * NS_ERROR_IN_PROGRESS is thrown.
+     */
+    attribute unsigned long pingTimeout;
+
 };