NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Re: kern/55567: tcp-send slows down to slow single byte transfers (analysis and fix)
The following reply was made to PR kern/55567; it has been noted by GNATS.
From: Frank Kardel <kardel%netbsd.org@localhost>
To: gnats-bugs%netbsd.org@localhost, tech-net%netbsd.org@localhost
Cc: kern-bug-people%netbsd.org@localhost, gnats-admin%netbsd.org@localhost, netbsd-bugs%netbsd.org@localhost
Subject: Re: kern/55567: tcp-send slows down to slow single byte transfers
(analysis and fix)
Date: Wed, 2 Sep 2020 10:01:30 +0200
Analysis:
The issue is that on a tcp connection that is only sending data but not
receiving data it
can happen that the send window closes. In this state bytes are
transferred to the receiver
via one by one via the zero window probes but snd_wnd updates are skipped.
The reason for the skipped updates is that SND.WL2 (last window update
ACK) has left
the valid range of SND.UNA =< SEG.ACK <= SND.MAXSEND by a large amount.
Data flows again when the receiving side sends some data (that's why you
can get
remote login session unstuck by typing.
Tracking a connection finds that SND.WL2 is basically starving in this
scenario and reaches
a point where the window update code does not update the send window any
more:
/*
* Update window information.
* Don't look at window if no ACK: TAC's send garbage on first SYN.
*/
if ((tiflags & TH_ACK) && (SEQ_LT(tp->snd_wl1, th->th_seq) ||
(tp->snd_wl1 == th->th_seq && (SEQ_LT(tp->snd_wl2, th->th_ack) ||
(tp->snd_wl2 == th->th_ack && tiwin > tp->snd_wnd))))) {
It is an ACK, yes, th_seq has not changed (no data from the recieving side).
SEQ_LT(tp->snd_wl2, th->th_ack) returns 0 in the stuck state and it is
not a true window
update,, thus the send window updates are stuck until the comparison
returns 1 again.
This can take a very long time at a data rate of 0.2 Bytes/second (the
rate will be scaled
back further later on).
So, why are updates to SND.WL2 not happening. The cause is an
optimization common
cases of unidirectional transfers:
/*
* Fast path: check for the two common cases of a uni-directional
* data transfer. If:
* o We are in the ESTABLISHED state, and
* o The packet has no control flags, and
* o The packet is in-sequence, and
* o The window didn't change, and
* o We are not retransmitting
* It's a candidate.
*
* If the length (tlen) is zero and the ack moved forward, we're
* the sender side of the transfer. Just free the data acked and
* wake any higher level process that was blocked waiting for
* space.
*
* If the length is non-zero and the ack didn't move, we're the
* receiver side. If we're getting packets in-order (the reassembly
* queue is empty), add the data to the socket buffer and note
* that we need a delayed ack.
*/
The path taken that leads to SND.WL2 being starved out is the pure ACK
section
which adjusts the send buffer, updates SND.UNA, SND.FACK, SND.HIGH,
frees mbuf
, send more data if available and returns.
SND.WL2 is never touched here an so a longer sequence can leave SND.WL2
far enough
behind for the stuck zero window size to occur.
Proposed fix:
diff -u -r1.418 tcp_input.c
--- tcp_input.c 6 Jul 2020 18:49:12 -0000 1.418
+++ tcp_input.c 2 Sep 2020 07:59:46 -0000
@@ -1897,6 +1897,19 @@
tp->snd_fack = tp->snd_una;
if (SEQ_LT(tp->snd_high, tp->snd_una))
tp->snd_high = tp->snd_una;
+ /*
+ * drag snd_wl2 along so only newer
+ * ACKs can update the window size.
+ * also avoids the state where snd_wl2
+ * is eventually larger than th_ack and thus
+ * blocking the window update mechanism and
+ * the connection gets stuck for a loooong
+ * time in the zero sized send window state.
+ *
+ * see PR/kern 55567
+ */
+ tp->snd_wl2 = tp->snd_una;
+
m_freem(m);
/*
Frank
Home |
Main Index |
Thread Index |
Old Index