Skip to content

Commit

Permalink
Bug 939318 - Find and close HTTP connections without traffic after ne…
Browse files Browse the repository at this point in the history
…twork change. r=mcmanus

SPDY/http2 connections get a ping and be allowed N seconds to respond.

Active HTTP connections will be allowed N seconds to get traffic, if they
don't afer N seconds they get closed to avoid risking stalled transfers.

N is 5 by default: pref is "network.http.network-changed.timeout"
  • Loading branch information
bagder committed Sep 11, 2014
1 parent e3a15e4 commit 4626439
Show file tree
Hide file tree
Showing 14 changed files with 309 additions and 6 deletions.
5 changes: 5 additions & 0 deletions modules/libpref/init/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,11 @@ pref("network.http.connection-retry-timeout", 250);
// to give up if the OS does not give up first
pref("network.http.connection-timeout", 90);

// The number of seconds to allow active connections to prove that they have
// traffic before considered stalled, after a network change has been detected
// and signalled.
pref("network.http.network-changed.timeout", 5);

// The maximum number of current global half open sockets allowable
// when starting a new speculative connection.
pref("network.http.speculative-parallel-limit", 6);
Expand Down
2 changes: 2 additions & 0 deletions netwerk/protocol/http/ASpdySession.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class ASpdySession : public nsAHttpTransaction
return true;
}

virtual void SendPing() = 0;

const static uint32_t kSendingChunkSize = 4095;
const static uint32_t kTCPSendBufferSize = 131072;

Expand Down
36 changes: 34 additions & 2 deletions netwerk/protocol/http/Http2Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Http2Session::Http2Session(nsISocketTransport *aSocketTransport)
, mOutputQueueSent(0)
, mLastReadEpoch(PR_IntervalNow())
, mPingSentEpoch(0)
, mPreviousUsed(false)
, mWaitingForSettingsAck(false)
, mGoAwayOnPush(false)
{
Expand Down Expand Up @@ -295,8 +296,14 @@ Http2Session::ReadTimeoutTick(PRIntervalTime now)

if ((now - mLastReadEpoch) < mPingThreshold) {
// recent activity means ping is not an issue
if (mPingSentEpoch)
if (mPingSentEpoch) {
mPingSentEpoch = 0;
if (mPreviousUsed) {
// restore the former value
mPingThreshold = mPreviousPingThreshold;
mPreviousUsed = false;
}
}

return PR_IntervalToSeconds(mPingThreshold) -
PR_IntervalToSeconds(now - mLastReadEpoch);
Expand All @@ -316,8 +323,9 @@ Http2Session::ReadTimeoutTick(PRIntervalTime now)
LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this));

mPingSentEpoch = PR_IntervalNow();
if (!mPingSentEpoch)
if (!mPingSentEpoch) {
mPingSentEpoch = 1; // avoid the 0 sentinel value
}
GeneratePing(false);
ResumeRecv(); // read the ping reply

Expand Down Expand Up @@ -3303,5 +3311,29 @@ Http2Session::PushBack(const char *buf, uint32_t len)
return mConnection->PushBack(buf, len);
}

void
Http2Session::SendPing()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

if (mPreviousUsed) {
// alredy in progress, get out
return;
}

mPingSentEpoch = PR_IntervalNow();
if (!mPingSentEpoch) {
mPingSentEpoch = 1; // avoid the 0 sentinel value
}
if (!mPingThreshold ||
(mPingThreshold > gHttpHandler->NetworkChangedTimeout())) {
mPreviousPingThreshold = mPingThreshold;
mPreviousUsed = true;
mPingThreshold = gHttpHandler->NetworkChangedTimeout();
}
GeneratePing(false);
ResumeRecv();
}

} // namespace mozilla::net
} // namespace mozilla
5 changes: 5 additions & 0 deletions netwerk/protocol/http/Http2Session.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ class Http2Session MOZ_FINAL : public ASpdySession
void DecrementServerSessionWindow (uint32_t bytes) { mServerSessionWindow -= bytes; }
void GetNegotiatedToken(nsACString &s) { s.Assign(mNegotiatedToken); }

void SendPing() MOZ_OVERRIDE;

private:

