bug 623948 - Accelerate TCP connection retries in HTTP r=honzab

This commit is contained in:
Patrick McManus 2011-03-31 15:38:30 -04:00
Родитель 60a2b4eaa8
Коммит 82e6a8e87c
11 изменённых файлов: 632 добавлений и 167 удалений

Просмотреть файл

@ -753,6 +753,11 @@ pref("network.http.prompt-temp-redirect", true);
// 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);
// default values for FTP
// in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,
// Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22)

Просмотреть файл

@ -1797,9 +1797,21 @@ nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor **callbacks)
NS_IMETHODIMP
nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor *callbacks)
{
nsAutoLock lock(mLock);
mCallbacks = callbacks;
// XXX should we tell PSM about this?
nsCOMPtr<nsISupports> secinfo;
{
nsAutoLock lock(mLock);
mCallbacks = callbacks;
SOCKET_LOG(("Reset callbacks for secinfo=%p callbacks=%p\n",
mSecInfo.get(), mCallbacks.get()));
secinfo = mSecInfo;
}
// don't call into PSM while holding mLock!!
nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
if (secCtrl)
secCtrl->SetNotificationCallbacks(callbacks);
return NS_OK;
}

Просмотреть файл

@ -44,6 +44,7 @@ class nsAHttpConnection;
class nsAHttpSegmentReader;
class nsAHttpSegmentWriter;
class nsIInterfaceRequestor;
class nsIEventTarget;
//----------------------------------------------------------------------------
// Abstract base class for a HTTP transaction:
@ -62,7 +63,8 @@ public:
// called by the connection to get security callbacks to set on the
// socket transport.
virtual void GetSecurityCallbacks(nsIInterfaceRequestor **) = 0;
virtual void GetSecurityCallbacks(nsIInterfaceRequestor **,
nsIEventTarget **) = 0;
// called to report socket status (see nsITransportEventSink)
virtual void OnTransportStatus(nsresult status, PRUint64 progress) = 0;
@ -88,7 +90,8 @@ public:
#define NS_DECL_NSAHTTPTRANSACTION \
void SetConnection(nsAHttpConnection *); \
void GetSecurityCallbacks(nsIInterfaceRequestor **); \
void GetSecurityCallbacks(nsIInterfaceRequestor **, \
nsIEventTarget **); \
void OnTransportStatus(nsresult status, PRUint64 progress); \
PRBool IsDone(); \
nsresult Status(); \

Просмотреть файл

