bug 607741 - lift 50 socket limitation r=jduell
authorPatrick McManus <mcmanus@ducksong.com>
Fri, 08 Apr 2011 14:36:38 -0400
changeset 67708 649e429d342f926227772c2d6cf1003c594524d7
parent 67665 1d17596bbf5f830272cd8b1d3cacaf742451cf3c
child 67709 198b15a6543283696599bcd6f3640ace0c8b82bc
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell
bugs607741
milestone2.2a1pre
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 607741 - lift 50 socket limitation r=jduell raise sockettransportservice max sockets to 550 from 50 for linux, os x, and windows >= xp. This does not change the default http max-connections config (which remains at 30), but does allow configurations above 50 to work and will enhance the utility of other systems that use the sockettransportservice. win9x provides a small number of sockets (100), so we just leave the limits unchanged there out of conservatism.
netwerk/base/src/nsSocketTransportService2.cpp
netwerk/base/src/nsSocketTransportService2.h
netwerk/protocol/http/nsHttpHandler.cpp
--- a/netwerk/base/src/nsSocketTransportService2.cpp
+++ b/netwerk/base/src/nsSocketTransportService2.cpp
@@ -59,49 +59,63 @@ using namespace mozilla;
 #if defined(PR_LOGGING)
 PRLogModuleInfo *gSocketTransportLog = nsnull;
 #endif
 
 nsSocketTransportService *gSocketTransportService = nsnull;
 PRThread                 *gSocketThread           = nsnull;
 
 #define SEND_BUFFER_PREF "network.tcp.sendbuffer"
+#define SOCKET_LIMIT_TARGET 550
 
 //-----------------------------------------------------------------------------
 // ctor/dtor (called on the main/UI thread by the service manager)
 
 nsSocketTransportService::nsSocketTransportService()
     : mThread(nsnull)
     , mThreadEvent(nsnull)
     , mAutodialEnabled(PR_FALSE)
     , mLock("nsSocketTransportService::mLock")
     , mInitialized(PR_FALSE)
     , mShuttingDown(PR_FALSE)
+    , mActiveListSize(50)
+    , mIdleListSize(50)
     , mActiveCount(0)
     , mIdleCount(0)
     , mSendBufferSize(0)
 {
 #if defined(PR_LOGGING)
     gSocketTransportLog = PR_NewLogModule("nsSocketTransport");
 #endif
 
     NS_ASSERTION(NS_IsMainThread(), "wrong thread");
 
+    DetermineMaxCount();                          /* init mMaxCount */
+    mActiveList = (SocketContext *)
+        moz_xmalloc(sizeof(SocketContext) * mActiveListSize);
+    mIdleList = (SocketContext *)
+        moz_xmalloc(sizeof(SocketContext) * mIdleListSize);
+    mPollList = (PRPollDesc *)
+        moz_xmalloc(sizeof(PRPollDesc) * (mActiveListSize + 1));
+
     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);
 
+    moz_free(mActiveList);
+    moz_free(mIdleList);
+    moz_free(mPollList);
     gSocketTransportService = nsnull;
 }
 
 //-----------------------------------------------------------------------------
 // event queue (any thread)
 
 already_AddRefed<nsIThread>
 nsSocketTransportService::GetThreadSafely()
@@ -172,30 +186,30 @@ nsSocketTransportService::AttachSocket(P
 
     nsresult rv = AddToIdleList(&sock);
     if (NS_SUCCEEDED(rv))
         NS_ADDREF(handler);
     return rv;
 }
 
 nsresult
