bug 762162 - network.http.timeout-connection r=honzab a=akebyl
authorPatrick McManus <mcmanus@ducksong.com>
Fri, 27 Jul 2012 20:34:31 -0400
changeset 102223 068e4ca57007c8ce2cc16230a745ee9ab23c5ebf
parent 102222 92d17113de2671b22e7e9f5b6d8f06caff9dbac7
child 102224 2a8c122b8a60a14258b8af20a8cb95856297975f
push idunknown
push userunknown
push dateunknown
reviewershonzab, akebyl
bugs762162
milestone16.0a2
bug 762162 - network.http.timeout-connection r=honzab a=akebyl
modules/libpref/src/init/all.js
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -866,16 +866,20 @@ pref("network.http.assoc-req.enforce", f
 // Section 4.8 "High-Throughput Data Service Class"
 pref("network.http.qos", 0);
 
 // The number of milliseconds after sending a SYN for an HTTP connection,
 // to wait before trying a different connection. 0 means do not use a second
 // connection.
 pref("network.http.connection-retry-timeout", 250);
 
+// The number of seconds after sending initial SYN for an HTTP connection
+// to give up if the OS does not give up first
+pref("network.http.connection-timeout", 90);
+
 // Disable IPv6 for backup connections to workaround problems about broken
 // IPv6 connectivity.
 pref("network.http.fast-fallback-to-IPv4", true);
 
 // Try and use SPDY when using SSL
 pref("network.http.spdy.enabled", true);
 pref("network.http.spdy.enabled.v2", true);
 pref("network.http.spdy.enabled.v3", true);
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -1733,16 +1733,17 @@ nsHttpConnectionMgr::AddActiveConn(nsHtt
     mNumActiveConns++;
     ActivateTimeoutTick();
 }
 
 void
 nsHttpConnectionMgr::StartedConnect()
 {
     mNumActiveConns++;
+    ActivateTimeoutTick(); // likely disabled by RecvdConnect()
 }
 
 void
 nsHttpConnectionMgr::RecvdConnect()
 {
     mNumActiveConns--;
     ConditionallyStopReadTimeoutTick();
 }
@@ -2196,20 +2197,52 @@ nsHttpConnectionMgr::ReadTimeoutTickCB(c
                                        nsAutoPtr<nsConnectionEntry> &ent,
                                        void *closure)
 {
     nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
 
     LOG(("nsHttpConnectionMgr::ReadTimeoutTickCB() this=%p host=%s\n",
          self, ent->mConnInfo->Host()));
 
+    // first call the tick handler for each active connection
     PRIntervalTime now = PR_IntervalNow();
     for (PRUint32 index = 0; index < ent->mActiveConns.Length(); ++index)
         ent->mActiveConns[index]->ReadTimeoutTick(now);
 
+    // now check for any stalled half open sockets
+    if (ent->mHalfOpens.Length()) {
+        TimeStamp now = TimeStamp::Now();
+        double maxConnectTime = gHttpHandler->ConnectTimeout();  /* in milliseconds */
+
+        for (PRUint32 index = ent->mHalfOpens.Length(); index > 0; ) {
+            index--;
+
+            nsHalfOpenSocket *half = ent->mHalfOpens[index];
+            double delta = half->Duration(now);
+            // If the socket has timed out, close it so the waiting transaction
+            // will get the proper signal
+            if (delta > maxConnectTime) {
+                LOG(("Force timeout of half open to %s after %.2fms.\n",
+                     ent->mConnInfo->HashKey().get(), delta));
+                if (half->SocketTransport())
+                    half->SocketTransport()->Close(NS_ERROR_NET_TIMEOUT);
+                if (half->BackupTransport())
+                    half->BackupTransport()->Close(NS_ERROR_NET_TIMEOUT);
+            }
+
+            // If this half open hangs around for 5 seconds after we've closed() it
+            // then just abandon the socket.
+            if (delta > maxConnectTime + 5000) {
+                LOG(("Abandon half open to %s after %.2fms.\n",
+                     ent->mConnInfo->HashKey().get(), delta));
+                half->Abandon();
+            }
+        }
+    }
+
     return PL_DHASH_NEXT;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpConnectionMgr::nsConnectionHandle
 
 nsHttpConnectionMgr::nsConnectionHandle::~nsConnectionHandle()
 {
@@ -2336,34 +2369,18 @@ nsHalfOpenSocket::nsHalfOpenSocket(nsCon
 
 nsHttpConnectionMgr::nsHalfOpenSocket::~nsHalfOpenSocket()
 {
     NS_ABORT_IF_FALSE(!mStreamOut, "streamout not null");
     NS_ABORT_IF_FALSE(!mBackupStreamOut, "backupstreamout not null");
     NS_ABORT_IF_FALSE(!mSynTimer, "syntimer not null");
     LOG(("Destroying nsHalfOpenSocket [this=%p]\n", this));
     
-    if (mEnt) {
-        // If the removal of the HalfOpenSocket from the mHalfOpens list
-        // removes the RestrictConnections() throttle then we need to
-        // process the pending queue.
-        bool restrictedBeforeRelease =
-            gHttpHandler->ConnMgr()->RestrictConnections(mEnt);
-
-        // A failure to create the transport object at all
-        // will result in this not being present in the halfopen table
-        // so ignore failures of RemoveElement()
-        mEnt->mHalfOpens.RemoveElement(this);
-
-        if (restrictedBeforeRelease &&
-            !gHttpHandler->ConnMgr()->RestrictConnections(mEnt)) {
-            LOG(("nsHalfOpenSocket %p lifted RestrictConnections() limit.\n"));
-            gHttpHandler->ConnMgr()->ProcessPendingQForEntry(mEnt);
-        }
-    }
+    if (mEnt)
+        mEnt->RemoveHalfOpen(this);
 }
 
 nsresult
 nsHttpConnectionMgr::
 nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport,
                                nsIAsyncInputStream **instream,
                                nsIAsyncOutputStream **outstream,
                                bool isBackup)
@@ -2538,19 +2555,31 @@ nsHttpConnectionMgr::nsHalfOpenSocket::A
     if (mBackupStreamOut) {
         gHttpHandler->ConnMgr()->RecvdConnect();
         mBackupStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
         mBackupStreamOut = nsnull;
     }
 
     CancelBackupTimer();
 
+    if (mEnt)
+        mEnt->RemoveHalfOpen(this);
     mEnt = nsnull;
 }
 
+double
+nsHttpConnectionMgr::nsHalfOpenSocket::Duration(mozilla::TimeStamp epoch)
+{
+    if (mPrimarySynStarted.IsNull())
+        return 0;
+
+    return (epoch - mPrimarySynStarted).ToMilliseconds();
+}
+
+
 NS_IMETHODIMP // method for nsITimerCallback
 nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer *timer)
 {
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     NS_ABORT_IF_FALSE(timer == mSynTimer, "wrong timer");
 
     SetupBackupStreams();
 
@@ -2960,16 +2989,42 @@ nsConnectionEntry::OnPipelineFeedbackInf
         LOG(("transition %s to yellow\n", mConnInfo->Host()));
         mPipelineState = PS_YELLOW;
         mYellowConnection = nsnull;
     }
 }
 
 void
 nsHttpConnectionMgr::
