Thread (9 messages) 9 messages, 4 authors, 12d ago

Re: [PATCH net v2 1/2] tcp: restore RACK list membership when undoing loss

flat view

From: Eric Dumazet <edumazet@kernel.org>
Date: 2026-10-06 11:23:56
Also in: linux-kselftest, lkml

Le mar. 6 oct. 2026 à 08:42, Eric Dumazet [off-list ref] a écrit :


On 10/6/26 07:15, nramaswamy@openai.com wrote:
quoted
From: Neil Ramaswamy <redacted>

Partial undo can clear the TCPCB_LOST flag on segments already removed
from RACK's list, which prevents subsequent RACK loss detection, leading
to segments only being retransmitted after the RTO. Restoring them to
the RACK list as part of partial undo makes sure we can properly
reconsider them for fast retransmission in the future.

To do this, we first sort (by transmission time) the segments whose
TCPCB_LOST flag is being cleared and then linearly insert those back
into the RACK list, which is sorted by transmission time.

My investigation started from seeing repeated TCP stalls in prod and the
mitigation that seemed to prevent these stalls was limiting SO_SNDBUF to
96 KiB. It also seems like others have seen similar symptoms before [1].

[1]
https://lore.kernel.org/netdev/35A4DDAA-7E8D-43CB-A1F5-D1E46A4ED42E@gmail.com/ (local)

Fixes: 043b87d7599e ("tcp: more efficient RACK loss detection")
Signed-off-by: Neil Ramaswamy <redacted>
Assisted-by: LLM sparse
---
  net/ipv4/tcp_input.c | 35 +++++++++++++++++++++++++++++++++++
  1 file changed, 35 insertions(+)
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 92bc60716f33..51f04c7474dc 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -69,6 +69,7 @@
  #include <linux/module.h>
  #include <linux/sysctl.h>
  #include <linux/kernel.h>
+#include <linux/list_sort.h>
  #include <linux/prefetch.h>
  #include <linux/bitops.h>
  #include <net/dst.h>
@@ -2840,16 +2841,50 @@ static void DBGUNDO(struct sock *sk, const char *msg)
  #endif
  }

+static int tcp_rack_skb_cmp(void *priv, const struct list_head *a,
+                         const struct list_head *b)
+{
+     const struct sk_buff *skb_a = list_entry(a, struct sk_buff,
+                                            tcp_tsorted_anchor);
+     const struct sk_buff *skb_b = list_entry(b, struct sk_buff,
+                                            tcp_tsorted_anchor);
+
+     return tcp_skb_sent_after(tcp_skb_timestamp_us(skb_a),
+                               tcp_skb_timestamp_us(skb_b),
+                               TCP_SKB_CB(skb_a)->end_seq,
+                               TCP_SKB_CB(skb_b)->end_seq);
Patch LGTM, but perhaps we could avoid the two div_u64() calls here.


        return tcp_skb_sent_after(skb_a->skb_mstamp_ns,
                                  skb_b->skb_mstamp_ns,
                                  TCP_SKB_CB(skb_a)->end_seq,
                                  TCP_SKB_CB(skb_b)->end_seq);


quoted
+}
+
  static void tcp_undo_cwnd_reduction(struct sock *sk, bool unmark_loss)
  {
      struct tcp_sock *tp = tcp_sk(sk);

      if (unmark_loss) {
+             LIST_HEAD(restored);
              struct sk_buff *skb;

              skb_rbtree_walk(skb, &sk->tcp_rtx_queue) {
+                     if (TCP_SKB_CB(skb)->sacked & TCPCB_LOST)
+                             list_move_tail(&skb->tcp_tsorted_anchor,
+                                            &restored);
                      TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST;
              }
+             if (!list_empty(&restored)) {
+                     struct list_head *pos = &tp->tsorted_sent_queue;
+
+                     /* Ensure lost skbs are added in transmission order */
+                     list_sort(NULL, &restored, tcp_rack_skb_cmp);
Second thoughts (sorry I am currently attending LPC in Prague, little
time this week for reviews)

pw-bot: cr

I am quite concerned about calling list_sort() inside
tcp_undo_cwnd_reduction().

In high-BDP environments (like 100G/200G/400G fabrics), a flow can easily
have thousands of packets in flight. If a loss burst affects, say, 4000
skbs:

1. list_sort() will perform ~45,000 comparisons.
2. With indirect function calls (retpoline cost) and cache misses across
   1MB+ of sk_buff structures, this can freeze the CPU in SoftIRQ under
   the socket lock for multiple milliseconds..

We could avoid list_sort() completely by maintaining a second list,
e.g. tp->tsorted_lost_queue.

Consider the following:

1. When RACK detects loss in tcp_rack_detect_loss(), instead of calling
   list_del_init(&skb->tcp_tsorted_anchor), move the skb to the lost
   queue:
--- a/net/ipv4/tcp_recovery.c
+++ b/net/ipv4/tcp_recovery.c
@@ -101,7 +101,7 @@ static void tcp_rack_detect_loss(struct sock *sk,
u32 *reo_timeout)
  remaining = tcp_rack_skb_timeout(tp, skb, reo_wnd);
  if (remaining <= 0) {
  tcp_mark_skb_lost(sk, skb);
- list_del_init(&skb->tcp_tsorted_anchor);
+ list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_lost_queue);
  } else {

   Because RACK scans tsorted_sent_queue from oldest to newest, packets
   appended to tsorted_lost_queue are already guaranteed to be in strictly
   increasing departure timestamp order.

2. When a packet is retransmitted, tcp_update_skb_after_send() already
   calls:

   list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue);

   So retransmitted packets automatically leave tsorted_lost_queue and
   re-enter the tail of tsorted_sent_queue in O(1).

3. When a partial undo occurs in tcp_undo_cwnd_reduction(), any
   un-retransmitted packets remaining in tsorted_lost_queue are already
   in sorted order.

   Since these lost packets were originally sent prior to any subsequent
   transmissions in tsorted_sent_queue, restoring them can often be done
   with an O(1) list splice at the head:

   if (unmark_loss && !list_empty(&tp->tsorted_lost_queue))
           list_splice_init(&tp->tsorted_lost_queue, &tp->tsorted_sent_queue);
quoted
+                     while (!list_empty(&restored)) {
+                             struct list_head *entry = restored.next;
+
+                             while (pos->next != &tp->tsorted_sent_queue &&
+                                    !tcp_rack_skb_cmp(NULL, pos->next,
+                                              entry))
+                                     pos = pos->next;
+                             list_move(entry, pos);
+                             pos = entry;
+                     }
+             }
              tp->lost_out = 0;
              tcp_clear_all_retrans_hints(tp);
      }
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help