// These internal states do not correspond to the states of the HTTP/2 specification
Expand Down Expand Up @@ -442,6 +444,9 @@ class Http2Session MOZ_FINAL : public ASpdySession
PRIntervalTime mLastDataReadEpoch; // used for IdleTime()
PRIntervalTime mPingSentEpoch;

PRIntervalTime mPreviousPingThreshold; // backup for the former value
bool mPreviousUsed; // true when backup is used

// used as a temporary buffer while enumerating the stream hash during GoAway
nsDeque mGoAwayStreamsToRestart;

Expand Down
38 changes: 37 additions & 1 deletion netwerk/protocol/http/SpdySession3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ SpdySession3::SpdySession3(nsISocketTransport *aSocketTransport)
, mLastReadEpoch(PR_IntervalNow())
, mPingSentEpoch(0)
, mNextPingID(1)
, mPreviousUsed(false)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

Expand Down Expand Up @@ -224,8 +225,14 @@ SpdySession3::ReadTimeoutTick(PRIntervalTime now)

if ((now - mLastReadEpoch) < mPingThreshold) {
// recent activity means ping is not an issue
if (mPingSentEpoch)
if (mPingSentEpoch) {
mPingSentEpoch = 0;
if (mPreviousUsed) {
// restore the former value
mPingThreshold = mPreviousPingThreshold;
mPreviousUsed = false;
}
}

return PR_IntervalToSeconds(mPingThreshold) -
PR_IntervalToSeconds(now - mLastReadEpoch);
Expand Down Expand Up @@ -2813,5 +2820,34 @@ SpdySession3::PushBack(const char *buf, uint32_t len)
return mConnection->PushBack(buf, len);
}

void
SpdySession3::SendPing()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

if (mPreviousUsed) {
// alredy in progress, get out
return;
}

mPingSentEpoch = PR_IntervalNow();
if (!mPingSentEpoch) {
mPingSentEpoch = 1; // avoid the 0 sentinel value
}
if (!mPingThreshold ||
(mPingThreshold > gHttpHandler->NetworkChangedTimeout())) {
mPreviousPingThreshold = mPingThreshold;
mPreviousUsed = true;
mPingThreshold = gHttpHandler->NetworkChangedTimeout();
}

GeneratePing(mNextPingID);
mNextPingID += 2;
ResumeRecv();

gHttpHandler->ConnMgr()->ActivateTimeoutTick();
}


} // namespace mozilla::net
} // namespace mozilla
5 changes: 5 additions & 0 deletions netwerk/protocol/http/SpdySession3.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ class SpdySession3 MOZ_FINAL : public ASpdySession
z_stream *UpstreamZlib() { return &mUpstreamZlib; }
nsISocketTransport *SocketTransport() { return mSocketTransport; }

void SendPing() MOZ_OVERRIDE;

private:

enum stateType {
Expand Down Expand Up @@ -378,6 +380,9 @@ class SpdySession3 MOZ_FINAL : public ASpdySession
PRIntervalTime mPingSentEpoch;
uint32_t mNextPingID;

PRIntervalTime mPreviousPingThreshold; // backup for the former value
bool mPreviousUsed; // true when backup is used

// used as a temporary buffer while enumerating the stream hash during GoAway
nsDeque mGoAwayStreamsToRestart;

Expand Down
37 changes: 36 additions & 1 deletion netwerk/protocol/http/SpdySession31.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ SpdySession31::SpdySession31(nsISocketTransport *aSocketTransport)
, mLastReadEpoch(PR_IntervalNow())
, mPingSentEpoch(0)
, mNextPingID(1)
, mPreviousUsed(false)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

Expand Down Expand Up @@ -227,8 +228,14 @@ SpdySession31::ReadTimeoutTick(PRIntervalTime now)

if ((now - mLastReadEpoch) < mPingThreshold) {
// recent activity means ping is not an issue
if (mPingSentEpoch)
if (mPingSentEpoch) {
mPingSentEpoch = 0;
if (mPreviousUsed) {
// restore the former value
mPingThreshold = mPreviousPingThreshold;
mPreviousUsed = false;
}
}

return PR_IntervalToSeconds(mPingThreshold) -
PR_IntervalToSeconds(now - mLastReadEpoch);
Expand Down Expand Up @@ -2957,5 +2964,33 @@ SpdySession31::PushBack(const char *buf, uint32_t len)
return mConnection->PushBack(buf, len);
}

void
SpdySession31::SendPing()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

if (mPreviousUsed) {
// alredy in progress, get out
return;
}

mPingSentEpoch = PR_IntervalNow();
if (!mPingSentEpoch) {
mPingSentEpoch = 1; // avoid the 0 sentinel value
}
if (!mPingThreshold ||
(mPingThreshold > gHttpHandler->NetworkChangedTimeout())) {
mPreviousPingThreshold = mPingThreshold;
mPreviousUsed = true;
mPingThreshold = gHttpHandler->NetworkChangedTimeout();
}

GeneratePing(mNextPingID);
mNextPingID += 2;
ResumeRecv();

gHttpHandler->ConnMgr()->ActivateTimeoutTick();
}

} // namespace mozilla::net
} // namespace mozilla
5 changes: 5 additions & 0 deletions netwerk/protocol/http/SpdySession31.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ class SpdySession31 MOZ_FINAL : public ASpdySession
int64_t RemoteSessionWindow() { return mRemoteSessionWindow; }
void DecrementRemoteSessionWindow (uint32_t bytes) { mRemoteSessionWindow -= bytes; }

