tcp: Fix retransmit logic to avoid lots of spurious retransmits.

The retransmit timer was only stopped when all in flight data was
acknowledged and never updated on individual acknowledgements. This
caused a lot of erroneous retransmits whenever the buffer was filled
fast enough so that the acknowledgements never caught up, i.e. whenever
uploading or streaming data.

Move setting of the initial retransmit timer inside the send loop so it
is closer to the actual time the segment is sent out and simplify the
logic a bit.

Limit the minimal retransmit timeout to 200 msecs to avoid spurious
retransmit in the face of delayed acknowledgements. This is lower than
the 1 second minimum the RFCs suggest. Other stacks use various other
sub-second timeouts, the 200 msecs follows what Linux does.

Also add the exponential back off of the retransmit timeout when
retransmits are triggered. This is bounded by a 60 seconds maximum
according to RFC6298.
This commit is contained in:
Michael Lotz
2015-08-02 23:21:11 +02:00
parent da8fbe0e59
commit 5f7749078e
2 changed files with 50 additions and 24 deletions
@@ -1953,7 +1953,8 @@ TCPEndpoint::_SendQueued(bool force, uint32 sendWindow)
}
uint32 length = min_c(fSendQueue.Available(fSendNext), sendWindow);
tcp_sequence previousSendNext = fSendNext;
bool shouldStartRetransmitTimer = fSendNext == fSendUnacknowledged;
bool retransmit = fSendNext < fSendMax;
do {
uint32 segmentMaxSize = fSendMaxSegmentSize
@@ -1968,7 +1969,7 @@ TCPEndpoint::_SendQueued(bool force, uint32 sendWindow)
}
// Determine if we should really send this segment
if (!force && !_ShouldSendSegment(segment, segmentLength,
if (!force && !retransmit && !_ShouldSendSegment(segment, segmentLength,
segmentMaxSize, flightSize)) {
if (fSendQueue.Available()
&& !gStackModule->is_timer_active(&fPersistTimer)
@@ -2047,24 +2048,25 @@ TCPEndpoint::_SendQueued(bool force, uint32 sendWindow)
return status;
}
if (shouldStartRetransmitTimer && size > 0) {
TRACE("starting initial retransmit timer of: %" B_PRIdBIGTIME,
fRetransmitTimeout);
gStackModule->set_timer(&fRetransmitTimer, fRetransmitTimeout);
shouldStartRetransmitTimer = false;
}
if (segment.flags & TCP_FLAG_ACKNOWLEDGE)
fLastAcknowledgeSent = segment.acknowledge;
length -= segmentLength;
segment.flags &= ~(TCP_FLAG_SYNCHRONIZE | TCP_FLAG_RESET
| TCP_FLAG_FINISH);
if (retransmit)
break;
} while (length > 0);
// if we sent data from the beggining of the send queue,
// start the retransmition timer
if (previousSendNext == fSendUnacknowledged
&& fSendNext > previousSendNext) {
TRACE(" SendQueue(): set retransmit timer with rto %" B_PRIdBIGTIME,
fRetransmitTimeout);
gStackModule->set_timer(&fRetransmitTimer, fRetransmitTimeout);
}
return B_OK;
}
@@ -2120,24 +2122,34 @@ TCPEndpoint::_PrepareSendPath(const sockaddr* peer)
void
TCPEndpoint::_Acknowledged(tcp_segment_header& segment)
{
size_t previouslyUsed = fSendQueue.Used();
TRACE("_Acknowledged(): ack %" B_PRIu32 "; uack %" B_PRIu32 "; next %"
B_PRIu32 "; max %" B_PRIu32, segment.acknowledge,
fSendUnacknowledged.Number(), fSendNext.Number(), fSendMax.Number());
fSendQueue.RemoveUntil(segment.acknowledge);
fSendUnacknowledged = segment.acknowledge;
ASSERT(fSendUnacknowledged <= segment.acknowledge);
if (fSendNext < fSendUnacknowledged)
fSendNext = fSendUnacknowledged;
if (fSendUnacknowledged == fSendMax)
gStackModule->cancel_timer(&fRetransmitTimer);
if (fSendQueue.Used() < previouslyUsed) {
// this ACK acknowledged data
if (fSendUnacknowledged < segment.acknowledge) {
fSendQueue.RemoveUntil(segment.acknowledge);
fSendUnacknowledged = segment.acknowledge;
if (fSendNext < fSendUnacknowledged)
fSendNext = fSendUnacknowledged;
if (segment.options & TCP_HAS_TIMESTAMPS)
_UpdateRoundTripTime(tcp_diff_timestamp(segment.timestamp_reply));
else {
// TODO: Fallback to RFC 793 type estimation
// TODO: Fallback to RFC 793 type estimation; This just resets
// any potential exponential back off that happened due to
// retransmits.
fRetransmitTimeout = TCP_INITIAL_RTT;
}
if (fSendUnacknowledged == fSendMax) {
TRACE("all acknowledged, cancelling retransmission timer");
gStackModule->cancel_timer(&fRetransmitTimer);
} else {
TRACE("data acknowledged, resetting retransmission timer to: %"
B_PRIdBIGTIME, fRetransmitTimeout);
gStackModule->set_timer(&fRetransmitTimer, fRetransmitTimeout);
}
if (is_writable(fState)) {
@@ -2171,8 +2183,15 @@ void
TCPEndpoint::_Retransmit()
{
TRACE("Retransmit()");
_ResetSlowStart();
fSendNext = fSendUnacknowledged;
// Do exponential back off of the retransmit timeout
fRetransmitTimeout *= 2;
if (fRetransmitTimeout > TCP_MAX_RETRANSMIT_TIMEOUT)
fRetransmitTimeout = TCP_MAX_RETRANSMIT_TIMEOUT;
_SendQueued();
}
@@ -2192,6 +2211,8 @@ TCPEndpoint::_UpdateRoundTripTime(int32 roundTripTime)
fRetransmitTimeout = ((fRoundTripTime / 4 + fRoundTripDeviation) / 2)
* kTimestampFactor;
if (fRetransmitTimeout < TCP_MIN_RETRANSMIT_TIMEOUT)
fRetransmitTimeout = TCP_MIN_RETRANSMIT_TIMEOUT;
TRACE(" RTO is now %" B_PRIdBIGTIME " (after rtt %" B_PRId32 "ms)",
fRetransmitTimeout, roundTripTime);
@@ -186,6 +186,11 @@ operator==(tcp_sequence a, tcp_sequence b)
#define TCP_MAX_WINDOW 65535
#define TCP_MAX_SEGMENT_LIFETIME 60000000 // 60 secs
// Minimum retransmit timeout (consider delayed ack)
#define TCP_MIN_RETRANSMIT_TIMEOUT 200000 // 200 msecs
// Maximum retransmit timeout (per RFC6298)
#define TCP_MAX_RETRANSMIT_TIMEOUT 60000000 // 60 secs
struct tcp_sack {
uint32 left_edge;
uint32 right_edge;