+nsConnectionEntry::RemoveHalfOpen(nsHalfOpenSocket *halfOpen)
+{
+    // A failure to create the transport object at all
+    // will result in it not being present in the halfopen table
+    // so ignore failures of RemoveElement()
+    mHalfOpens.RemoveElement(halfOpen);
+
+    if (!UnconnectedHalfOpens())
+        // perhaps this reverted RestrictConnections()
+        gHttpHandler->ConnMgr()->ProcessPendingQForEntry(this);
+}
+
+
+PRUint32
+nsHttpConnectionMgr::nsConnectionEntry::UnconnectedHalfOpens()
+{
+    PRUint32 unconnectedHalfOpens = 0;
+    for (PRUint32 i = 0; i < mHalfOpens.Length(); ++i) {
+        if (!mHalfOpens[i]->HasConnected())
+            ++unconnectedHalfOpens;
+    }
+    return unconnectedHalfOpens;
+}
+
+void
+nsHttpConnectionMgr::
 nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn)
 {
     NS_ABORT_IF_FALSE(!mYellowConnection && mPipelineState == PS_YELLOW,
                       "yellow connection already set or state is not yellow");
     mYellowConnection = conn;
     mYellowGoodEvents = mYellowBadEvents = 0;
 }
 
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -260,16 +260,23 @@ private:
         ~nsConnectionEntry();
 
         nsHttpConnectionInfo        *mConnInfo;
         nsTArray<nsHttpTransaction*> mPendingQ;    // pending transaction queue
         nsTArray<nsHttpConnection*>  mActiveConns; // active connections
         nsTArray<nsHttpConnection*>  mIdleConns;   // idle persistent connections
         nsTArray<nsHalfOpenSocket*>  mHalfOpens;
 
+        // calculate the number of half open sockets that have not had at least 1
+        // connection complete
+        PRUint32 UnconnectedHalfOpens();
+
+        // Remove a particular half open socket from the mHalfOpens array
+        void RemoveHalfOpen(nsHalfOpenSocket *);
+
         // Pipeline depths for various states
         const static PRUint32 kPipelineUnlimited  = 1024; // fully open - extended green
         const static PRUint32 kPipelineOpen       = 6;    // 6 on each conn - normal green
         const static PRUint32 kPipelineRestricted = 2;    // 2 on just 1 conn in yellow
         
         nsHttpConnectionMgr::PipeliningState PipelineState();
         void OnPipelineFeedbackInfo(
             nsHttpConnectionMgr::PipelineFeedbackInfoType info,
@@ -383,17 +390,20 @@ private:
                               nsIAsyncInputStream **,
                               nsIAsyncOutputStream **,
                               bool isBackup);
         nsresult SetupPrimaryStreams();
         nsresult SetupBackupStreams();
         void     SetupBackupTimer();
         void     CancelBackupTimer();
         void     Abandon();