@ -51,6 +51,7 @@
#include "netCore.h"
#include "nsNetCID.h"
#include "nsAutoLock.h"
#include "nsProxyRelease.h"
#include "prmem.h"
#ifdef DEBUG
@ -66,9 +67,10 @@ static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
nsHttpConnection::nsHttpConnection()
: mTransaction(nsnull)
, mConnInfo(nsnull)
, mLastReadTime(0)
, mIdleTimeout(0)
, mConsiderReusedAfterInterval(0)
, mConsiderReusedAfterEpoch(0)
, mKeepAlive(PR_TRUE) // assume to keep-alive by default
, mKeepAliveMask(PR_TRUE)
, mSupportsPipelining(PR_FALSE) // assume low-grade server
@ -86,9 +88,12 @@ nsHttpConnection::nsHttpConnection()
nsHttpConnection::~nsHttpConnection()
{
LOG(("Destroying nsHttpConnection @%x\n", this));
NS_IF_RELEASE(mConnInfo);
NS_IF_RELEASE(mTransaction);
if (mCallbacks) {
nsIInterfaceRequestor *cbs = nsnull;
mCallbacks.swap(cbs);
NS_ProxyRelease(mCallbackTarget, cbs);
}
// release our reference to the handler
nsHttpHandler *handler = gHttpHandler;
@ -96,18 +101,38 @@ nsHttpConnection::~nsHttpConnection()
}
nsresult
nsHttpConnection::Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime)
nsHttpConnection::Init(nsHttpConnectionInfo *info,
PRUint16 maxHangTime,
nsISocketTransport *transport,
nsIAsyncInputStream *instream,
nsIAsyncOutputStream *outstream,
nsIInterfaceRequestor *callbacks,
nsIEventTarget *callbackTarget)
{
LOG(("nsHttpConnection::Init [this=%x]\n", this));
NS_ABORT_IF_FALSE(transport && instream && outstream,
"invalid socket information");
LOG(("nsHttpConnection::Init [this=%p "
"transport=%p instream=%p outstream=%p]\n",
this, transport, instream, outstream));
NS_ENSURE_ARG_POINTER(info);
NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
mConnInfo = info;
NS_ADDREF(mConnInfo);
mMaxHangTime = maxHangTime;
mLastReadTime = NowInSeconds();
mSocketTransport = transport;
mSocketIn = instream;
mSocketOut = outstream;
nsresult rv = mSocketTransport->SetEventSink(this, nsnull);
NS_ENSURE_SUCCESS(rv, rv);
mCallbacks = callbacks;
mCallbackTarget = callbackTarget;
rv = mSocketTransport->SetSecurityCallbacks(this);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
@ -117,6 +142,7 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps)
{
nsresult rv;
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
LOG(("nsHttpConnection::Activate [this=%x trans=%x caps=%x]\n",
this, trans, caps));
@ -125,32 +151,24 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps)
// take ownership of the transaction
mTransaction = trans;
NS_ADDREF(mTransaction);
// set mKeepAlive according to what will be requested
mKeepAliveMask = mKeepAlive = (caps & NS_HTTP_ALLOW_KEEPALIVE);
// if we don't have a socket transport then create a new one
if (!mSocketTransport) {
rv = CreateTransport(caps);
if (NS_FAILED(rv))
goto loser;
}
// need to handle SSL proxy CONNECT if this is the first time.
if (mConnInfo->UsingSSL() && mConnInfo->UsingHttpProxy() && !mCompletedSSLConnect) {
rv = SetupSSLProxyConnect();
if (NS_FAILED(rv))
goto loser;
goto failed_activation;
}
// wait for the output stream to be readable
rv = mSocketOut->AsyncWait(this, 0, 0, nsnull);
if (NS_SUCCEEDED(rv))
return rv;
rv = OnOutputStreamReady(mSocketOut);
failed_activation:
if (NS_FAILED(rv)) {
mTransaction = nsnull;
}
loser:
NS_RELEASE(mTransaction);
return rv;
}
@ -159,7 +177,7 @@ nsHttpConnection::Close(nsresult reason)
{
LOG(("nsHttpConnection::Close [this=%x reason=%x]\n", this, reason));
NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
if (NS_FAILED(reason)) {
if (mSocketTransport) {
@ -193,8 +211,25 @@ nsHttpConnection::ProxyStartSSL()
PRBool
nsHttpConnection::CanReuse()
{
return IsKeepAlive() && (NowInSeconds() - mLastReadTime < mIdleTimeout)
&& IsAlive();
PRBool canReuse = IsKeepAlive() &&
(NowInSeconds() - mLastReadTime < mIdleTimeout) &&
IsAlive();
// An idle persistent connection should not have data waiting to be read
// before a request is sent. Data here is likely a 408 timeout response
// which we would deal with later on through the restart logic, but that
// path is more expensive than just closing the socket now. SSL check can
// be removed with fixing of 631801
PRUint32 dataSize;
if (canReuse && mSocketIn && !mConnInfo->UsingSSL() &&
NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) {
LOG(("nsHttpConnection::CanReuse %p %s"
"Socket not reusable because read data pending (%d) on it.\n",
this, mConnInfo->Host(), dataSize));
canReuse = PR_FALSE;
}
return canReuse;
}
PRUint32 nsHttpConnection::TimeToLive()
@ -387,7 +422,7 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans,
// processing a transaction pipeline until after the first HTTP/1.1
// response.
nsHttpTransaction *trans =
static_cast<nsHttpTransaction *>(mTransaction);
static_cast<nsHttpTransaction *>(mTransaction.get());
trans->SetSSLConnectFailed();
}
}
@ -395,6 +430,27 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans,
return NS_OK;
}
PRBool
nsHttpConnection::IsReused()
{
if (mIsReused)
return PR_TRUE;
if (!mConsiderReusedAfterInterval)
return PR_FALSE;
// ReusedAfter allows a socket to be consider reused only after a certain
// interval of time has passed
return (PR_IntervalNow() - mConsiderReusedAfterEpoch) >=
mConsiderReusedAfterInterval;
}
void
nsHttpConnection::SetIsReusedAfter(PRUint32 afterMilliseconds)
{
mConsiderReusedAfterEpoch = PR_IntervalNow();
mConsiderReusedAfterInterval = PR_MillisecondsToInterval(afterMilliseconds);
}
void
nsHttpConnection::GetSecurityInfo(nsISupports **secinfo)
{
@ -438,69 +494,6 @@ nsHttpConnection::ResumeRecv()
// nsHttpConnection <private>
//-----------------------------------------------------------------------------
nsresult
nsHttpConnection::CreateTransport(PRUint8 caps)
{
nsresult rv;
NS_PRECONDITION(!mSocketTransport, "unexpected");
nsCOMPtr<nsISocketTransportService> sts =
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
// configure the socket type based on the connection type requested.
const char* types[1];
if (mConnInfo->UsingSSL())
types[0] = "ssl";
else
types[0] = gHttpHandler->DefaultSocketType();
nsCOMPtr<nsISocketTransport> strans;
PRUint32 typeCount = (types[0] != nsnull);
rv = sts->CreateTransport(types, typeCount,
nsDependentCString(mConnInfo->Host()),
mConnInfo->Port(),
mConnInfo->ProxyInfo(),
getter_AddRefs(strans));
if (NS_FAILED(rv)) return rv;
PRUint32 tmpFlags = 0;
if (caps & NS_HTTP_REFRESH_DNS)
tmpFlags = nsISocketTransport::BYPASS_CACHE;
if (caps & NS_HTTP_LOAD_ANONYMOUS)
tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
strans->SetConnectionFlags(tmpFlags);
strans->SetQoSBits(gHttpHandler->GetQoSBits());
// NOTE: these create cyclical references, which we break inside
// nsHttpConnection::Close
rv = strans->SetEventSink(this, nsnull);
if (NS_FAILED(rv)) return rv;
rv = strans->SetSecurityCallbacks(this);
if (NS_FAILED(rv)) return rv;
// next open the socket streams
nsCOMPtr<nsIOutputStream> sout;
rv = strans->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
getter_AddRefs(sout));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIInputStream> sin;
rv = strans->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
getter_AddRefs(sin));
if (NS_FAILED(rv)) return rv;
mSocketTransport = strans;
mSocketIn = do_QueryInterface(sin);
mSocketOut = do_QueryInterface(sout);
return NS_OK;
}
void
nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
{
@ -515,9 +508,7 @@ nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
reason = NS_OK;
mTransaction->Close(reason);
NS_RELEASE(mTransaction);
mTransaction = 0;
mTransaction = nsnull;
if (NS_FAILED(reason))
Close(reason);
@ -728,7 +719,8 @@ nsHttpConnection::SetupSSLProxyConnect()
// NOTE: this cast is valid since this connection cannot be processing a
// transaction pipeline until after the first HTTP/1.1 response.
nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(mTransaction);
nsHttpTransaction *trans =
static_cast<nsHttpTransaction *>(mTransaction.get());
val = trans->RequestHead()->PeekHeader(nsHttp::Host);
if (val) {
@ -792,8 +784,8 @@ nsHttpConnection::OnInputStreamReady(nsIAsyncInputStream *in)
NS_IMETHODIMP
nsHttpConnection::OnOutputStreamReady(nsIAsyncOutputStream *out)
{
NS_ASSERTION(out == mSocketOut, "unexpected stream");
NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
NS_ABORT_IF_FALSE(out == mSocketOut, "unexpected socket");
// if the transaction was dropped...
if (!mTransaction) {
@ -835,14 +827,14 @@ nsHttpConnection::GetInterface(const nsIID &iid, void **result)
// the socket transport thread. If that weren't the case, then we'd
// have to worry about the possibility of mTransaction going away
// part-way through this function call. See CloseTransaction.
NS_ASSERTION(PR_GetCurrentThread() != gSocketThread, "wrong thread");
if (mTransaction) {
nsCOMPtr<nsIInterfaceRequestor> callbacks;
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
if (callbacks)
return callbacks->GetInterface(iid, result);
}
// NOTE - there is a bug here, the call to getinterface is proxied off the
// nss thread, not the ui thread as the above comment says. So there is
// indeed a chance of mTransaction going away. bug 615342
NS_ASSERTION(PR_GetCurrentThread() != gSocketThread, "wrong thread");
if (mCallbacks)
return mCallbacks->GetInterface(iid, result);
return NS_ERROR_NO_INTERFACE;
}

Просмотреть файл

@ -47,12 +47,14 @@
#include "nsCOMPtr.h"
#include "prlock.h"
#include "nsAutoPtr.h"
#include "prinrval.h"
#include "nsIStreamListener.h"
#include "nsISocketTransport.h"
#include "nsIAsyncInputStream.h"
#include "nsIAsyncOutputStream.h"
#include "nsIInterfaceRequestor.h"
#include "nsIEventTarget.h"
//-----------------------------------------------------------------------------
// nsHttpConnection - represents a connection to a HTTP server (or proxy)
@ -85,7 +87,10 @@ public:
// maxHangTime - limits the amount of time this connection can spend on a
// single transaction before it should no longer be kept
// alive. a value of 0xffff indicates no limit.
nsresult Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime);
nsresult Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime,
nsISocketTransport *, nsIAsyncInputStream *,
nsIAsyncOutputStream *, nsIInterfaceRequestor *,
nsIEventTarget *);
// Activate causes the given transaction to be processed on this
// connection. It fails if there is already an existing transaction.
@ -128,7 +133,9 @@ public:
void GetConnectionInfo(nsHttpConnectionInfo **ci) { NS_IF_ADDREF(*ci = mConnInfo); }
void GetSecurityInfo(nsISupports **);
PRBool IsPersistent() { return IsKeepAlive(); }
PRBool IsReused() { return mIsReused; }
PRBool IsReused();
void SetIsReusedAfter(PRUint32 afterMilliseconds);
void SetIdleTimeout(PRUint16 val) {mIdleTimeout = val;}
nsresult PushBack(const char *data, PRUint32 length) { NS_NOTREACHED("PushBack"); return NS_ERROR_UNEXPECTED; }
nsresult ResumeSend();
nsresult ResumeRecv();
@ -140,7 +147,6 @@ private:
// called to cause the underlying socket to start speaking SSL
nsresult ProxyStartSSL();
nsresult CreateTransport(PRUint8 caps);
nsresult OnTransactionDone(nsresult reason);
nsresult OnSocketWritable();
nsresult OnSocketReadable();
@ -161,12 +167,20 @@ private:
nsCOMPtr<nsIInputStream> mSSLProxyConnectStream;
nsCOMPtr<nsIInputStream> mRequestStream;
nsAHttpTransaction *mTransaction; // hard ref
nsHttpConnectionInfo *mConnInfo; // hard ref
// mTransaction only points to the HTTP Transaction callbacks if the
// transaction is open, otherwise it is null.
nsRefPtr<nsAHttpTransaction> mTransaction;
nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
nsCOMPtr<nsIEventTarget> mCallbackTarget;
nsRefPtr<nsHttpConnectionInfo> mConnInfo;
PRUint32 mLastReadTime;
PRUint16 mMaxHangTime; // max download time before dropping keep-alive status
PRUint16 mIdleTimeout; // value of keep-alive: timeout=
PRIntervalTime mConsiderReusedAfterInterval;
PRIntervalTime mConsiderReusedAfterEpoch;
PRPackedBool mKeepAlive;
PRPackedBool mKeepAliveMask;

Просмотреть файл

@ -400,13 +400,21 @@ nsHttpConnectionMgr::ProcessOneTransactionCB(nsHashKey *key, void *data, void *c
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::PurgeOneIdleConnectionCB(nsHashKey *key, void *data, void *closure)
nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(nsHashKey *key,
void *data, void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
nsConnectionEntry *ent = (nsConnectionEntry *) data;
if (ent->mIdleConns.Length() > 0) {
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);
@ -414,10 +422,8 @@ nsHttpConnectionMgr::PurgeOneIdleConnectionCB(nsHashKey *key, void *data, void *
self->mNumIdleConns--;
if (0 == self->mNumIdleConns)
self->StopPruneDeadConnectionsTimer();
return kHashEnumerateStop;
}
return kHashEnumerateNext;
return kHashEnumerateStop;
}
PRIntn
@ -474,6 +480,7 @@ nsHttpConnectionMgr::PruneDeadConnectionsCB(nsHashKey *key, void *data, void *cl
// if this entry is empty, then we can remove it.
if (ent->mIdleConns.Length() == 0 &&
ent->mActiveConns.Length() == 0 &&
ent->mHalfOpens.Length() == 0 &&
ent->mPendingQ.Length() == 0) {
LOG((" removing empty connection entry\n"));
delete ent;
@ -533,6 +540,10 @@ nsHttpConnectionMgr::ShutdownPassCB(nsHashKey *key, void *data, void *closure)
NS_RELEASE(trans);
}
// close all half open tcp connections
for (PRInt32 i = ((PRInt32) ent->mHalfOpens.Length()) - 1; i >= 0; i--)
ent->mHalfOpens[i]->Abandon();
delete ent;
return kHashEnumerateRemove;
}
@ -552,7 +563,7 @@ nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent)
nsHttpConnection *conn = nsnull;
for (i=0; i<count; ++i) {
trans = ent->mPendingQ[i];
GetConnection(ent, trans->Caps(), &conn);
GetConnection(ent, trans, &conn);
if (conn)
break;
}
@ -592,8 +603,8 @@ nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, PRUint8 cap
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 there are more active connections than the global 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;
@ -611,6 +622,11 @@ nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, PRUint8 cap
persistCount++;
}
// Add in the in-progress tcp connections, we will assume they are
// keepalive enabled.
totalCount += ent->mHalfOpens.Length();
persistCount += ent->mHalfOpens.Length();
LOG((" total=%d, persist=%d\n", totalCount, persistCount));
PRUint16 maxConns;
@ -631,18 +647,26 @@ nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, PRUint8 cap
}
void
nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, PRUint8 caps,
nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent,
nsHttpTransaction *trans,
nsHttpConnection **result)
{
LOG(("nsHttpConnectionMgr::GetConnection [ci=%s caps=%x]\n",
ent->mConnInfo->HashKey().get(), PRUint32(caps)));
ent->mConnInfo->HashKey().get(), PRUint32(trans->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
if (trans->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
@ -672,31 +696,68 @@ nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, PRUint8 caps,
// 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(PurgeOneIdleConnectionCB, this);
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"));
if (AtActiveConnectionLimit(ent, trans->Caps())) {
LOG(("nsHttpConnectionMgr::GetConnection [ci = %s]"
"at active connection limit - will queue\n",
ent->mConnInfo->HashKey().get()));
return;
}
conn = new nsHttpConnection();
if (!conn)
return;
NS_ADDREF(conn);
nsresult rv = conn->Init(ent->mConnInfo, mMaxRequestDelay);
if (NS_FAILED(rv)) {
NS_RELEASE(conn);
return;
}
nsresult rv = CreateTransport(ent, trans);
if (NS_FAILED(rv))
trans->Close(rv);
return;
}
// hold an owning ref to this connection
ent->mActiveConns.AppendElement(conn);
mNumActiveConns++;
NS_ADDREF(conn);
*result = conn;
}
void
nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn,
nsConnectionEntry *ent)
{
NS_ADDREF(conn);
ent->mActiveConns.AppendElement(conn);
mNumActiveConns++;
}
void
nsHttpConnectionMgr::StartedConnect()
{
mNumActiveConns++;
}
void
nsHttpConnectionMgr::RecvdConnect()
{
mNumActiveConns--;
}
nsresult
nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent,
nsHttpTransaction *trans)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
nsRefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans);
nsresult rv = sock->SetupPrimaryStreams();
NS_ENSURE_SUCCESS(rv, rv);
sock->SetupBackupTimer();
ent->mHalfOpens.AppendElement(sock);
return NS_OK;
}
nsresult
nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
nsAHttpTransaction *trans,
@ -718,11 +779,6 @@ nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
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);
@ -795,6 +851,8 @@ nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
nsresult
nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
// 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
@ -837,18 +895,9 @@ nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
// 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);
GetConnection(ent, trans, &conn);
nsresult rv;
if (!conn) {
@ -998,13 +1047,22 @@ nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param)
NS_ASSERTION(ent, "no connection entry");
if (ent) {
ent->mActiveConns.RemoveElement(conn);
mNumActiveConns--;
// 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
@ -1018,8 +1076,6 @@ nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param)
LOG((" connection cannot be reused; closing connection\n"));
// make sure the connection is closed and release our reference.
conn->Close(NS_ERROR_ABORT);
nsHttpConnection *temp = conn;
NS_RELEASE(temp);
}
}
@ -1130,6 +1186,323 @@ nsHttpConnectionMgr::nsConnectionHandle::PushBack(const char *buf, PRUint32 bufL
return mConn->PushBack(buf, bufLen);
}
//////////////////////// nsHalfOpenSocket
NS_IMPL_THREADSAFE_ISUPPORTS4(nsHttpConnectionMgr::nsHalfOpenSocket,
nsIOutputStreamCallback,
nsITransportEventSink,
nsIInterfaceRequestor,
nsITimerCallback)
nsHttpConnectionMgr::
nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent,
nsHttpTransaction *trans)
: mEnt(ent),
mTransaction(trans)
{
NS_ABORT_IF_FALSE(ent && trans, "constructor with null arguments");
LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s]\n",
this, trans, ent->mConnInfo->Host()));
}
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) {
PRInt32 index = mEnt->mHalfOpens.IndexOf(this);
NS_ABORT_IF_FALSE(index != -1, "half open complete but no item");
mEnt->mHalfOpens.RemoveElementAt(index);
}
}
nsresult
nsHttpConnectionMgr::
nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport,
nsIAsyncInputStream **instream,
nsIAsyncOutputStream **outstream)
{
nsresult rv;
const char* types[1];
types[0] = (mEnt->mConnInfo->UsingSSL()) ?
"ssl" : gHttpHandler->DefaultSocketType();
PRUint32 typeCount = (types[0] != nsnull);
nsCOMPtr<nsISocketTransport> socketTransport;
nsCOMPtr<nsISocketTransportService> sts;
sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = sts->CreateTransport(types, typeCount,
nsDependentCString(mEnt->mConnInfo->Host()),
mEnt->mConnInfo->Port(),
mEnt->mConnInfo->ProxyInfo(),
getter_AddRefs(socketTransport));
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 tmpFlags = 0;
if (mTransaction->Caps() & NS_HTTP_REFRESH_DNS)
tmpFlags = nsISocketTransport::BYPASS_CACHE;
if (mTransaction->Caps() & NS_HTTP_LOAD_ANONYMOUS)
tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
socketTransport->SetConnectionFlags(tmpFlags);
socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
rv = socketTransport->SetEventSink(this, nsnull);
NS_ENSURE_SUCCESS(rv, rv);
rv = socketTransport->SetSecurityCallbacks(this);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOutputStream> sout;
rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED,
0, 0,
getter_AddRefs(sout));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> sin;
rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED,
0, 0,
getter_AddRefs(sin));
NS_ENSURE_SUCCESS(rv, rv);
socketTransport.forget(transport);
CallQueryInterface(sin, instream);
CallQueryInterface(sout, outstream);
rv = (*outstream)->AsyncWait(this, 0, 0, nsnull);
if (NS_SUCCEEDED(rv))
gHttpHandler->ConnMgr()->StartedConnect();
return rv;
}
nsresult
nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams()
{
nsresult rv = SetupStreams(getter_AddRefs(mSocketTransport),
getter_AddRefs(mStreamIn),
getter_AddRefs(mStreamOut));
LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]",
this, mEnt->mConnInfo->Host(), rv));
if (NS_FAILED(rv)) {
if (mStreamOut)
mStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
mStreamOut = nsnull;
mStreamIn = nsnull;
mSocketTransport = nsnull;
}
return rv;
}
nsresult
nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
{
nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
getter_AddRefs(mBackupStreamIn),
getter_AddRefs(mBackupStreamOut));
LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
this, mEnt->mConnInfo->Host(), rv));
if (NS_FAILED(rv)) {
if (mBackupStreamOut)
mBackupStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
mBackupStreamOut = nsnull;
mBackupStreamIn = nsnull;
mBackupTransport = nsnull;
}
return rv;
}
void
nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer()
{
PRUint16 timeout = gHttpHandler->GetIdleSynTimeout();
NS_ABORT_IF_FALSE(!mSynTimer, "timer already initd");
if (timeout) {
// Setup the timer that will establish a backup socket
// if we do not get a writable event on the main one.
// We do this because a lost SYN takes a very long time
// to repair at the TCP level.
//
// Failure to setup the timer is something we can live with,
// so don't return an error in that case.
nsresult rv;
mSynTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv))
mSynTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
}
}
void
nsHttpConnectionMgr::nsHalfOpenSocket::Abandon()
{
LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s]",
this, mEnt->mConnInfo->Host()));
nsRefPtr<nsHalfOpenSocket> deleteProtector(this);
if (mStreamOut) {
gHttpHandler->ConnMgr()->RecvdConnect();
mStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
mStreamOut = nsnull;
}
if (mBackupStreamOut) {
gHttpHandler->ConnMgr()->RecvdConnect();
mBackupStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
mBackupStreamOut = nsnull;
}
if (mSynTimer) {
mSynTimer->Cancel();
mSynTimer = nsnull;
}
mEnt = nsnull;
}
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");
mSynTimer = nsnull;
if (!gHttpHandler->ConnMgr()->
AtActiveConnectionLimit(mEnt, mTransaction->Caps())) {
SetupBackupStreams();
}
return NS_OK;
}
// method for nsIAsyncOutputStreamCallback
NS_IMETHODIMP
nsHttpConnectionMgr::
nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
NS_ABORT_IF_FALSE(out == mStreamOut ||
out == mBackupStreamOut, "stream mismatch");
LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n",
this, mEnt->mConnInfo->Host(),
out == mStreamOut ? "primary" : "backup"));
PRInt32 index;
nsresult rv;
gHttpHandler->ConnMgr()->RecvdConnect();
// If the syntimer is still armed, we can cancel it because no backup
// socket should be formed at this point
if (mSynTimer) {
NS_ABORT_IF_FALSE (out == mStreamOut, "timer for non existant stream");
LOG(("nsHalfOpenSocket::OnOutputStreamReady "
"Backup connection timer canceled\n"));
mSynTimer->Cancel();
mSynTimer = nsnull;
}
// assign the new socket to the http connection
nsRefPtr<nsHttpConnection> conn = new nsHttpConnection();
LOG(("nsHalfOpenSocket::OnOutputStreamReady "
"Created new nshttpconnection %p\n", conn));
nsCOMPtr<nsIInterfaceRequestor> callbacks;
nsCOMPtr<nsIEventTarget> callbackTarget;
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks),
getter_AddRefs(callbackTarget));
if (out == mStreamOut) {
rv = conn->Init(mEnt->mConnInfo,
gHttpHandler->ConnMgr()->mMaxRequestDelay,
mSocketTransport, mStreamIn, mStreamOut,
callbacks, callbackTarget);
// The nsHttpConnection object now owns these streams and sockets
mStreamOut = nsnull;
mStreamIn = nsnull;
mSocketTransport = nsnull;
}
else {
rv = conn->Init(mEnt->mConnInfo,
gHttpHandler->ConnMgr()->mMaxRequestDelay,
mBackupTransport, mBackupStreamIn, mBackupStreamOut,
callbacks, callbackTarget);
// The nsHttpConnection object now owns these streams and sockets
mBackupStreamOut = nsnull;
mBackupStreamIn = nsnull;
mBackupTransport = nsnull;
}
if (NS_FAILED(rv)) {
LOG(("nsHalfOpenSocket::OnOutputStreamReady "
"conn->init (%p) failed %x\n", conn, rv));
return rv;
}
// if this is still in the pending list, remove it and dispatch it
index = mEnt->mPendingQ.IndexOf(mTransaction);
if (index != -1) {
mEnt->mPendingQ.RemoveElementAt(index);
nsHttpTransaction *temp = mTransaction;
NS_RELEASE(temp);
gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, mTransaction,
mTransaction->Caps(),
conn);
}
else {
// this transaction was dispatched off the pending q before all the
// sockets established themselves.
// We need to establish a small non-zero idle timeout so the connection
// mgr perceives this socket as suitable for persistent connection reuse
conn->SetIdleTimeout(NS_MIN((PRUint16) 5, gHttpHandler->IdleTimeout()));
// After about 1 second allow for the possibility of restarting a
// transaction due to server close. Keep at sub 1 second as that is the
// minimum granularity we can expect a server to be timing out with.
conn->SetIsReusedAfter(950);
NS_ADDREF(conn); // because onmsg*() expects to drop a reference
gHttpHandler->ConnMgr()->OnMsgReclaimConnection(NS_OK, conn);
}
return rv;
}
// method for nsITransportEventSink
NS_IMETHODIMP
nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
nsresult status,
PRUint64 progress,
PRUint64 progressMax)
{
if (mTransaction)
mTransaction->OnTransportStatus(status, progress);
return NS_OK;
}
// method for nsIInterfaceRequestor
NS_IMETHODIMP
nsHttpConnectionMgr::nsHalfOpenSocket::GetInterface(const nsIID &iid,
void **result)
{
if (mTransaction) {
nsCOMPtr<nsIInterfaceRequestor> callbacks;
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks), nsnull);
if (callbacks)
return callbacks->GetInterface(iid, result);
}
return NS_ERROR_NO_INTERFACE;
}
PRBool
nsHttpConnectionMgr::nsConnectionHandle::LastTransactionExpectedNoContent()
{

Просмотреть файл

@ -47,6 +47,7 @@
#include "nsHashtable.h"
#include "nsAutoPtr.h"
#include "prmon.h"
#include "nsISocketTransportService.h"
#include "nsIObserver.h"
#include "nsITimer.h"
@ -139,7 +140,8 @@ public:
private:
virtual ~nsHttpConnectionMgr();
class nsHalfOpenSocket;
// nsConnectionEntry
//
// mCT maps connection info hash key to nsConnectionEntry object, which
@ -159,6 +161,7 @@ private:
nsTArray<nsHttpTransaction*> mPendingQ; // pending transaction queue
nsTArray<nsHttpConnection*> mActiveConns; // active connections
nsTArray<nsHttpConnection*> mIdleConns; // idle persistent connections
nsTArray<nsHalfOpenSocket*> mHalfOpens;
};
// nsConnectionHandle
@ -182,6 +185,48 @@ private:
nsHttpConnection *mConn;
};
// nsHalfOpenSocket is used to hold the state of an opening TCP socket
// while we wait for it to establish and bind it to a connection
class nsHalfOpenSocket : public nsIOutputStreamCallback,
public nsITransportEventSink,
public nsIInterfaceRequestor,
public nsITimerCallback
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOUTPUTSTREAMCALLBACK
NS_DECL_NSITRANSPORTEVENTSINK
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSITIMERCALLBACK
nsHalfOpenSocket(nsConnectionEntry *ent,
nsHttpTransaction *trans);
~nsHalfOpenSocket();
nsresult SetupStreams(nsISocketTransport **,
nsIAsyncInputStream **,
nsIAsyncOutputStream **);
nsresult SetupPrimaryStreams();
nsresult SetupBackupStreams();
void SetupBackupTimer();
void Abandon();
private:
nsConnectionEntry *mEnt;
nsRefPtr<nsHttpTransaction> mTransaction;
nsCOMPtr<nsISocketTransport> mSocketTransport;
nsCOMPtr<nsIAsyncOutputStream> mStreamOut;
nsCOMPtr<nsIAsyncInputStream> mStreamIn;
// for syn retry
nsCOMPtr<nsITimer> mSynTimer;
nsCOMPtr<nsISocketTransport> mBackupTransport;
nsCOMPtr<nsIAsyncOutputStream> mBackupStreamOut;
nsCOMPtr<nsIAsyncInputStream> mBackupStreamIn;
};
friend class nsHalfOpenSocket;
//-------------------------------------------------------------------------
// NOTE: these members may be accessed from any thread (use mMonitor)
//-------------------------------------------------------------------------
@ -206,19 +251,24 @@ private:
//-------------------------------------------------------------------------
static PRIntn ProcessOneTransactionCB(nsHashKey *, void *, void *);
static PRIntn PurgeOneIdleConnectionCB(nsHashKey *, void *, void *);
static PRIntn PruneDeadConnectionsCB(nsHashKey *, void *, void *);
static PRIntn ShutdownPassCB(nsHashKey *, void *, void *);
static PRIntn PurgeExcessIdleConnectionsCB(nsHashKey *, void *, void *);
PRBool ProcessPendingQForEntry(nsConnectionEntry *);
PRBool AtActiveConnectionLimit(nsConnectionEntry *, PRUint8 caps);
void GetConnection(nsConnectionEntry *, PRUint8 caps, nsHttpConnection **);
void GetConnection(nsConnectionEntry *, nsHttpTransaction *,
nsHttpConnection **);
nsresult DispatchTransaction(nsConnectionEntry *, nsAHttpTransaction *,
PRUint8 caps, nsHttpConnection *);
PRBool BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **);
nsresult ProcessNewTransaction(nsHttpTransaction *);
nsresult EnsureSocketThreadTargetIfOnline();
nsresult CreateTransport(nsConnectionEntry *, nsHttpTransaction *);
void AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
void StartedConnect();
void RecvdConnect();
// message handlers have this signature
typedef void (nsHttpConnectionMgr:: *nsConnEventHandler)(PRInt32, void *);