-nsSocketTransportService::DetachSocket(SocketContext *sock)
+nsSocketTransportService::DetachSocket(SocketContext *listHead, SocketContext *sock)
 {
     SOCKET_LOG(("nsSocketTransportService::DetachSocket [handler=%x]\n", sock->mHandler));
+    NS_ABORT_IF_FALSE((listHead == mActiveList) || (listHead == mIdleList),
+                      "DetachSocket invalid head");
 
     // inform the handler that this socket is going away
     sock->mHandler->OnSocketDetached(sock->mFD);
 
     // cleanup
     sock->mFD = nsnull;
     NS_RELEASE(sock->mHandler);
 
-    // find out what list this is on.
-    PRUint32 index = sock - mActiveList;
-    if (index < NS_SOCKET_MAX_COUNT)
+    if (listHead == mActiveList)
         RemoveFromPollList(sock);
     else
         RemoveFromIdleList(sock);
 
     // NOTE: sock is now an invalid pointer
     
     //
     // notify the first element on the pending socket queue...
@@ -206,23 +220,28 @@ nsSocketTransportService::DetachSocket(S
         return Dispatch(event, NS_DISPATCH_NORMAL);
     }
     return NS_OK;
 }
 
 nsresult
 nsSocketTransportService::AddToPollList(SocketContext *sock)
 {
-    SOCKET_LOG(("nsSocketTransportService::AddToPollList [handler=%x]\n", sock->mHandler));
+    NS_ABORT_IF_FALSE(!(((PRUint32)(sock - mActiveList)) < mActiveListSize),
+                      "AddToPollList Socket Already Active");
 
-    if (mActiveCount == NS_SOCKET_MAX_COUNT) {
-        NS_ERROR("too many active sockets");
-        return NS_ERROR_UNEXPECTED;
+    SOCKET_LOG(("nsSocketTransportService::AddToPollList [handler=%x]\n", sock->mHandler));
+    if (mActiveCount == mActiveListSize) {
+        SOCKET_LOG(("  Active List size of %d met\n", mActiveCount));
+        if (!GrowActiveList()) {
+            NS_ERROR("too many active sockets");
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
     }
-
+    
     mActiveList[mActiveCount] = *sock;
     mActiveCount++;
 
     mPollList[mActiveCount].fd = sock->mFD;
     mPollList[mActiveCount].in_flags = sock->mHandler->mPollFlags;
     mPollList[mActiveCount].out_flags = 0;
 
     SOCKET_LOG(("  active=%u idle=%u\n", mActiveCount, mIdleCount));
@@ -230,81 +249,118 @@ nsSocketTransportService::AddToPollList(
 }
 
 void
 nsSocketTransportService::RemoveFromPollList(SocketContext *sock)
 {
     SOCKET_LOG(("nsSocketTransportService::RemoveFromPollList [handler=%x]\n", sock->mHandler));
 
     PRUint32 index = sock - mActiveList;
-    NS_ASSERTION(index < NS_SOCKET_MAX_COUNT, "invalid index");
+    NS_ABORT_IF_FALSE(index < mActiveListSize, "invalid index");
 
     SOCKET_LOG(("  index=%u mActiveCount=%u\n", index, mActiveCount));
 
     if (index != mActiveCount-1) {
         mActiveList[index] = mActiveList[mActiveCount-1];
         mPollList[index+1] = mPollList[mActiveCount];
     }
     mActiveCount--;
 
     SOCKET_LOG(("  active=%u idle=%u\n", mActiveCount, mIdleCount));
 }
 
 nsresult
 nsSocketTransportService::AddToIdleList(SocketContext *sock)
 {
-    SOCKET_LOG(("nsSocketTransportService::AddToIdleList [handler=%x]\n", sock->mHandler));
+    NS_ABORT_IF_FALSE(!(((PRUint32)(sock - mIdleList)) < mIdleListSize),
+                      "AddToIdlelList Socket Already Idle");
 
-    if (mIdleCount == NS_SOCKET_MAX_COUNT) {
-        NS_ERROR("too many idle sockets");
-        return NS_ERROR_UNEXPECTED;
+    SOCKET_LOG(("nsSocketTransportService::AddToIdleList [handler=%x]\n", sock->mHandler));
+    if (mIdleCount == mIdleListSize) {
+        SOCKET_LOG(("  Idle List size of %d met\n", mIdleCount));
+        if (!GrowIdleList()) {
+            NS_ERROR("too many idle sockets");
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
     }
 
     mIdleList[mIdleCount] = *sock;
     mIdleCount++;
 
     SOCKET_LOG(("  active=%u idle=%u\n", mActiveCount, mIdleCount));
     return NS_OK;
 }
 
 void
 nsSocketTransportService::RemoveFromIdleList(SocketContext *sock)
 {
     SOCKET_LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%x]\n", sock->mHandler));
 
-    PRUint32 index = sock - &mIdleList[0];
-    NS_ASSERTION(index < NS_SOCKET_MAX_COUNT, "invalid index");
+    PRUint32 index = sock - mIdleList;
+    NS_ASSERTION(index < mIdleListSize, "invalid index in idle list");
 
     if (index != mIdleCount-1)
         mIdleList[index] = mIdleList[mIdleCount-1];
     mIdleCount--;
 
     SOCKET_LOG(("  active=%u idle=%u\n", mActiveCount, mIdleCount));
 }
 
 void
 nsSocketTransportService::MoveToIdleList(SocketContext *sock)
 {
     nsresult rv = AddToIdleList(sock);
     if (NS_FAILED(rv))
-        DetachSocket(sock);
+        DetachSocket(mActiveList, sock);
     else
         RemoveFromPollList(sock);
 }
 
 void
 nsSocketTransportService::MoveToPollList(SocketContext *sock)
 {
     nsresult rv = AddToPollList(sock);
     if (NS_FAILED(rv))
-        DetachSocket(sock);
+        DetachSocket(mIdleList, sock);
     else
         RemoveFromIdleList(sock);
 }
 
+PRBool
+nsSocketTransportService::GrowActiveList()
+{
+    PRInt32 toAdd = MaxCount() - mActiveListSize;
+    if (toAdd > 100)
+        toAdd = 100;
+    if (toAdd < 1)
+        return PR_FALSE;
+    
+    mActiveListSize += toAdd;
+    mActiveList = (SocketContext *)
+        moz_xrealloc(mActiveList, sizeof(SocketContext) * mActiveListSize);
+    mPollList = (PRPollDesc *)
+        moz_xrealloc(mPollList, sizeof(PRPollDesc) * (mActiveListSize + 1));
+    return PR_TRUE;
+}
+
+PRBool
+nsSocketTransportService::GrowIdleList()
+{
+    PRInt32 toAdd = MaxCount() - mIdleListSize;
+    if (toAdd > 100)
+        toAdd = 100;
+    if (toAdd < 1)
+        return PR_FALSE;
+
+    mIdleListSize += toAdd;
+    mIdleList = (SocketContext *)
+        moz_xrealloc(mIdleList, sizeof(SocketContext) * mIdleListSize);
+    return PR_TRUE;
+}
+
 PRIntervalTime
 nsSocketTransportService::PollTimeout()
 {
     if (mActiveCount == 0)
         return NS_SOCKET_POLL_TIMEOUT;
 
     // compute minimum time before any socket timeout expires.
     PRUint32 minR = PR_UINT16_MAX;
@@ -589,19 +645,19 @@ nsSocketTransportService::Run()
         NS_ProcessNextEvent(thread);
     }
 
     SOCKET_LOG(("STS shutting down thread\n"));
 
     // detach any sockets
     PRInt32 i;
     for (i=mActiveCount-1; i>=0; --i)
-        DetachSocket(&mActiveList[i]);
+        DetachSocket(mActiveList, &mActiveList[i]);
     for (i=mIdleCount-1; i>=0; --i)
-        DetachSocket(&mIdleList[i]);
+        DetachSocket(mIdleList, &mIdleList[i]);
 
     // Final pass over the event queue. This makes sure that events posted by
     // socket detach handlers get processed.
     NS_ProcessPendingEvents(thread);
 
     gSocketThread = nsnull;
 
     SOCKET_LOG(("STS thread exit\n"));
@@ -630,17 +686,17 @@ nsSocketTransportService::DoPollIteratio
     for (i=mActiveCount-1; i>=0; --i) {
         //---
         SOCKET_LOG(("  active [%u] { handler=%x condition=%x pollflags=%hu }\n", i,
             mActiveList[i].mHandler,
             mActiveList[i].mHandler->mCondition,
             mActiveList[i].mHandler->mPollFlags));
         //---
         if (NS_FAILED(mActiveList[i].mHandler->mCondition))
-            DetachSocket(&mActiveList[i]);
+            DetachSocket(mActiveList, &mActiveList[i]);
         else {
             PRUint16 in_flags = mActiveList[i].mHandler->mPollFlags;
             if (in_flags == 0)
                 MoveToIdleList(&mActiveList[i]);
             else {
                 // update poll flags
                 mPollList[i+1].in_flags = in_flags;
                 mPollList[i+1].out_flags = 0;
@@ -650,17 +706,17 @@ nsSocketTransportService::DoPollIteratio
     for (i=count-1; i>=0; --i) {
         //---
         SOCKET_LOG(("  idle [%u] { handler=%x condition=%x pollflags=%hu }\n", i,
             mIdleList[i].mHandler,
             mIdleList[i].mHandler->mCondition,
             mIdleList[i].mHandler->mPollFlags));
         //---
         if (NS_FAILED(mIdleList[i].mHandler->mCondition))
-            DetachSocket(&mIdleList[i]);
+            DetachSocket(mIdleList, &mIdleList[i]);
         else if (mIdleList[i].mHandler->mPollFlags != 0)
             MoveToPollList(&mIdleList[i]);
     }
 
     SOCKET_LOG(("  calling PR_Poll [active=%u idle=%u]\n", mActiveCount, mIdleCount));
 
     // Measures seconds spent while blocked on PR_Poll
     PRUint32 pollInterval;
@@ -697,17 +753,17 @@ 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[i]);
+                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) {
                 // On Windows, the TCP loopback connection in the
                 // pollable event may become broken when a laptop
                 // switches between wired and wireless networks or
@@ -766,8 +822,72 @@ nsSocketTransportService::Observe(nsISup
 NS_IMETHODIMP
 nsSocketTransportService::GetSendBufferSize(PRInt32 *value)
 {
     *value = mSendBufferSize;
     return NS_OK;
 }
 
 
+/// ugly OS specific includes are placed at the bottom of the src for clarity
+
+#if defined(XP_WIN)
+#include <windows.h>
+#elif defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+#include <sys/resource.h>
+#endif
+
+void
+nsSocketTransportService::DetermineMaxCount()
+{
+    mMaxCount = 50;                                  /* historic default */
+    
+#if defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+    // on unix and os x network sockets and file
+    // descriptors are the same. OS X comes defaulted at 256,
+    // most linux at 1000. We can reliably use [sg]rlimit to
+    // query that and raise it. We will try to raise it 250 past
+    // our target number of SOCKET_LIMIT_TARGET so that some descriptors
+    // are still available for other things.
+
+    struct rlimit rlimitData;
+    if (getrlimit(RLIMIT_NOFILE, &rlimitData) == -1)
+        return;
+    if (rlimitData.rlim_cur >=  SOCKET_LIMIT_TARGET + 250) {
+        mMaxCount = SOCKET_LIMIT_TARGET;
+        return;
+    }
+
+    PRInt32 maxallowed = rlimitData.rlim_max;
+    if (maxallowed == -1)                         /* no limit */
+        maxallowed = SOCKET_LIMIT_TARGET + 250;
+    else if (maxallowed < 50 + 250)
+        return;
+    else if (maxallowed > SOCKET_LIMIT_TARGET + 250)
+        maxallowed = SOCKET_LIMIT_TARGET + 250;
+
+    rlimitData.rlim_cur = maxallowed;
+    setrlimit(RLIMIT_NOFILE, &rlimitData);
+    if (getrlimit(RLIMIT_NOFILE, &rlimitData) == -1)
+        return;
+    if (rlimitData.rlim_cur > 50 + 250)
+        mMaxCount = rlimitData.rlim_cur - 250;
+    return;
+
+#elif defined(XP_WIN) && !defined(WIN_CE)
+    // win 95, 98, etc had a limit of 100 - so we will just
+    // use the historical 50 in every case older than XP (0x501).
+    // >= XP is confirmed to have at least 1000
+
+    OSVERSIONINFO osInfo = { sizeof(OSVERSIONINFO) };
+    if (GetVersionEx(&osInfo)) {
+        PRInt32 version = 
+            (osInfo.dwMajorVersion & 0xff) << 8 | 
+            (osInfo.dwMinorVersion & 0xff);
+        if (version >= 0x501) {                    /* xp or later */
+            mMaxCount = SOCKET_LIMIT_TARGET;
+        }
+    }
+    return;
+#else
+    // other platforms are harder to test - so leave at safe legacy value
+#endif
+}
--- a/netwerk/base/src/nsSocketTransportService2.h
+++ b/netwerk/base/src/nsSocketTransportService2.h
@@ -60,17 +60,16 @@
 //
 extern PRLogModuleInfo *gSocketTransportLog;
 #endif
 #define SOCKET_LOG(args)     PR_LOG(gSocketTransportLog, PR_LOG_DEBUG, args)
 #define SOCKET_LOG_ENABLED() PR_LOG_TEST(gSocketTransportLog, PR_LOG_DEBUG)
 
 //-----------------------------------------------------------------------------
 
-#define NS_SOCKET_MAX_COUNT    50
 #define NS_SOCKET_POLL_TIMEOUT PR_INTERVAL_NO_TIMEOUT
 
 //-----------------------------------------------------------------------------
 
 class nsSocketTransportService : public nsPISocketTransportService
                                , public nsIEventTarget
                                , public nsIThreadObserver
                                , public nsIRunnable
@@ -92,19 +91,21 @@ public:
     //
     // the number of sockets that can be attached at any given time is
     // limited.  this is done because some operating systems (e.g., Win9x)
     // limit the number of sockets that can be created by an application.
     // AttachSocket will fail if the limit is exceeded.  consumers should
     // call CanAttachSocket and check the result before creating a socket.
     //
     PRBool CanAttachSocket() {
-        return mActiveCount + mIdleCount < NS_SOCKET_MAX_COUNT;
+        return mActiveCount + mIdleCount < mMaxCount;
     }
 
+    PRUint32 MaxCount() { return mMaxCount; }
+
 protected:
 
     virtual ~nsSocketTransportService();
 
 private:
 
     //-------------------------------------------------------------------------
     // misc (any thread)
@@ -148,38 +149,45 @@ private:
 
     struct SocketContext
     {
         PRFileDesc       *mFD;
         nsASocketHandler *mHandler;
         PRUint16          mElapsedTime;  // time elapsed w/o activity
     };
 
-    SocketContext mActiveList [ NS_SOCKET_MAX_COUNT ];
-    SocketContext mIdleList   [ NS_SOCKET_MAX_COUNT ];
+    SocketContext *mActiveList;                   /* mListSize entries */
+    SocketContext *mIdleList;                     /* mListSize entries */
 
+    PRUint32 mActiveListSize;
+    PRUint32 mIdleListSize;
     PRUint32 mActiveCount;
     PRUint32 mIdleCount;
+    PRUint32 mMaxCount;
 
-    nsresult DetachSocket(SocketContext *);
+    nsresult DetachSocket(SocketContext *, SocketContext *);
     nsresult AddToIdleList(SocketContext *);
     nsresult AddToPollList(SocketContext *);
     void RemoveFromIdleList(SocketContext *);
     void RemoveFromPollList(SocketContext *);
     void MoveToIdleList(SocketContext *sock);
     void MoveToPollList(SocketContext *sock);
-    
+
+    PRBool GrowActiveList();
+    PRBool GrowIdleList();
+    void   DetermineMaxCount();
+
     //-------------------------------------------------------------------------
     // poll list (socket thread only)
     //
     // first element of the poll list is mThreadEvent (or null if the pollable
     // event cannot be created).
     //-------------------------------------------------------------------------
 
-    PRPollDesc mPollList[ NS_SOCKET_MAX_COUNT + 1 ];
+    PRPollDesc *mPollList;                        /* mListSize + 1 entries */
 
     PRIntervalTime PollTimeout();            // computes ideal poll timeout
     nsresult       DoPollIteration(PRBool wait);
                                              // perfoms a single poll iteration
     PRInt32        Poll(PRBool wait, PRUint32 *interval);
                                              // calls PR_Poll.  the out param
                                              // interval indicates the poll
                                              // duration in seconds.
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -839,17 +839,18 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
                 mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_REQUEST_DELAY,
                                       mMaxRequestDelay);
         }
     }
 
     if (PREF_CHANGED(HTTP_PREF("max-connections"))) {
         rv = prefs->GetIntPref(HTTP_PREF("max-connections"), &val);
         if (NS_SUCCEEDED(rv)) {
-            mMaxConnections = (PRUint16) NS_CLAMP(val, 1, NS_SOCKET_MAX_COUNT);
+            mMaxConnections = (PRUint16) NS_CLAMP(
+                val, 1, gSocketTransportService->MaxCount());
             if (mConnMgr)
                 mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_CONNECTIONS,
                                       mMaxConnections);
         }
     }
 
     if (PREF_CHANGED(HTTP_PREF("max-connections-per-server"))) {
         rv = prefs->GetIntPref(HTTP_PREF("max-connections-per-server"), &val);