-        
+        double   Duration(mozilla::TimeStamp epoch);
+        nsISocketTransport *SocketTransport() { return mSocketTransport; }
+        nsISocketTransport *BackupTransport() { return mBackupTransport; }
+
         nsAHttpTransaction *Transaction() { return mTransaction; }
 
         bool IsSpeculative() { return mSpeculative; }
         void SetSpeculative(bool val) { mSpeculative = val; }
 
         bool HasConnected() { return mHasConnected; }
 
         void PrintDiagnostics(nsCString &log);
@@ -577,18 +587,18 @@ private:
     PRUint16 mNumIdleConns;
 
     // Holds time in seconds for next wake-up to prune dead connections. 
     PRUint64 mTimeOfNextWakeUp;
     // Timer for next pruning of dead connections.
     nsCOMPtr<nsITimer> mTimer;
 
     // A 1s tick to call nsHttpConnection::ReadTimeoutTick on
-    // active http/1 connections. Disabled when there are no
-    // active connections.
+    // active http/1 connections and check for orphaned half opens.
+    // Disabled when there are no active or half open connections.
     nsCOMPtr<nsITimer> mReadTimeoutTick;
     bool mReadTimeoutTickArmed;
 
     //
     // the connection table
     //
     // this table is indexed by connection key.  each entry is a
     // nsConnectionEntry object.
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -171,16 +171,17 @@ nsHttpHandler::nsHttpHandler()
     , mEnableSpdy(false)
     , mSpdyV2(true)
     , mSpdyV3(true)
     , mCoalesceSpdy(true)
     , mUseAlternateProtocol(false)
     , mSpdySendingChunkSize(ASpdySession::kSendingChunkSize)
     , mSpdyPingThreshold(PR_SecondsToInterval(44))
     , mSpdyPingTimeout(PR_SecondsToInterval(8))
+    , mConnectTimeout(90000)
 {
 #if defined(PR_LOGGING)
     gHttpLog = PR_NewLogModule("nsHttp");
 #endif
 
     LOG(("Creating nsHttpHandler [this=%x].\n", this));
 
     NS_ASSERTION(!gHttpHandler, "HTTP handler already created!");
@@ -1138,16 +1139,25 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
     // closing the session.
     if (PREF_CHANGED(HTTP_PREF("spdy.ping-timeout"))) {
         rv = prefs->GetIntPref(HTTP_PREF("spdy.ping-timeout"), &val);
         if (NS_SUCCEEDED(rv))
             mSpdyPingTimeout =
                 PR_SecondsToInterval((PRUint16) clamped(val, 0, 0x7fffffff));
     }
 
+    // The maximum amount of time to wait for socket transport to be
+    // established
+    if (PREF_CHANGED(HTTP_PREF("connection-timeout"))) {
+        rv = prefs->GetIntPref(HTTP_PREF("connection-timeout"), &val);
+        if (NS_SUCCEEDED(rv))
+            // the pref is in seconds, but the variable is in milliseconds
+            mConnectTimeout = clamped(val, 1, 0xffff) * PR_MSEC_PER_SEC;
+    }
+
     // on transition of network.http.diagnostics to true print
     // a bunch of information to the console
     if (pref && PREF_CHANGED(HTTP_PREF("diagnostics"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("diagnostics"), &cVar);
         if (NS_SUCCEEDED(rv) && cVar) {
             if (mConnMgr)
                 mConnMgr->PrintDiagnostics();
         }
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -89,16 +89,17 @@ public:
     bool           IsSpdyEnabled() { return mEnableSpdy; }
     bool           IsSpdyV2Enabled() { return mSpdyV2; }
     bool           IsSpdyV3Enabled() { return mSpdyV3; }
     bool           CoalesceSpdy() { return mCoalesceSpdy; }
     bool           UseAlternateProtocol() { return mUseAlternateProtocol; }
     PRUint32       SpdySendingChunkSize() { return mSpdySendingChunkSize; }
     PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
     PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
+    PRUint32       ConnectTimeout()  { return mConnectTimeout; }
 
     bool           PromptTempRedirect()      { return mPromptTempRedirect; }
 
     nsHttpAuthCache     *AuthCache() { return &mAuthCache; }
     nsHttpConnectionMgr *ConnMgr()   { return mConnMgr; }
 
     // cache support
     bool UseCache() const { return mUseCache; }
@@ -362,16 +363,20 @@ private:
     bool           mEnableSpdy;
     bool           mSpdyV2;
     bool           mSpdyV3;
     bool           mCoalesceSpdy;
     bool           mUseAlternateProtocol;
     PRUint32       mSpdySendingChunkSize;
     PRIntervalTime mSpdyPingThreshold;
     PRIntervalTime mSpdyPingTimeout;
+
+    // The maximum amount of time to wait for socket transport to be
+    // established. In milliseconds.
+    PRUint32       mConnectTimeout;
 };
 
 //-----------------------------------------------------------------------------
 
 extern nsHttpHandler *gHttpHandler;
 
 //-----------------------------------------------------------------------------
 // nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the