void SendPing() MOZ_OVERRIDE;

private:

enum stateType {
Expand Down Expand Up @@ -397,6 +399,9 @@ class SpdySession31 MOZ_FINAL : public ASpdySession
PRIntervalTime mPingSentEpoch;
uint32_t mNextPingID;

PRIntervalTime mPreviousPingThreshold; // backup for the former value
bool mPreviousUsed; // true when backup is used

// used as a temporary buffer while enumerating the stream hash during GoAway
nsDeque mGoAwayStreamsToRestart;

Expand Down
19 changes: 19 additions & 0 deletions netwerk/protocol/http/nsHttpConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ nsHttpConnection::nsHttpConnection()
, mExperienced(false)
, mInSpdyTunnel(false)
, mForcePlainText(false)
, mTrafficStamp(false)
, mHttp1xTransactionCount(0)
, mRemainingConnectionUses(0xffffffff)
, mClassification(nsAHttpTransaction::CLASS_GENERAL)
Expand Down Expand Up @@ -2080,5 +2081,23 @@ nsHttpConnection::GetInterface(const nsIID &iid, void **result)
return NS_ERROR_NO_INTERFACE;
}

void
nsHttpConnection::CheckForTraffic(bool check)
{
if (check) {
if (mSpdySession) {
// Send a ping to verify it is still alive
mSpdySession->SendPing();
} else {
// If not SPDY, Store snapshot amount of data right now
mTrafficCount = mTotalBytesWritten + mTotalBytesRead;
mTrafficStamp = true;
}
} else {
// mark it as not checked
mTrafficStamp = false;
}
}

} // namespace mozilla::net
} // namespace mozilla
16 changes: 16 additions & 0 deletions netwerk/protocol/http/nsHttpConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,18 @@ class nsHttpConnection MOZ_FINAL : public nsAHttpSegmentReader
void SetupSecondaryTLS();
void SetInSpdyTunnel(bool arg);

// Check active connections for traffic (or not). SPDY connections send a
// ping, ordinary HTTP connections get some time to get traffic to be
// considered alive.
void CheckForTraffic(bool check);

// NoTraffic() returns true if there's been no traffic on the (non-spdy)
// connection since CheckForTraffic() was called.
bool NoTraffic() {
return mTrafficStamp &&
(mTrafficCount == (mTotalBytesWritten + mTotalBytesRead));
}

private:
// Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
enum TCPKeepaliveConfig {
Expand Down Expand Up @@ -292,6 +304,10 @@ class nsHttpConnection MOZ_FINAL : public nsAHttpSegmentReader
bool mInSpdyTunnel;
bool mForcePlainText;

// A snapshot of current number of transfered bytes
int64_t mTrafficCount;
bool mTrafficStamp; // true then the above is set

// The number of <= HTTP/1.1 transactions performed on this connection. This
// excludes spdy transactions.
uint32_t mHttp1xTransactionCount;
Expand Down
Loading

0 comments on commit 4626439

Please sign in to comment.