Просмотреть файл

@ -179,6 +179,7 @@ nsHttpHandler::nsHttpHandler()
, mIdleTimeout(10)
, mMaxRequestAttempts(10)
, mMaxRequestDelay(10)
, mIdleSynTimeout(250)
, mMaxConnections(24)
, mMaxConnectionsPerServer(8)
, mMaxPersistentConnectionsPerServer(2)
@ -926,6 +927,12 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
mRedirectionLimit = (PRUint8) NS_CLAMP(val, 0, 0xff);
}
if (PREF_CHANGED(HTTP_PREF("connection-retry-timeout"))) {
rv = prefs->GetIntPref(HTTP_PREF("connection-retry-timeout"), &val);
if (NS_SUCCEEDED(rv))
mIdleSynTimeout = (PRUint16) NS_CLAMP(val, 0, 3000);
}
if (PREF_CHANGED(HTTP_PREF("version"))) {
nsXPIDLCString httpVersion;
prefs->GetCharPref(HTTP_PREF("version"), getter_Copies(httpVersion));

Просмотреть файл

@ -107,6 +107,7 @@ public:
nsIIDNService *IDNConverter() { return mIDNConverter; }
PRUint32 PhishyUserPassLength() { return mPhishyUserPassLength; }
PRUint8 GetQoSBits() { return mQoSBits; }
PRUint16 GetIdleSynTimeout() { return mIdleSynTimeout; }
PRBool IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; }
@ -263,6 +264,7 @@ private:
PRUint16 mIdleTimeout;
PRUint16 mMaxRequestAttempts;
PRUint16 mMaxRequestDelay;
PRUint16 mIdleSynTimeout;
PRUint16 mMaxConnections;
PRUint8 mMaxConnectionsPerServer;

Просмотреть файл

@ -324,16 +324,20 @@ nsHttpPipeline::SetConnection(nsAHttpConnection *conn)
}
void
nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result)
nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result,
nsIEventTarget **target)
{
NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
// return security callbacks from first request
nsAHttpTransaction *trans = Request(0);
if (trans)
trans->GetSecurityCallbacks(result);
else
trans->GetSecurityCallbacks(result, target);
else {
*result = nsnull;
if (target)
*target = nsnull;
}
}
void

Просмотреть файл

@ -333,9 +333,12 @@ nsHttpTransaction::SetConnection(nsAHttpConnection *conn)
}
void
nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb)
nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb,
nsIEventTarget **target)
{
NS_IF_ADDREF(*cb = mCallbacks);
if (target)
NS_IF_ADDREF(*target = mConsumerTarget);
}
void