mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
b46439ff28
Losing a TCP SYN requires a long painful (typically 3 second) delay before being retried. This patch creates a second parallel connection attempt for any nsHttpConnection which has not become writable before a timeout occurs. If you assume .5% packet loss, this converts a full 3 second delay from a 1 in 200 event into a 1 in 40,000 event. Whichever connection establishes itself first is used. If another one has been started and it does connect before the one being used is closed then the extra one is handed to the connection manager for use by a different transaction - essentially a persistent connection with 0 previous transactions on it. (Another way to think about is pre-fetching a 3WHS on a high latency connection). The pref network.http.connection-retry-timeout controls the amount of time in ms to wait for success on the initial connection before beginning the second one. Setting it to 0 disables the parallel connection, the default is 250.
1116 lines
35 KiB
C++
1116 lines
35 KiB
C++
/* vim:set ts=4 sw=4 sts=4 et cin: */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Mozilla.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 2002
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Darin Fisher <darin@netscape.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
#include "nsHttpConnectionMgr.h"
|
|
#include "nsHttpConnection.h"
|
|
#include "nsHttpPipeline.h"
|
|
#include "nsHttpHandler.h"
|
|
#include "nsAutoLock.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsCOMPtr.h"
|
|
|
|
#include "nsIServiceManager.h"
|
|
|
|
#include "nsIObserverService.h"
|
|
|
|
// defined by the socket transport service while active
|
|
extern PRThread *gSocketThread;
|
|
|
|
static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(nsHttpConnectionMgr, nsIObserver)
|
|
|
|
static void
|
|
InsertTransactionSorted(nsTArray<nsHttpTransaction*> &pendingQ, nsHttpTransaction *trans)
|
|
{
|
|
// insert into queue with smallest valued number first. search in reverse
|
|
// order under the assumption that many of the existing transactions will
|
|
// have the same priority (usually 0).
|
|
|
|
for (PRInt32 i=pendingQ.Length()-1; i>=0; --i) {
|
|
nsHttpTransaction *t = pendingQ[i];
|
|
if (trans->Priority() >= t->Priority()) {
|
|
pendingQ.InsertElementAt(i+1, trans);
|
|
return;
|
|
}
|
|
}
|
|
pendingQ.InsertElementAt(0, trans);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsHttpConnectionMgr::nsHttpConnectionMgr()
|
|
: mRef(0)
|
|
, mMonitor(nsAutoMonitor::NewMonitor("nsHttpConnectionMgr"))
|
|
, mMaxConns(0)
|
|
, mMaxConnsPerHost(0)
|
|
, mMaxConnsPerProxy(0)
|
|
, mMaxPersistConnsPerHost(0)
|
|
, mMaxPersistConnsPerProxy(0)
|
|
, mNumActiveConns(0)
|
|
, mNumIdleConns(0)
|
|
, mTimeOfNextWakeUp(LL_MAXUINT)
|
|
{
|
|
LOG(("Creating nsHttpConnectionMgr @%x\n", this));
|
|
}
|
|
|
|
nsHttpConnectionMgr::~nsHttpConnectionMgr()
|
|
{
|
|
LOG(("Destroying nsHttpConnectionMgr @%x\n", this));
|
|
|
|
if (mMonitor)
|
|
nsAutoMonitor::DestroyMonitor(mMonitor);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::Init(PRUint16 maxConns,
|
|
PRUint16 maxConnsPerHost,
|
|
PRUint16 maxConnsPerProxy,
|
|
PRUint16 maxPersistConnsPerHost,
|
|
PRUint16 maxPersistConnsPerProxy,
|
|
PRUint16 maxRequestDelay,
|
|
PRUint16 maxPipelinedRequests)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::Init\n"));
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
// do nothing if already initialized
|
|
if (mSocketThreadTarget)
|
|
return NS_OK;
|
|
|
|
// no need to do any special synchronization here since there cannot be
|
|
// any activity on the socket thread (because Shutdown is synchronous).
|
|
mMaxConns = maxConns;
|
|
mMaxConnsPerHost = maxConnsPerHost;
|
|
mMaxConnsPerProxy = maxConnsPerProxy;
|
|
mMaxPersistConnsPerHost = maxPersistConnsPerHost;
|
|
mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
|
|
mMaxRequestDelay = maxRequestDelay;
|
|
mMaxPipelinedRequests = maxPipelinedRequests;
|
|
|
|
mSocketThreadTarget = sts;
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::Shutdown()
|
|
{
|
|
LOG(("nsHttpConnectionMgr::Shutdown\n"));
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
// do nothing if already shutdown
|
|
if (!mSocketThreadTarget)
|
|
return NS_OK;
|
|
|
|
nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgShutdown);
|
|
|
|
// release our reference to the STS to prevent further events
|
|
// from being posted. this is how we indicate that we are
|
|
// shutting down.
|
|
mSocketThreadTarget = 0;
|
|
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("unable to post SHUTDOWN message");
|
|
return rv;
|
|
}
|
|
|
|
// wait for shutdown event to complete
|
|
mon.Wait();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler, PRInt32 iparam, void *vparam)
|
|
{
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
nsresult rv;
|
|
if (!mSocketThreadTarget) {
|
|
NS_WARNING("cannot post event if not initialized");
|
|
rv = NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
else {
|
|
nsRefPtr<nsIRunnable> event = new nsConnEvent(this, handler, iparam, vparam);
|
|
if (!event)
|
|
rv = NS_ERROR_OUT_OF_MEMORY;
|
|
else
|
|
rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::PruneDeadConnectionsAfter(PRUint32 timeInSeconds)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n"));
|
|
|
|
if(!mTimer)
|
|
mTimer = do_CreateInstance("@mozilla.org/timer;1");
|
|
|
|
// failure to create a timer is not a fatal error, but idle connections
|
|
// will not be cleaned up until we try to use them.
|
|
if (mTimer) {
|
|
mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
|
|
mTimer->Init(this, timeInSeconds*1000, nsITimer::TYPE_ONE_SHOT);
|
|
} else {
|
|
NS_WARNING("failed to create: timer for pruning the dead connections!");
|
|
}
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::StopPruneDeadConnectionsTimer()
|
|
{
|
|
LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
|
|
|
|
// Reset mTimeOfNextWakeUp so that we can find a new shortest value.
|
|
mTimeOfNextWakeUp = LL_MAXUINT;
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
mTimer = NULL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsHttpConnectionMgr::nsIObserver
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
nsHttpConnectionMgr::Observe(nsISupports *subject,
|
|
const char *topic,
|
|
const PRUnichar *data)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic));
|
|
|
|
if (0 == strcmp(topic, "timer-callback")) {
|
|
// prune dead connections
|
|
PruneDeadConnections();
|
|
#ifdef DEBUG
|
|
nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
|
|
NS_ASSERTION(timer == mTimer, "unexpected timer-callback");
|
|
#endif
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::AddTransaction(nsHttpTransaction *trans, PRInt32 priority)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::AddTransaction [trans=%x %d]\n", trans, priority));
|
|
|
|
NS_ADDREF(trans);
|
|
nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority, trans);
|
|
if (NS_FAILED(rv))
|
|
NS_RELEASE(trans);
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, PRInt32 priority)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%x %d]\n", trans, priority));
|
|
|
|
NS_ADDREF(trans);
|
|
nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans);
|
|
if (NS_FAILED(rv))
|
|
NS_RELEASE(trans);
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%x reason=%x]\n", trans, reason));
|
|
|
|
NS_ADDREF(trans);
|
|
nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction, reason, trans);
|
|
if (NS_FAILED(rv))
|
|
NS_RELEASE(trans);
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::PruneDeadConnections()
|
|
{
|
|
return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
|
|
{
|
|
nsAutoMonitor mon(mMonitor);
|
|
NS_IF_ADDREF(*target = mSocketThreadTarget);
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::AddTransactionToPipeline(nsHttpPipeline *pipeline)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::AddTransactionToPipeline [pipeline=%x]\n", pipeline));
|
|
|
|
NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
|
|
|
|
nsRefPtr<nsHttpConnectionInfo> ci;
|
|
pipeline->GetConnectionInfo(getter_AddRefs(ci));
|
|
if (ci) {
|
|
nsCStringKey key(ci->HashKey());
|
|
nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
|
|
if (ent) {
|
|
// search for another request to pipeline...
|
|
PRInt32 i, count = ent->mPendingQ.Length();
|
|
for (i=0; i<count; ++i) {
|
|
nsHttpTransaction *trans = ent->mPendingQ[i];
|
|
if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) {
|
|
pipeline->AddTransaction(trans);
|
|
|
|
// remove transaction from pending queue
|
|
ent->mPendingQ.RemoveElementAt(i);
|
|
NS_RELEASE(trans);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%x]\n", conn));
|
|
|
|
NS_ADDREF(conn);
|
|
nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn);
|
|
if (NS_FAILED(rv))
|
|
NS_RELEASE(conn);
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::UpdateParam(nsParamName name, PRUint16 value)
|
|
{
|
|
PRUint32 param = (PRUint32(name) << 16) | PRUint32(value);
|
|
return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam, 0, (void *) param);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo *ci)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
|
|
|
|
NS_ADDREF(ci);
|
|
nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
|
|
if (NS_FAILED(rv))
|
|
NS_RELEASE(ci);
|
|
return rv;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// enumeration callbacks
|
|
|
|
PRIntn
|
|
nsHttpConnectionMgr::ProcessOneTransactionCB(nsHashKey *key, void *data, void *closure)
|
|
{
|
|
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
|
|
nsConnectionEntry *ent = (nsConnectionEntry *) data;
|
|
|
|
if (self->ProcessPendingQForEntry(ent))
|
|
return kHashEnumerateStop;
|
|
|
|
return kHashEnumerateNext;
|
|
}
|
|
|
|
// If the global number of idle connections is preventing the opening of
|
|
// new connections to a host without idle connections, then
|
|
// close them regardless of their TTL
|
|
PRIntn
|
|
nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(nsHashKey *key,
|
|
void *data, void *closure)
|
|
{
|
|
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
|
|
nsConnectionEntry *ent = (nsConnectionEntry *) data;
|
|
|
|
while (self->mNumIdleConns + self->mNumActiveConns + 1 >= self->mMaxConns) {
|
|
if (!ent->mIdleConns.Length()) {
|
|
// There are no idle conns left in this connection entry
|
|
return kHashEnumerateNext;
|
|
}
|
|
nsHttpConnection *conn = ent->mIdleConns[0];
|
|
ent->mIdleConns.RemoveElementAt(0);
|
|
conn->Close(NS_ERROR_ABORT);
|
|
NS_RELEASE(conn);
|
|
self->mNumIdleConns--;
|
|
if (0 == self->mNumIdleConns)
|
|
self->StopPruneDeadConnectionsTimer();
|
|
}
|
|
return kHashEnumerateStop;
|
|
}
|
|
|
|
PRIntn
|
|
nsHttpConnectionMgr::PruneDeadConnectionsCB(nsHashKey *key, void *data, void *closure)
|
|
{
|
|
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
|
|
nsConnectionEntry *ent = (nsConnectionEntry *) data;
|
|
|
|
LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
|
|
|
|
// Find out how long it will take for next idle connection to not be reusable
|
|
// anymore.
|
|
PRUint32 timeToNextExpire = PR_UINT32_MAX;
|
|
PRInt32 count = ent->mIdleConns.Length();
|
|
if (count > 0) {
|
|
for (PRInt32 i=count-1; i>=0; --i) {
|
|
nsHttpConnection *conn = ent->mIdleConns[i];
|
|
if (!conn->CanReuse()) {
|
|
ent->mIdleConns.RemoveElementAt(i);
|
|
conn->Close(NS_ERROR_ABORT);
|
|
NS_RELEASE(conn);
|
|
self->mNumIdleConns--;
|
|
} else {
|
|
timeToNextExpire = PR_MIN(timeToNextExpire, conn->TimeToLive());
|
|
}
|
|
}
|
|
}
|
|
|
|
// If time to next expire found is shorter than time to next wake-up, we need to
|
|
// change the time for next wake-up.
|
|
PRUint32 now = NowInSeconds();
|
|
if (0 < ent->mIdleConns.Length()) {
|
|
PRUint64 timeOfNextExpire = now + timeToNextExpire;
|
|
// If pruning of dead connections is not already scheduled to happen
|
|
// or time found for next connection to expire is is before
|
|
// mTimeOfNextWakeUp, we need to schedule the pruning to happen
|
|
// after timeToNextExpire.
|
|
if (!self->mTimer || timeOfNextExpire < self->mTimeOfNextWakeUp) {
|
|
self->PruneDeadConnectionsAfter(timeToNextExpire);
|
|
}
|
|
} else if (0 == self->mNumIdleConns) {
|
|
self->StopPruneDeadConnectionsTimer();
|
|
}
|
|
#ifdef DEBUG
|
|
count = ent->mActiveConns.Length();
|
|
if (count > 0) {
|
|
for (PRInt32 i=count-1; i>=0; --i) {
|
|
nsHttpConnection *conn = ent->mActiveConns[i];
|
|
LOG((" active conn [%x] with trans [%x]\n", conn, conn->Transaction()));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// if this entry is empty, then we can remove it.
|
|
if (ent->mIdleConns.Length() == 0 &&
|
|
ent->mActiveConns.Length() == 0 &&
|
|
ent->mPendingQ.Length() == 0) {
|
|
LOG((" removing empty connection entry\n"));
|
|
delete ent;
|
|
return kHashEnumerateRemove;
|
|
}
|
|
|
|
// else, use this opportunity to compact our arrays...
|
|
ent->mIdleConns.Compact();
|
|
ent->mActiveConns.Compact();
|
|
ent->mPendingQ.Compact();
|
|
|
|
return kHashEnumerateNext;
|
|
}
|
|
|
|
PRIntn
|
|
nsHttpConnectionMgr::ShutdownPassCB(nsHashKey *key, void *data, void *closure)
|
|
{
|
|
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
|
|
nsConnectionEntry *ent = (nsConnectionEntry *) data;
|
|
|
|
nsHttpTransaction *trans;
|
|
nsHttpConnection *conn;
|
|
|
|
// close all active connections
|
|
while (ent->mActiveConns.Length()) {
|
|
conn = ent->mActiveConns[0];
|
|
|
|
ent->mActiveConns.RemoveElementAt(0);
|
|
self->mNumActiveConns--;
|
|
|
|
conn->Close(NS_ERROR_ABORT);
|
|
NS_RELEASE(conn);
|
|
}
|
|
|
|
// close all idle connections
|
|
while (ent->mIdleConns.Length()) {
|
|
conn = ent->mIdleConns[0];
|
|
|
|
ent->mIdleConns.RemoveElementAt(0);
|
|
self->mNumIdleConns--;
|
|
|
|
conn->Close(NS_ERROR_ABORT);
|
|
NS_RELEASE(conn);
|
|
}
|
|
// If all idle connections are removed,
|
|
// we can stop pruning dead connections.
|
|
if (0 == self->mNumIdleConns)
|
|
self->StopPruneDeadConnectionsTimer();
|
|
|
|
// close all pending transactions
|
|
while (ent->mPendingQ.Length()) {
|
|
trans = ent->mPendingQ[0];
|
|
|
|
ent->mPendingQ.RemoveElementAt(0);
|
|
|
|
trans->Close(NS_ERROR_ABORT);
|
|
NS_RELEASE(trans);
|
|
}
|
|
|
|
delete ent;
|
|
return kHashEnumerateRemove;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
PRBool
|
|
nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n",
|
|
ent->mConnInfo->HashKey().get()));
|
|
|
|
PRInt32 i, count = ent->mPendingQ.Length();
|
|
if (count > 0) {
|
|
LOG((" pending-count=%u\n", count));
|
|
nsHttpTransaction *trans = nsnull;
|
|
nsHttpConnection *conn = nsnull;
|
|
for (i=0; i<count; ++i) {
|
|
trans = ent->mPendingQ[i];
|
|
GetConnection(ent, trans->Caps(), &conn);
|
|
if (conn)
|
|
break;
|
|
}
|
|
if (conn) {
|
|
LOG((" dispatching pending transaction...\n"));
|
|
|
|
// remove pending transaction
|
|
ent->mPendingQ.RemoveElementAt(i);
|
|
|
|
nsresult rv = DispatchTransaction(ent, trans, trans->Caps(), conn);
|
|
if (NS_SUCCEEDED(rv))
|
|
NS_RELEASE(trans);
|
|
else {
|
|
LOG((" DispatchTransaction failed [rv=%x]\n", rv));
|
|
// on failure, just put the transaction back
|
|
ent->mPendingQ.InsertElementAt(i, trans);
|
|
// might be something wrong with the connection... close it.
|
|
conn->Close(rv);
|
|
}
|
|
|
|
NS_RELEASE(conn);
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
// we're at the active connection limit if any one of the following conditions is true:
|
|
// (1) at max-connections
|
|
// (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
|
|
// (3) keep-alive disabled and at max-connections-per-server
|
|
PRBool
|
|
nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, PRUint8 caps)
|
|
{
|
|
nsHttpConnectionInfo *ci = ent->mConnInfo;
|
|
|
|
LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x]\n",
|
|
ci->HashKey().get(), caps));
|
|
|
|
// If we have more active connections than the limit, then we're done --
|
|
// purging idle connections won't get us below it.
|
|
if (mNumActiveConns >= mMaxConns) {
|
|
LOG((" num active conns == max conns\n"));
|
|
return PR_TRUE;
|
|
}
|
|
|
|
nsHttpConnection *conn;
|
|
PRInt32 i, totalCount, persistCount = 0;
|
|
|
|
totalCount = ent->mActiveConns.Length();
|
|
|
|
// count the number of persistent connections
|
|
for (i=0; i<totalCount; ++i) {
|
|
conn = ent->mActiveConns[i];
|
|
if (conn->IsKeepAlive()) // XXX make sure this is thread-safe
|
|
persistCount++;
|
|
}
|
|
|
|
LOG((" total=%d, persist=%d\n", totalCount, persistCount));
|
|
|
|
PRUint16 maxConns;
|
|
PRUint16 maxPersistConns;
|
|
|
|
if (ci->UsingHttpProxy() && !ci->UsingSSL()) {
|
|
maxConns = mMaxConnsPerProxy;
|
|
maxPersistConns = mMaxPersistConnsPerProxy;
|
|
}
|
|
else {
|
|
maxConns = mMaxConnsPerHost;
|
|
maxPersistConns = mMaxPersistConnsPerHost;
|
|
}
|
|
|
|
// use >= just to be safe
|
|
return (totalCount >= maxConns) || ( (caps & NS_HTTP_ALLOW_KEEPALIVE) &&
|
|
(persistCount >= maxPersistConns) );
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, PRUint8 caps,
|
|
nsHttpConnection **result)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::GetConnection [ci=%s caps=%x]\n",
|
|
ent->mConnInfo->HashKey().get(), PRUint32(caps)));
|
|
|
|
// First, see if an idle persistent connection may be reused instead of
|
|
// establishing a new socket. We do not need to check the connection limits
|
|
// yet as they govern the maximum number of open connections and reusing
|
|
// an old connection never increases that.
|
|
|
|
*result = nsnull;
|
|
|
|
nsHttpConnection *conn = nsnull;
|
|
|
|
if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
|
|
// search the idle connection list. Each element in the list
|
|
// has a reference, so if we remove it from the list into a local
|
|
// ptr, that ptr now owns the reference
|
|
while (!conn && (ent->mIdleConns.Length() > 0)) {
|
|
conn = ent->mIdleConns[0];
|
|
// we check if the connection can be reused before even checking if
|
|
// it is a "matching" connection.
|
|
if (!conn->CanReuse()) {
|
|
LOG((" dropping stale connection: [conn=%x]\n", conn));
|
|
conn->Close(NS_ERROR_ABORT);
|
|
NS_RELEASE(conn);
|
|
}
|
|
else
|
|
LOG((" reusing connection [conn=%x]\n", conn));
|
|
ent->mIdleConns.RemoveElementAt(0);
|
|
mNumIdleConns--;
|
|
|
|
// If there are no idle connections left at all, we need to make
|
|
// sure that we are not pruning dead connections anymore.
|
|
if (0 == mNumIdleConns)
|
|
StopPruneDeadConnectionsTimer();
|
|
}
|
|
}
|
|
|
|
if (!conn) {
|
|
// Check if we need to purge an idle connection. Note that we may have
|
|
// removed one above; if so, this will be a no-op. We do this before
|
|
// checking the active connection limit to catch the case where we do
|
|
// have an idle connection, but the purge timer hasn't fired yet.
|
|
// XXX this just purges a random idle connection. we should instead
|
|
// enumerate the entire hash table to find the eldest idle connection.
|
|
if (mNumIdleConns && mNumIdleConns + mNumActiveConns + 1 >= mMaxConns)
|
|
mCT.Enumerate(PurgeExcessIdleConnectionsCB, this);
|
|
|
|
// Need to make a new TCP connection. First, we check if we've hit
|
|
// either the maximum connection limit globally or for this particular
|
|
// host or proxy. If we have, we're done.
|
|
if (AtActiveConnectionLimit(ent, caps)) {
|
|
LOG((" at active connection limit!\n"));
|
|
return;
|
|
}
|
|
|
|
conn = new nsHttpConnection();
|
|
NS_ADDREF(conn);
|
|
nsresult rv = conn->Init(ent->mConnInfo, mMaxRequestDelay);
|
|
if (NS_FAILED(rv)) {
|
|
NS_RELEASE(conn);
|
|
return;
|
|
}
|
|
}
|
|
|
|
*result = conn;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
|
|
nsAHttpTransaction *trans,
|
|
PRUint8 caps,
|
|
nsHttpConnection *conn)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::DispatchTransaction [ci=%s trans=%x caps=%x conn=%x]\n",
|
|
ent->mConnInfo->HashKey().get(), trans, caps, conn));
|
|
|
|
nsConnectionHandle *handle = new nsConnectionHandle(conn);
|
|
if (!handle)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
NS_ADDREF(handle);
|
|
|
|
nsHttpPipeline *pipeline = nsnull;
|
|
if (conn->SupportsPipelining() && (caps & NS_HTTP_ALLOW_PIPELINING)) {
|
|
LOG((" looking to build pipeline...\n"));
|
|
if (BuildPipeline(ent, trans, &pipeline))
|
|
trans = pipeline;
|
|
}
|
|
|
|
// hold an owning ref to this connection
|
|
ent->mActiveConns.AppendElement(conn);
|
|
mNumActiveConns++;
|
|
NS_ADDREF(conn);
|
|
|
|
// give the transaction the indirect reference to the connection.
|
|
trans->SetConnection(handle);
|
|
|
|
nsresult rv = conn->Activate(trans, caps);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
LOG((" conn->Activate failed [rv=%x]\n", rv));
|
|
ent->mActiveConns.RemoveElement(conn);
|
|
mNumActiveConns--;
|
|
// sever back references to connection, and do so without triggering
|
|
// a call to ReclaimConnection ;-)
|
|
trans->SetConnection(nsnull);
|
|
NS_RELEASE(handle->mConn);
|
|
// destroy the connection
|
|
NS_RELEASE(conn);
|
|
}
|
|
|
|
// if we were unable to activate the pipeline, then this will destroy
|
|
// the pipeline, which will cause each the transactions owned by the
|
|
// pipeline to be restarted.
|
|
NS_IF_RELEASE(pipeline);
|
|
|
|
NS_RELEASE(handle);
|
|
return rv;
|
|
}
|
|
|
|
PRBool
|
|
nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
|
|
nsAHttpTransaction *firstTrans,
|
|
nsHttpPipeline **result)
|
|
{
|
|
if (mMaxPipelinedRequests < 2)
|
|
return PR_FALSE;
|
|
|
|
nsHttpPipeline *pipeline = nsnull;
|
|
nsHttpTransaction *trans;
|
|
|
|
PRUint32 i = 0, numAdded = 0;
|
|
while (i < ent->mPendingQ.Length()) {
|
|
trans = ent->mPendingQ[i];
|
|
if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) {
|
|
if (numAdded == 0) {
|
|
pipeline = new nsHttpPipeline;
|
|
if (!pipeline)
|
|
return PR_FALSE;
|
|
pipeline->AddTransaction(firstTrans);
|
|
numAdded = 1;
|
|
}
|
|
pipeline->AddTransaction(trans);
|
|
|
|
// remove transaction from pending queue
|
|
ent->mPendingQ.RemoveElementAt(i);
|
|
NS_RELEASE(trans);
|
|
|
|
if (++numAdded == mMaxPipelinedRequests)
|
|
break;
|
|
}
|
|
else
|
|
++i; // skip to next pending transaction
|
|
}
|
|
|
|
if (numAdded == 0)
|
|
return PR_FALSE;
|
|
|
|
LOG((" pipelined %u transactions\n", numAdded));
|
|
NS_ADDREF(*result = pipeline);
|
|
return PR_TRUE;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
|
|
{
|
|
// since "adds" and "cancels" are processed asynchronously and because
|
|
// various events might trigger an "add" directly on the socket thread,
|
|
// we must take care to avoid dispatching a transaction that has already
|
|
// been canceled (see bug 190001).
|
|
if (NS_FAILED(trans->Status())) {
|
|
LOG((" transaction was canceled... dropping event!\n"));
|
|
return NS_OK;
|
|
}
|
|
|
|
PRUint8 caps = trans->Caps();
|
|
nsHttpConnectionInfo *ci = trans->ConnectionInfo();
|
|
NS_ASSERTION(ci, "no connection info");
|
|
|
|
nsCStringKey key(ci->HashKey());
|
|
nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
|
|
if (!ent) {
|
|
nsHttpConnectionInfo *clone = ci->Clone();
|
|
if (!clone)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
ent = new nsConnectionEntry(clone);
|
|
if (!ent)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
mCT.Put(&key, ent);
|
|
}
|
|
|
|
nsHttpConnection *conn;
|
|
|
|
// check if the transaction already has a sticky reference to a connection.
|
|
// if so, then we can just use it directly. XXX check if alive??
|
|
// XXX add a TakeConnection method or something to make this clearer!
|
|
nsConnectionHandle *handle = (nsConnectionHandle *) trans->Connection();
|
|
if (handle) {
|
|
NS_ASSERTION(caps & NS_HTTP_STICKY_CONNECTION, "unexpected caps");
|
|
NS_ASSERTION(handle->mConn, "no connection");
|
|
|
|
// steal reference from connection handle.
|
|
// XXX prevent SetConnection(nsnull) from calling ReclaimConnection
|
|
conn = handle->mConn;
|
|
handle->mConn = nsnull;
|
|
|
|
// destroy connection handle.
|
|
trans->SetConnection(nsnull);
|
|
|
|
// remove sticky connection from active connection list; we'll add it
|
|
// right back in DispatchTransaction.
|
|
if (ent->mActiveConns.RemoveElement(conn))
|
|
mNumActiveConns--;
|
|
else {
|
|
NS_ERROR("sticky connection not found in active list");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
}
|
|
else
|
|
GetConnection(ent, caps, &conn);
|
|
|
|
nsresult rv;
|
|
if (!conn) {
|
|
LOG((" adding transaction to pending queue [trans=%x pending-count=%u]\n",
|
|
trans, ent->mPendingQ.Length()+1));
|
|
// put this transaction on the pending queue...
|
|
InsertTransactionSorted(ent->mPendingQ, trans);
|
|
NS_ADDREF(trans);
|
|
rv = NS_OK;
|
|
}
|
|
else {
|
|
rv = DispatchTransaction(ent, trans, caps, conn);
|
|
NS_RELEASE(conn);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void
|
|
nsHttpConnectionMgr::OnMsgShutdown(PRInt32, void *)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
|
|
|
|
mCT.Reset(ShutdownPassCB, this);
|
|
|
|
// signal shutdown complete
|
|
nsAutoMonitor mon(mMonitor);
|
|
mon.Notify();
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::OnMsgNewTransaction(PRInt32 priority, void *param)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param));
|
|
|
|
nsHttpTransaction *trans = (nsHttpTransaction *) param;
|
|
trans->SetPriority(priority);
|
|
nsresult rv = ProcessNewTransaction(trans);
|
|
if (NS_FAILED(rv))
|
|
trans->Close(rv); // for whatever its worth
|
|
NS_RELEASE(trans);
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::OnMsgReschedTransaction(PRInt32 priority, void *param)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param));
|
|
|
|
nsHttpTransaction *trans = (nsHttpTransaction *) param;
|
|
trans->SetPriority(priority);
|
|
|
|
nsHttpConnectionInfo *ci = trans->ConnectionInfo();
|
|
nsCStringKey key(ci->HashKey());
|
|
nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
|
|
if (ent) {
|
|
PRInt32 index = ent->mPendingQ.IndexOf(trans);
|
|
if (index >= 0) {
|
|
ent->mPendingQ.RemoveElementAt(index);
|
|
InsertTransactionSorted(ent->mPendingQ, trans);
|
|
}
|
|
}
|
|
|
|
NS_RELEASE(trans);
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::OnMsgCancelTransaction(PRInt32 reason, void *param)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
|
|
|
|
nsHttpTransaction *trans = (nsHttpTransaction *) param;
|
|
//
|
|
// if the transaction owns a connection and the transaction is not done,
|
|
// then ask the connection to close the transaction. otherwise, close the
|
|
// transaction directly (removing it from the pending queue first).
|
|
//
|
|
nsAHttpConnection *conn = trans->Connection();
|
|
if (conn && !trans->IsDone())
|
|
conn->CloseTransaction(trans, reason);
|
|
else {
|
|
nsHttpConnectionInfo *ci = trans->ConnectionInfo();
|
|
nsCStringKey key(ci->HashKey());
|
|
nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
|
|
if (ent) {
|
|
PRInt32 index = ent->mPendingQ.IndexOf(trans);
|
|
if (index >= 0) {
|
|
ent->mPendingQ.RemoveElementAt(index);
|
|
nsHttpTransaction *temp = trans;
|
|
NS_RELEASE(temp); // b/c NS_RELEASE nulls its argument!
|
|
}
|
|
}
|
|
trans->Close(reason);
|
|
}
|
|
NS_RELEASE(trans);
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::OnMsgProcessPendingQ(PRInt32, void *param)
|
|
{
|
|
nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param;
|
|
|
|
LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
|
|
|
|
// start by processing the queue identified by the given connection info.
|
|
nsCStringKey key(ci->HashKey());
|
|
nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
|
|
if (!(ent && ProcessPendingQForEntry(ent))) {
|
|
// if we reach here, it means that we couldn't dispatch a transaction
|
|
// for the specified connection info. walk the connection table...
|
|
mCT.Enumerate(ProcessOneTransactionCB, this);
|
|
}
|
|
|
|
NS_RELEASE(ci);
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::OnMsgPruneDeadConnections(PRInt32, void *)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
|
|
|
|
// Reset mTimeOfNextWakeUp so that we can find a new shortest value.
|
|
mTimeOfNextWakeUp = LL_MAXUINT;
|
|
if (mNumIdleConns > 0)
|
|
mCT.Enumerate(PruneDeadConnectionsCB, this);
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param)
|
|
{
|
|
LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param));
|
|
|
|
nsHttpConnection *conn = (nsHttpConnection *) param;
|
|
|
|
//
|
|
// 1) remove the connection from the active list
|
|
// 2) if keep-alive, add connection to idle list
|
|
// 3) post event to process the pending transaction queue
|
|
//
|
|
|
|
nsHttpConnectionInfo *ci = conn->ConnectionInfo();
|
|
NS_ADDREF(ci);
|
|
|
|
nsCStringKey key(ci->HashKey());
|
|
nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
|
|
|
|
NS_ASSERTION(ent, "no connection entry");
|
|
if (ent) {
|
|
// If the connection is in the active list, remove that entry
|
|
// and the reference held by the mActiveConns list.
|
|
// This is never the final reference on conn as the event context
|
|
// is also holding one that is released at the end of this function.
|
|
if (ent->mActiveConns.RemoveElement(conn)) {
|
|
nsHttpConnection *temp = conn;
|
|
NS_RELEASE(temp);
|
|
mNumActiveConns--;
|
|
}
|
|
|
|
if (conn->CanReuse()) {
|
|
LOG((" adding connection to idle list\n"));
|
|
// hold onto this connection in the idle list. we push it to
|
|
// the end of the list so as to ensure that we'll visit older
|
|
// connections first before getting to this one.
|
|
NS_ADDREF(conn);
|
|
ent->mIdleConns.AppendElement(conn);
|
|
mNumIdleConns++;
|
|
// If the added connection was first idle connection or has shortest
|
|
// time to live among the idle connections, pruning dead
|
|
// connections needs to be done when it can't be reused anymore.
|
|
PRUint32 timeToLive = conn->TimeToLive();
|
|
if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
|
|
PruneDeadConnectionsAfter(timeToLive);
|
|
}
|
|
else {
|
|
LOG((" connection cannot be reused; closing connection\n"));
|
|
// make sure the connection is closed and release our reference.
|
|
conn->Close(NS_ERROR_ABORT);
|
|
}
|
|
}
|
|
|
|
OnMsgProcessPendingQ(NS_OK, ci); // releases |ci|
|
|
NS_RELEASE(conn);
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::OnMsgUpdateParam(PRInt32, void *param)
|
|
{
|
|
PRUint16 name = (NS_PTR_TO_INT32(param) & 0xFFFF0000) >> 16;
|
|
PRUint16 value = NS_PTR_TO_INT32(param) & 0x0000FFFF;
|
|
|
|
switch (name) {
|
|
case MAX_CONNECTIONS:
|
|
mMaxConns = value;
|
|
break;
|
|
case MAX_CONNECTIONS_PER_HOST:
|
|
mMaxConnsPerHost = value;
|
|
break;
|
|
case MAX_CONNECTIONS_PER_PROXY:
|
|
mMaxConnsPerProxy = value;
|
|
break;
|
|
case MAX_PERSISTENT_CONNECTIONS_PER_HOST:
|
|
mMaxPersistConnsPerHost = value;
|
|
break;
|
|
case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
|
|
mMaxPersistConnsPerProxy = value;
|
|
break;
|
|
case MAX_REQUEST_DELAY:
|
|
mMaxRequestDelay = value;
|
|
break;
|
|
case MAX_PIPELINED_REQUESTS:
|
|
mMaxPipelinedRequests = value;
|
|
break;
|
|
default:
|
|
NS_NOTREACHED("unexpected parameter name");
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsHttpConnectionMgr::nsConnectionHandle
|
|
|
|
nsHttpConnectionMgr::nsConnectionHandle::~nsConnectionHandle()
|
|
{
|
|
if (mConn) {
|
|
gHttpHandler->ReclaimConnection(mConn);
|
|
NS_RELEASE(mConn);
|
|
}
|
|
}
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS0(nsHttpConnectionMgr::nsConnectionHandle)
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::nsConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans,
|
|
nsHttpRequestHead *req,
|
|
nsHttpResponseHead *resp,
|
|
PRBool *reset)
|
|
{
|
|
return mConn->OnHeadersAvailable(trans, req, resp, reset);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::nsConnectionHandle::ResumeSend()
|
|
{
|
|
return mConn->ResumeSend();
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv()
|
|
{
|
|
return mConn->ResumeRecv();
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::nsConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
|
|
{
|
|
mConn->CloseTransaction(trans, reason);
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::nsConnectionHandle::GetConnectionInfo(nsHttpConnectionInfo **result)
|
|
{
|
|
mConn->GetConnectionInfo(result);
|
|
}
|
|
|
|
void
|
|
nsHttpConnectionMgr::nsConnectionHandle::GetSecurityInfo(nsISupports **result)
|
|
{
|
|
mConn->GetSecurityInfo(result);
|
|
}
|
|
|
|
PRBool
|
|
nsHttpConnectionMgr::nsConnectionHandle::IsPersistent()
|
|
{
|
|
return mConn->IsPersistent();
|
|
}
|
|
|
|
PRBool
|
|
nsHttpConnectionMgr::nsConnectionHandle::IsReused()
|
|
{
|
|
return mConn->IsReused();
|
|
}
|
|
|
|
nsresult
|
|
nsHttpConnectionMgr::nsConnectionHandle::PushBack(const char *buf, PRUint32 bufLen)
|
|
{
|
|
return mConn->PushBack(buf, bufLen);
|
|
}
|
|
|