gecko/netwerk/protocol/http/nsHttpConnectionMgr.cpp
2011-12-02 10:28:56 -05:00

2015 lines
65 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 "nsNetCID.h"
#include "nsCOMPtr.h"
#include "nsNetUtil.h"
#include "nsIServiceManager.h"
#include "nsIObserverService.h"
#include "nsIDNSRecord.h"
#include "nsIDNSService.h"
#include "nsICancelable.h"
using namespace mozilla;
// 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)
, mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
, mMaxConns(0)
, mMaxConnsPerHost(0)
, mMaxConnsPerProxy(0)
, mMaxPersistConnsPerHost(0)
, mMaxPersistConnsPerProxy(0)
, mIsShuttingDown(false)
, mNumActiveConns(0)
, mNumIdleConns(0)
, mTimeOfNextWakeUp(LL_MAXUINT)
{
LOG(("Creating nsHttpConnectionMgr @%x\n", this));
mCT.Init();
}
nsHttpConnectionMgr::~nsHttpConnectionMgr()
{
LOG(("Destroying nsHttpConnectionMgr @%x\n", this));
}
nsresult
nsHttpConnectionMgr::EnsureSocketThreadTargetIfOnline()
{
nsresult rv;
nsCOMPtr<nsIEventTarget> sts;
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
if (NS_SUCCEEDED(rv)) {
bool offline = true;
ioService->GetOffline(&offline);
if (!offline) {
sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
}
}
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
// do nothing if already initialized or if we've shut down
if (mSocketThreadTarget || mIsShuttingDown)
return NS_OK;
mSocketThreadTarget = sts;
return rv;
}
nsresult
nsHttpConnectionMgr::Init(PRUint16 maxConns,
PRUint16 maxConnsPerHost,
PRUint16 maxConnsPerProxy,
PRUint16 maxPersistConnsPerHost,
PRUint16 maxPersistConnsPerProxy,
PRUint16 maxRequestDelay,
PRUint16 maxPipelinedRequests)
{
LOG(("nsHttpConnectionMgr::Init\n"));
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mSpdyPreferredHash.Init();
mMaxConns = maxConns;
mMaxConnsPerHost = maxConnsPerHost;
mMaxConnsPerProxy = maxConnsPerProxy;
mMaxPersistConnsPerHost = maxPersistConnsPerHost;
mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
mMaxRequestDelay = maxRequestDelay;
mMaxPipelinedRequests = maxPipelinedRequests;
mIsShuttingDown = false;
}
return EnsureSocketThreadTargetIfOnline();
}
nsresult
nsHttpConnectionMgr::Shutdown()
{
LOG(("nsHttpConnectionMgr::Shutdown\n"));
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
// 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.
mIsShuttingDown = true;
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)
{
// This object doesn't get reinitialized if the offline state changes, so our
// socket thread target might be uninitialized if we were offline when this
// object was being initialized, and we go online later on. This call takes
// care of initializing the socket thread target if that's the case.
EnsureSocketThreadTargetIfOnline();
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
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::ConditionallyStopPruneDeadConnectionsTimer()
{
// Leave the timer in place if there are connections that potentially
// need management
if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
return;
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::ClosePersistentConnections()
{
return PostEvent(&nsHttpConnectionMgr::OnMsgClosePersistentConnections);
}
nsresult
nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
{
// This object doesn't get reinitialized if the offline state changes, so our
// socket thread target might be uninitialized if we were offline when this
// object was being initialized, and we go online later on. This call takes
// care of initializing the socket thread target if that's the case.
EnsureSocketThreadTargetIfOnline();
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
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) {
nsConnectionEntry *ent = mCT.Get(ci->HashKey());
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;
}
nsresult
nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p",
this, conn));
nsHttpConnectionInfo *ci = conn->ConnectionInfo();
if (!ci)
return NS_ERROR_UNEXPECTED;
nsConnectionEntry *ent = mCT.Get(ci->HashKey());
if (!ent || !ent->mIdleConns.RemoveElement(conn))
return NS_ERROR_UNEXPECTED;
conn->Close(NS_ERROR_ABORT);
NS_RELEASE(conn);
mNumIdleConns--;
ConditionallyStopPruneDeadConnectionsTimer();
return NS_OK;
}
void
nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn,
bool usingSpdy)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
nsConnectionEntry *ent = mCT.Get(conn->ConnectionInfo()->HashKey());
NS_ABORT_IF_FALSE(ent, "no connection entry");
if (!ent)
return;
ent->mTestedSpdy = true;
if (!usingSpdy) {
if (ent->mUsingSpdy)
conn->DontReuse();
return;
}
ent->mUsingSpdy = true;
PRUint32 ttl = conn->TimeToLive();
PRUint64 timeOfExpire = NowInSeconds() + ttl;
if (!mTimer || timeOfExpire < mTimeOfNextWakeUp)
PruneDeadConnectionsAfter(ttl);
nsConnectionEntry *preferred = GetSpdyPreferred(ent->mDottedDecimalAddress);
LOG(("ReportSpdyConnection %s %s ent=%p ispreferred=%d\n",
ent->mConnInfo->Host(), ent->mDottedDecimalAddress.get(),
ent, preferred));
if (!preferred) {
ent->mSpdyPreferred = true;
SetSpdyPreferred(ent->mDottedDecimalAddress, ent);
ent->mSpdyRedir = false;
}
else if (preferred != ent) {
// A different hostname is the preferred spdy host for this
// IP address.
ent->mSpdyRedir = true;
conn->DontReuse();
}
ProcessSpdyPendingQ();
}
nsHttpConnectionMgr::nsConnectionEntry *
nsHttpConnectionMgr::GetSpdyPreferred(nsACString &aDottedDecimal)
{
if (!gHttpHandler->IsSpdyEnabled() ||
!gHttpHandler->CoalesceSpdy() ||
aDottedDecimal.IsEmpty())
return nsnull;
return mSpdyPreferredHash.Get(aDottedDecimal);
}
void
nsHttpConnectionMgr::SetSpdyPreferred(nsACString &aDottedDecimal,
nsConnectionEntry *ent)
{
if (!gHttpHandler->CoalesceSpdy())
return;
if (aDottedDecimal.IsEmpty())
return;
mSpdyPreferredHash.Put(aDottedDecimal, ent);
}
void
nsHttpConnectionMgr::RemoveSpdyPreferred(nsACString &aDottedDecimal)
{
if (!gHttpHandler->CoalesceSpdy())
return;
if (aDottedDecimal.IsEmpty())
return;
mSpdyPreferredHash.Remove(aDottedDecimal);
}
//-----------------------------------------------------------------------------
// enumeration callbacks
PLDHashOperator
nsHttpConnectionMgr::ProcessOneTransactionCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
if (self->ProcessPendingQForEntry(ent))
return PL_DHASH_STOP;
return PL_DHASH_NEXT;
}
// 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
PLDHashOperator
nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
while (self->mNumIdleConns + self->mNumActiveConns + 1 >= self->mMaxConns) {
if (!ent->mIdleConns.Length()) {
// There are no idle conns left in this connection entry
return PL_DHASH_NEXT;
}
nsHttpConnection *conn = ent->mIdleConns[0];
ent->mIdleConns.RemoveElementAt(0);
conn->Close(NS_ERROR_ABORT);
NS_RELEASE(conn);
self->mNumIdleConns--;
self->ConditionallyStopPruneDeadConnectionsTimer();
}
return PL_DHASH_STOP;
}
PLDHashOperator
nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
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.
bool liveConnections = false;
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 = NS_MIN(timeToNextExpire, conn->TimeToLive());
liveConnections = true;
}
}
}
if (ent->mUsingSpdy) {
for (PRUint32 index = 0; index < ent->mActiveConns.Length(); ++index) {
nsHttpConnection *conn = ent->mActiveConns[index];
if (conn->UsingSpdy()) {
if (!conn->CanReuse()) {
// marking it dont reuse will create an active tear down if
// the spdy session is idle.
conn->DontReuse();
}
else {
timeToNextExpire = NS_MIN(timeToNextExpire,
conn->TimeToLive());
liveConnections = true;
}
}
}
}
// If time to next expire found is shorter than time to next wake-up, we need to
// change the time for next wake-up.
if (liveConnections) {
PRUint32 now = NowInSeconds();
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 {
self->ConditionallyStopPruneDeadConnectionsTimer();
}
#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->mHalfOpens.Length() == 0 &&
ent->mPendingQ.Length() == 0 &&
((!ent->mTestedSpdy && !ent->mUsingSpdy) ||
!gHttpHandler->IsSpdyEnabled() ||
self->mCT.Count() > 300)) {
LOG((" removing empty connection entry\n"));
return PL_DHASH_REMOVE;
}
// else, use this opportunity to compact our arrays...
ent->mIdleConns.Compact();
ent->mActiveConns.Compact();
ent->mPendingQ.Compact();
return PL_DHASH_NEXT;
}
PLDHashOperator
nsHttpConnectionMgr::ShutdownPassCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
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.
self->ConditionallyStopPruneDeadConnectionsTimer();
// close all pending transactions
while (ent->mPendingQ.Length()) {
trans = ent->mPendingQ[0];
ent->mPendingQ.RemoveElementAt(0);
trans->Close(NS_ERROR_ABORT);
NS_RELEASE(trans);
}
// close all half open tcp connections
for (PRInt32 i = ((PRInt32) ent->mHalfOpens.Length()) - 1; i >= 0; i--)
ent->mHalfOpens[i]->Abandon();
return PL_DHASH_REMOVE;
}
//-----------------------------------------------------------------------------
bool
nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n",
ent->mConnInfo->HashKey().get()));
if (gHttpHandler->IsSpdyEnabled())
ProcessSpdyPendingQ(ent);
PRUint32 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];
// When this transaction has already established a half-open
// connection, we want to prevent any duplicate half-open
// connections from being established and bound to this
// transaction. Allow only use of an idle persistent connection
// (if found) for transactions referred by a half-open connection.
bool alreadyHalfOpen = false;
for (PRInt32 j = 0; j < ((PRInt32) ent->mHalfOpens.Length()); j++) {
if (ent->mHalfOpens[j]->Transaction() == trans) {
alreadyHalfOpen = true;
break;
}
}
GetConnection(ent, trans, alreadyHalfOpen, &conn);
if (conn)
break;
// Check to see if a pending transaction was dispatched with the
// coalesce logic
if (count != ent->mPendingQ.Length()) {
count = ent->mPendingQ.Length();
i = 0;
}
}
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 true;
}
}
return 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
bool
nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, PRUint8 caps)
{
nsHttpConnectionInfo *ci = ent->mConnInfo;
LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x]\n",
ci->HashKey().get(), caps));
// update maxconns if potentially limited by the max socket count
// this requires a dynamic reduction in the max socket count to a point
// lower than the max-connections pref.
PRUint32 maxSocketCount = gHttpHandler->MaxSocketCount();
if (mMaxConns > maxSocketCount) {
mMaxConns = maxSocketCount;
LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u",
this, mMaxConns));
}
// 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 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++;
}
// 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;
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::ClosePersistentConnections(nsConnectionEntry *ent)
{
LOG(("nsHttpConnectionMgr::ClosePersistentConnections [ci=%s]\n",
ent->mConnInfo->HashKey().get()));
while (ent->mIdleConns.Length()) {
nsHttpConnection *conn = ent->mIdleConns[0];
ent->mIdleConns.RemoveElementAt(0);
mNumIdleConns--;
conn->Close(NS_ERROR_ABORT);
NS_RELEASE(conn);
}
PRInt32 activeCount = ent->mActiveConns.Length();
for (PRInt32 i=0; i < activeCount; i++)
ent->mActiveConns[i]->DontReuse();
}
PLDHashOperator
nsHttpConnectionMgr::ClosePersistentConnectionsCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = static_cast<nsHttpConnectionMgr *>(closure);
self->ClosePersistentConnections(ent);
return PL_DHASH_NEXT;
}
void
nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent,
nsHttpTransaction *trans,
bool onlyReusedConnection,
nsHttpConnection **result)
{
LOG(("nsHttpConnectionMgr::GetConnection [ci=%s caps=%x]\n",
ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps())));
// First, see if an existing connection can be used - either an idle
// persistent connection or an active spdy session 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;
bool addConnToActiveList = true;
if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) {
conn = GetSpdyPreferredConn(ent);
if (conn)
addConnToActiveList = false;
// 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));
conn->EndIdleMonitoring();
}
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.
ConditionallyStopPruneDeadConnectionsTimer();
}
}
if (!conn) {
// If the onlyReusedConnection parameter is TRUE, then GetConnection()
// does not create new transports under any circumstances.
if (onlyReusedConnection)
return;
// If this is a possible Spdy connection we need to limit the number of
// connections outstanding to 1 while we wait for the spdy/https
// ReportSpdyConnection()
if (gHttpHandler->IsSpdyEnabled() &&
ent->mConnInfo->UsingSSL() &&
!ent->mConnInfo->UsingHttpProxy())
{
nsConnectionEntry *preferred =
GetSpdyPreferred(ent->mDottedDecimalAddress);
if (preferred)
ent = preferred;
if ((!ent->mTestedSpdy || ent->mUsingSpdy) &&
(ent->mSpdyRedir || ent->mHalfOpens.Length() ||
ent->mActiveConns.Length()))
return;
}
// 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, trans->Caps())) {
LOG(("nsHttpConnectionMgr::GetConnection [ci = %s]"
"at active connection limit - will queue\n",
ent->mConnInfo->HashKey().get()));
return;
}
LOG(("nsHttpConnectionMgr::GetConnection Open Connection "
"%s %s ent=%p spdy=%d",
ent->mConnInfo->Host(), ent->mDottedDecimalAddress.get(),
ent, ent->mUsingSpdy));
nsresult rv = CreateTransport(ent, trans);
if (NS_FAILED(rv))
trans->Close(rv);
return;
}
if (addConnToActiveList) {
// 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);
ent->mHalfOpens.AppendElement(sock);
return NS_OK;
}
nsresult
nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
nsHttpTransaction *aTrans,
PRUint8 caps,
nsHttpConnection *conn)
{
LOG(("nsHttpConnectionMgr::DispatchTransaction [ci=%s trans=%x caps=%x conn=%x]\n",
ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
nsresult rv;
PRInt32 priority = aTrans->Priority();
if (conn->UsingSpdy()) {
rv = conn->Activate(aTrans, caps, priority);
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch");
return rv;
}
nsConnectionHandle *handle = new nsConnectionHandle(conn);
if (!handle)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(handle);
nsHttpPipeline *pipeline = nsnull;
nsAHttpTransaction *trans = aTrans;
if (conn->SupportsPipelining() && (caps & NS_HTTP_ALLOW_PIPELINING)) {
LOG((" looking to build pipeline...\n"));
if (BuildPipeline(ent, trans, &pipeline))
trans = pipeline;
}
// give the transaction the indirect reference to the connection.
trans->SetConnection(handle);
rv = conn->Activate(trans, caps, priority);
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;
}
bool
nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
nsAHttpTransaction *firstTrans,
nsHttpPipeline **result)
{
if (mMaxPipelinedRequests < 2)
return 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 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 false;
LOG((" pipelined %u transactions\n", numAdded));
NS_ADDREF(*result = pipeline);
return true;
}
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
// 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");
nsConnectionEntry *ent = mCT.Get(ci->HashKey());
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(ci->HashKey(), ent);
}
// SPDY coalescing of hostnames means we might redirect from this
// connection entry onto the preferred one.
nsConnectionEntry *preferredEntry =
GetSpdyPreferred(ent->mDottedDecimalAddress);
if (preferredEntry) {
LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
"redirected via coalescing from %s to %s\n", trans,
ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host()));
ent = preferredEntry;
}
// If we are doing a force reload then close out any existing conns
// to this host so that changes in DNS, LBs, etc.. are reflected
if (caps & NS_HTTP_CLEAR_KEEPALIVES)
ClosePersistentConnections(ent);
// Check if the transaction already has a sticky reference to a connection.
// If so, then we can just use it directly by transferring its reference
// to the new connection var instead of calling GetConnection() to search
// for an available one.
nsAHttpConnection *wrappedConnection = trans->Connection();
nsHttpConnection *conn;
conn = wrappedConnection ? wrappedConnection->TakeHttpConnection() : nsnull;
if (conn) {
NS_ASSERTION(caps & NS_HTTP_STICKY_CONNECTION, "unexpected caps");
trans->SetConnection(nsnull);
}
else
GetConnection(ent, trans, false, &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::ProcessSpdyPendingQ(nsConnectionEntry *ent)
{
nsHttpConnection *conn = GetSpdyPreferredConn(ent);
if (!conn)
return;
for (PRInt32 index = ent->mPendingQ.Length() - 1;
index >= 0 && conn->CanDirectlyActivate();
--index) {
nsHttpTransaction *trans = ent->mPendingQ[index];
if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
trans->Caps() & NS_HTTP_DISALLOW_SPDY)
continue;
ent->mPendingQ.RemoveElementAt(index);
nsresult rv2 = DispatchTransaction(ent, trans, trans->Caps(), conn);
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv2), "Dispatch SPDY Transaction");
NS_RELEASE(trans);
}
}
PLDHashOperator
nsHttpConnectionMgr::ProcessSpdyPendingQCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
self->ProcessSpdyPendingQ(ent);
return PL_DHASH_NEXT;
}
void
nsHttpConnectionMgr::ProcessSpdyPendingQ()
{
mCT.Enumerate(ProcessSpdyPendingQCB, this);
}
nsHttpConnection *
nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
NS_ABORT_IF_FALSE(ent, "no connection entry");
nsConnectionEntry *preferred = GetSpdyPreferred(ent->mDottedDecimalAddress);
// this entry is spdy-enabled if it is a redirect to another spdy host
if (preferred && preferred != ent) {
ent->mUsingSpdy = true;
ent->mSpdyRedir = true;
}
else {
ent->mSpdyRedir = false;
// don't clear usingSpdy, that will be reset in ReportSpdyConnection
// if it no longer applies
}
if (!preferred)
preferred = ent;
nsHttpConnection *conn = nsnull;
if (preferred->mUsingSpdy) {
for (PRUint32 index = 0;
index < preferred->mActiveConns.Length();
++index) {
if (preferred->mActiveConns[index]->CanDirectlyActivate()) {
conn = preferred->mActiveConns[index];
break;
}
}
}
return conn;
}
//-----------------------------------------------------------------------------
void
nsHttpConnectionMgr::OnMsgShutdown(PRInt32, void *)
{
LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
mCT.Enumerate(ShutdownPassCB, this);
// signal shutdown complete
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
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();
nsConnectionEntry *ent = mCT.Get(ci->HashKey());
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();
nsConnectionEntry *ent = mCT.Get(ci->HashKey());
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.
nsConnectionEntry *ent = mCT.Get(ci->HashKey());
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;
// check canreuse() for all idle connections plus any active connections on
// connection entries that are using spdy.
if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
mCT.Enumerate(PruneDeadConnectionsCB, this);
}
void
nsHttpConnectionMgr::OnMsgClosePersistentConnections(PRInt32, void *)
{
LOG(("nsHttpConnectionMgr::OnMsgClosePersistentConnections\n"));
mCT.Enumerate(ClosePersistentConnectionsCB, 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);
nsConnectionEntry *ent = mCT.Get(ci->HashKey());
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->mUsingSpdy)
conn->DontReuse();
if (ent->mActiveConns.RemoveElement(conn)) {
nsHttpConnection *temp = conn;
NS_RELEASE(temp);
mNumActiveConns--;
}
if (conn->CanReuse()) {
LOG((" adding connection to idle list\n"));
// Keep The idle connection list sorted with the connections that
// have moved the largest data pipelines at the front because these
// connections have the largest cwnds on the server.
// The linear search is ok here because the number of idleconns
// in a single entry is generally limited to a small number (i.e. 6)
PRUint32 idx;
for (idx = 0; idx < ent->mIdleConns.Length(); idx++) {
nsHttpConnection *idleConn = ent->mIdleConns[idx];
if (idleConn->MaxBytesRead() < conn->MaxBytesRead())
break;
}
NS_ADDREF(conn);
ent->mIdleConns.InsertElementAt(idx, conn);
mNumIdleConns++;
conn->BeginIdleMonitoring();
// If the added connection was first idle connection or has shortest
// time to live among the watched 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::nsConnectionEntry
nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
{
if (mSpdyPreferred)
gHttpHandler->ConnMgr()->RemoveSpdyPreferred(mDottedDecimalAddress);
NS_RELEASE(mConnInfo);
}
//-----------------------------------------------------------------------------
// 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,
bool *reset)
{
return mConn->OnHeadersAvailable(trans, req, resp, reset);
}
nsresult
nsHttpConnectionMgr::nsConnectionHandle::ResumeSend(nsAHttpTransaction *caller)
{
return mConn->ResumeSend(caller);
}
nsresult
nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv(nsAHttpTransaction *caller)
{
return mConn->ResumeRecv(caller);
}
void
nsHttpConnectionMgr::nsConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
{
mConn->CloseTransaction(trans, reason);
}
void
nsHttpConnectionMgr::nsConnectionHandle::GetConnectionInfo(nsHttpConnectionInfo **result)
{
mConn->GetConnectionInfo(result);
}
nsresult
nsHttpConnectionMgr::
nsConnectionHandle::TakeTransport(nsISocketTransport **aTransport,
nsIAsyncInputStream **aInputStream,
nsIAsyncOutputStream **aOutputStream)
{
return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
}
void
nsHttpConnectionMgr::nsConnectionHandle::GetSecurityInfo(nsISupports **result)
{
mConn->GetSecurityInfo(result);
}
bool
nsHttpConnectionMgr::nsConnectionHandle::IsPersistent()
{
return mConn->IsPersistent();
}
bool
nsHttpConnectionMgr::nsConnectionHandle::IsReused()
{
return mConn->IsReused();
}
nsresult
nsHttpConnectionMgr::nsConnectionHandle::PushBack(const char *buf, PRUint32 bufLen)
{
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) {
// A failure to create the transport object at all
// will result in this not being present in the halfopen table
// so ignore failures of RemoveElement()
mEnt->mHalfOpens.RemoveElement(this);
}
}
nsresult
nsHttpConnectionMgr::
nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport,
nsIAsyncInputStream **instream,
nsIAsyncOutputStream **outstream,
bool isBackup)
{
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;
// For backup connections, we disable IPv6. That's because some users have
// broken IPv6 connectivity (leading to very long timeouts), and disabling
// IPv6 on the backup connection gives them a much better user experience
// with dual-stack hosts, though they still pay the 250ms delay for each new
// connection. This strategy is also known as "happy eyeballs".
if (isBackup && gHttpHandler->FastFallbackToIPv4())
tmpFlags |= nsISocketTransport::DISABLE_IPV6;
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()
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
nsresult rv;
if (gHttpHandler->IsSpdyEnabled() &&
gHttpHandler->CoalesceSpdy() &&
mEnt->mConnInfo->UsingSSL() &&
!mEnt->mConnInfo->UsingHttpProxy() &&
!mEnt->mDidDNS) {
// Grab the DNS resolution for this host for un-sharding purposes
// This lookup will no doubt be coalesced by the DNS sub system as it
// overlaps with the SocketTransport
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID,
&rv);
mEnt->mDidDNS = true;
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsICancelable> cancelable;
dns->AsyncResolve(mEnt->mConnInfo->GetHost(), 0, this,
NS_GetCurrentThread(),
getter_AddRefs(cancelable));
}
}
rv = SetupStreams(getter_AddRefs(mSocketTransport),
getter_AddRefs(mStreamIn),
getter_AddRefs(mStreamOut),
false);
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),
true);
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;
}
NS_IMETHODIMP // nsIDNSListener
nsHttpConnectionMgr::
nsHalfOpenSocket::OnLookupComplete(nsICancelable *aRequest,
nsIDNSRecord *aRecord,
nsresult aStatus)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
if (NS_SUCCEEDED(aStatus) && mEnt) {
aRecord->GetNextAddrAsString(mEnt->mDottedDecimalAddress);
gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt);
}
return NS_OK;
}
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);
LOG(("nsHalfOpenSocket::SetupBackupTimer()"));
}
}
}
void
nsHttpConnectionMgr::nsHalfOpenSocket::CancelBackupTimer()
{
// If the syntimer is still armed, we can cancel it because no backup
// socket should be formed at this point
if (!mSynTimer)
return;
LOG(("nsHalfOpenSocket::CancelBackupTimer()"));
mSynTimer->Cancel();
mSynTimer = nsnull;
}
void
nsHttpConnectionMgr::nsHalfOpenSocket::Abandon()
{
LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s]",
this, mEnt->mConnInfo->Host()));
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
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;
}
CancelBackupTimer();
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");
if (!gHttpHandler->ConnMgr()->
AtActiveConnectionLimit(mEnt, mTransaction->Caps())) {
SetupBackupStreams();
}
mSynTimer = nsnull;
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();
CancelBackupTimer();
// assign the new socket to the http connection
nsRefPtr<nsHttpConnection> conn = new nsHttpConnection();
LOG(("nsHalfOpenSocket::OnOutputStreamReady "
"Created new nshttpconnection %p\n", conn.get()));
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.get(), 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);
nsRefPtr<nsHttpConnection> copy(conn); // because onmsg*() expects to drop a reference
gHttpHandler->ConnMgr()->OnMsgReclaimConnection(NS_OK, conn.forget().get());
}
return rv;
}
// method for nsITransportEventSink
NS_IMETHODIMP
nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
nsresult status,
PRUint64 progress,
PRUint64 progressMax)
{
if (mTransaction)
mTransaction->OnTransportStatus(trans, status, progress);
if (trans != mSocketTransport)
return NS_OK;
switch (status) {
case nsISocketTransport::STATUS_CONNECTING_TO:
// Passed DNS resolution, now trying to connect, start the backup timer
// only prevent creating another backup transport.
// We also check for mEnt presence to not instantiate the timer after
// this half open socket has already been abandoned. It may happen
// when we get this notification right between main-thread calls to
// nsHttpConnectionMgr::Shutdown and nsSocketTransportService::Shutdown
// where the first abandones all half open socket instances and only
// after that the second stops the socket thread.
if (mEnt && !mBackupTransport && !mSynTimer)
SetupBackupTimer();
break;
case nsISocketTransport::STATUS_CONNECTED_TO:
// TCP connection's up, now transfer or SSL negotiantion starts,
// no need for backup socket
CancelBackupTimer();
break;
default:
break;
}
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;
}
nsHttpConnection *
nsHttpConnectionMgr::nsConnectionHandle::TakeHttpConnection()
{
// return our connection object to the caller and clear it internally
// do not drop our reference - the caller now owns it.
NS_ASSERTION(mConn, "no connection");
nsHttpConnection *conn = mConn;
mConn = nsnull;
return conn;
}
bool
nsHttpConnectionMgr::nsConnectionHandle::LastTransactionExpectedNoContent()
{
return mConn->LastTransactionExpectedNoContent();
}
void
nsHttpConnectionMgr::
nsConnectionHandle::SetLastTransactionExpectedNoContent(bool val)
{
mConn->SetLastTransactionExpectedNoContent(val);
}
nsISocketTransport *
nsHttpConnectionMgr::nsConnectionHandle::Transport()
{
if (!mConn)
return nsnull;
return mConn->Transport();
}