bug 664305 - websocket max connection limitation r=biesi
authorPatrick McManus <mcmanus@ducksong.com>
Sat, 25 Jun 2011 14:28:27 -0400
changeset 71792 43f88c89f4cb7e0b9e60c47cf1c8ee89821b6518
parent 71791 f20493d819b497494754ff50d54aeabee8c237a0
child 71793 d6ea917070d0f45d9f4d4754f76ad6cd6ab4fada
push id20613
push usermlamouri@mozilla.com
push dateMon, 27 Jun 2011 09:03:51 +0000
treeherdermozilla-central@aee9017b0b4f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbiesi
bugs664305
milestone7.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 664305 - websocket max connection limitation r=biesi
modules/libpref/src/init/all.js
netwerk/protocol/websocket/nsWebSocketHandler.cpp
netwerk/protocol/websocket/nsWebSocketHandler.h
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -808,16 +808,21 @@ pref("network.websocket.timeout.ping.req
 // generating a ping. If no activity happens then an error and unclean close
 // event is sent to the javascript websockets application
 pref("network.websocket.timeout.ping.response", 10);
 
 // Defines whether or not to try and negotiate the stream-deflate compression
 // extension with the websocket server
 pref("network.websocket.extensions.stream-deflate", true);
 
+// the maximum number of concurrent websocket sessions. By specification there
+// is never more than one handshake oustanding to an individual host at
+// one time.
+pref("network.websocket.max-connections", 200);
+
 // </ws>
 
 // Server-Sent Events
 
 pref("dom.server-events.enabled", true);
 // Equal to the DEFAULT_RECONNECTION_TIME_VALUE value in nsEventSource.cpp
 pref("dom.server-events.default-reconnection-time", 5000); // in milliseconds
 
--- a/netwerk/protocol/websocket/nsWebSocketHandler.cpp
+++ b/netwerk/protocol/websocket/nsWebSocketHandler.cpp
@@ -490,16 +490,17 @@ static nsWSAdmissionManager *sWebSocketA
 // nsWebSocketHandler
 
 nsWebSocketHandler::nsWebSocketHandler() :
     mEncrypted(PR_FALSE),
     mCloseTimeout(20000),
     mOpenTimeout(20000),
     mPingTimeout(0),
     mPingResponseTimeout(10000),
+    mMaxConcurrentConnections(200),
     mRecvdHttpOnStartRequest(0),
     mRecvdHttpUpgradeTransport(0),
     mRequestedClose(0),
     mClientClosed(0),
     mServerClosed(0),
     mStopped(0),
     mCalledOnStop(0),
     mPingOutstanding(0),
@@ -1438,21 +1439,17 @@ nsWebSocketHandler::StopSession(nsresult
 
 void
 nsWebSocketHandler::AbortSession(nsresult reason)
 {
     LOG(("WebSocketHandler::AbortSession() %p [reason %x] stopped = %d\n",
          this, reason, mStopped));
 
     // normally this should be called on socket thread, but it is ok to call it
-    // from OnStartRequest before the socket thread machine has gotten underway
-
-    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread ||
-                      !(mRecvdHttpOnStartRequest &&
-                        mRecvdHttpUpgradeTransport), "wrong thread");
+    // from the main thread before StartWebsocketData() has completed
 
     // When we are failing we need to close the TCP connection immediately
     // as per 7.1.1
     mTCPClosed = PR_TRUE;
 
     if (mLingeringCloseTimer) {
         NS_ABORT_IF_FALSE(mStopped, "Lingering without Stop");
         LOG(("Cleanup Connection based on TCP Close"));
@@ -1666,16 +1663,37 @@ nsWebSocketHandler::ApplyForAdmission()
                       this,
                       mainThread,
                       getter_AddRefs(mDNSRequest));
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
+// Called after both OnStartRequest and OnTransportAvailable have
+// executed. This essentially ends the handshake and starts the websockets
+// protocol state machine.
+nsresult
+nsWebSocketHandler::StartWebsocketData()
+{
+    LOG(("WebSocketHandler::StartWebsocketData() %p", this));
+
+    if (sWebSocketAdmissions &&
+        sWebSocketAdmissions->ConnectedCount() > mMaxConcurrentConnections) {
+        LOG(("nsWebSocketHandler max concurrency %d exceeded "
+             "in OnTransportAvailable()",
+             mMaxConcurrentConnections));
+        
+        AbortSession(NS_ERROR_SOCKET_CREATE_FAILED);
+        return NS_OK;
+    }
+
+    return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+}
+
 // nsIDNSListener
 
 NS_IMETHODIMP
 nsWebSocketHandler::OnLookupComplete(nsICancelable *aRequest,
                                      nsIDNSRecord *aRecord,
                                      nsresult aStatus)
 {
     LOG(("WebSocketHandler::OnLookupComplete() %p [%p %p %x]\n",
@@ -2035,18 +2053,37 @@ nsWebSocketHandler::AsyncOpen(nsIURI *aU
         if (NS_SUCCEEDED(rv)) {
             mAllowCompression = boolpref ? 1 : 0;
         }
         rv = prefService->GetBoolPref
             ("network.websocket.auto-follow-http-redirects", &boolpref);
         if (NS_SUCCEEDED(rv)) {
             mAutoFollowRedirects = boolpref ? 1 : 0;
         }
+        rv = prefService->GetIntPref
+            ("network.websocket.max-connections", &intpref);
+        if (NS_SUCCEEDED(rv)) {
+            mMaxConcurrentConnections = NS_CLAMP(intpref, 1, 0xffff);
+        }
     }
     
+    if (sWebSocketAdmissions &&
+        sWebSocketAdmissions->ConnectedCount() >= mMaxConcurrentConnections) {
+        // Checking this early creates an optimal fast-fail, but it is
+        // also a time-of-check-time-of-use problem. So we will check again
+        // after the handshake is complete to catch anything that sneaks
+        // through the race condition.
+        LOG(("nsWebSocketHandler max concurrency %d exceeded",
+             mMaxConcurrentConnections));
+        
+        // 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(("nsWebSocketHandler will generate ping after %d ms "
                  "of receive silence\n", mPingTimeout));
@@ -2194,17 +2231,17 @@ nsWebSocketHandler::OnTransportAvailable
     nsresult rv;
     rv = mTransport->SetEventSink(nsnull, nsnull);
     if (NS_FAILED(rv)) return rv;
     rv = mTransport->SetSecurityCallbacks(mCallbacks);
     if (NS_FAILED(rv)) return rv;
 
     mRecvdHttpUpgradeTransport = 1;
     if (mRecvdHttpOnStartRequest)
-        return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+        return StartWebsocketData();
     return NS_OK;
 }
 
 // nsIRequestObserver (from nsIStreamListener)
 
 NS_IMETHODIMP
 nsWebSocketHandler::OnStartRequest(nsIRequest *aRequest,
                                    nsISupports *aContext)
@@ -2359,17 +2396,17 @@ nsWebSocketHandler::OnStartRequest(nsIRe
     LOG(("WebSocketHandler::OnStartRequest Notifying Listener %p\n",
          mListener.get()));
     
     if (mListener)
         mListener->OnStart(mContext);
 
     mRecvdHttpOnStartRequest = 1;
     if (mRecvdHttpUpgradeTransport)
-        return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+        return StartWebsocketData();
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWebSocketHandler::OnStopRequest(nsIRequest *aRequest,
                                   nsISupports *aContext,
                                   nsresult aStatusCode)
--- a/netwerk/protocol/websocket/nsWebSocketHandler.h
+++ b/netwerk/protocol/websocket/nsWebSocketHandler.h
@@ -135,16 +135,17 @@ private:
   void PrimeNewOutgoingMessage();
   void GeneratePong(PRUint8 *payload, PRUint32 len);
   void GeneratePing();
 
   nsresult BeginOpen();
   nsresult HandleExtensions();
   nsresult SetupRequest();
   nsresult ApplyForAdmission();
+  nsresult StartWebsocketData();
   PRUint16 ResultToCloseCode(nsresult resultCode);
   
   void StopSession(nsresult reason);
   void AbortSession(nsresult reason);
   void ReleaseSession();
   void CleanupConnection();
 
   void EnsureHdrOut(PRUint32 size);
@@ -226,16 +227,18 @@ private:
   nsCOMPtr<nsITimer>              mPingTimer;
   PRUint32                        mPingTimeout;  /* milliseconds */
   PRUint32                        mPingResponseTimeout;  /* milliseconds */
   
   nsCOMPtr<nsITimer>              mLingeringCloseTimer;
   const static PRInt32            kLingeringCloseTimeout =   1000;
   const static PRInt32            kLingeringCloseThreshold = 50;
 
+  PRUint32                        mMaxConcurrentConnections;
+
   PRUint32                        mRecvdHttpOnStartRequest   : 1;
   PRUint32                        mRecvdHttpUpgradeTransport : 1;
   PRUint32                        mRequestedClose            : 1;
   PRUint32                        mClientClosed              : 1;
   PRUint32                        mServerClosed              : 1;
   PRUint32                        mStopped                   : 1;
   PRUint32                        mCalledOnStop              : 1;
   PRUint32                        mPingOutstanding           : 1;