mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Joseph Kain" <jkain@nuro.ai>
Cc: linux-rt-devel@lists.linux.dev, linux-kernel@vger.kernel.org
Subject: Re: [PATCH net v6] net: schedule a purge when the defer queue rejects a free
Date: Tue, 20 Oct 2026 17:43:18 +0000	[thread overview]
Message-ID: <sashiko-outbox-179437@kernel.org> (raw)
In-Reply-To: <20261019174258.1375583-1-jkain@nuro.ai>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] net: skb_attempt_defer_free() still latches when skb_defer_max hits 0
- [Medium] net: repeated IPIs and lock contention on skb_attempt_defer_free() reject path
--

commit 4683493f7039cdaa5b1372dd8cadf3a6b5ffb8f0
Author: Joseph Kain <jkain@nuro.ai>

net: schedule a purge when the defer queue rejects a free

This commit schedules a defer list purge from the reject path in
skb_attempt_defer_free(), changes the limit comparison from >= to >, and
reorders llist_del_all() before resetting defer_count in
__skb_defer_free_flush() so that rejected frees do not leave defer_count
permanently stuck above skb_defer_max.

> diff --git a/net/core/dev.c b/net/core/dev.c
> index e76762e29360e..a5912b728ddd7 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -6940,8 +6940,11 @@ static void __skb_defer_free_flush(struct skb_defer_node *sdn, int budget)
>  
>  	if (llist_empty(&sdn->defer_list))
>  		return;
> -	atomic_long_set(&sdn->defer_count, 0);
>  	free_list = llist_del_all(&sdn->defer_list);
> +	/* Racy on purpose, as in __dev_xmit_skb(): a producer landing between
> +	 * the del_all and this store stays queued but uncounted.
> +	 */
> +	atomic_long_set(&sdn->defer_count, 0);
>  
>  	llist_for_each_entry_safe(skb, next, free_list, ll_node) {
>  		prefetch(next);
> diff --git a/net/core/skbuff.c b/net/core/skbuff.c
> index 41beaf625421a..596f99811bacd 100644
> --- a/net/core/skbuff.c
> +++ b/net/core/skbuff.c
> @@ -7392,8 +7392,14 @@ nodefer:	kfree_skb_napi_cache(skb);
>  	defer_max = READ_ONCE(net_hotdata.sysctl_skb_defer_max);
>  	defer_count = atomic_long_inc_return(&sdn->defer_count);
>  
> -	if (defer_count >= defer_max)
> +	if (unlikely(defer_count > defer_max)) {
> +		/* The kept increment only comes back down when a flush zeroes
> +		 * it, and this is the one path that sees a full queue.
> +		 */
> +		if (!llist_empty(&sdn->defer_list))
> +			kick_defer_list_purge(cpu);
>  		goto nodefer;
> +	}

[Severity: Medium]
Can sdn->defer_count still permanently latch above defer_max with an empty
sdn->defer_list when net.core.skb_defer_max is set to 0 and later restored to
a positive value?

Both the new reject path in skb_attempt_defer_free() and
__skb_defer_free_flush() rely on sdn->defer_list being non-empty whenever
sdn->defer_count > defer_max:

net/core/skbuff.c:skb_attempt_defer_free() {
	...
	if (unlikely(defer_count > defer_max)) {
		if (!llist_empty(&sdn->defer_list))
			kick_defer_list_purge(cpu);
		goto nodefer;
	}
}

net/core/dev.c:__skb_defer_free_flush() {
	if (llist_empty(&sdn->defer_list))
		return;
	free_list = llist_del_all(&sdn->defer_list);
	atomic_long_set(&sdn->defer_count, 0);
	...
}

When a producer observes defer_max == 0, even the first increment
(defer_count == 1) satisfies defer_count > defer_max and jumps to nodefer
without adding an skb to sdn->defer_list.

The commit message notes that from v7.1 on, 0 is safe because
proc_do_skb_defer_max() flips skb_defer_disable_key before producers
increment. However, in proc_do_skb_defer_max():

net/core/sysctl_net_core.c:proc_do_skb_defer_max() {
	oval = !net_hotdata.sysctl_skb_defer_max;
	ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
	nval = !net_hotdata.sysctl_skb_defer_max;

	if (nval != oval) {
		if (nval)
			static_branch_enable(&skb_defer_disable_key);
		else
			static_branch_disable(&skb_defer_disable_key);
	}
}

proc_dointvec_minmax() writes 0 to net_hotdata.sysctl_skb_defer_max before
static_branch_enable() acquires the jump_label locks and patches kernel text
across all CPUs. During that window (and deterministically on 6.18 through
7.0 stable trees targeted by Fixes: 844c9db7f7f5, which do not have
skb_defer_disable_key), concurrent callers of skb_attempt_defer_free() pass
the static key check, read defer_max == 0, and increment sdn->defer_count
while rejecting before llist_add().

Once sdn->defer_list is empty and sdn->defer_count has accumulated to a value
at or above the restored net.core.skb_defer_max limit, every subsequent
producer sees defer_count > defer_max and llist_empty(&sdn->defer_list),
skips kick_defer_list_purge(), and even an autonomous __skb_defer_free_flush()
on the owner CPU returns early without zeroing sdn->defer_count.

[Severity: Medium]
Does calling kick_defer_list_purge(cpu) on every rejected skb when
!llist_empty(&sdn->defer_list) cause repeated cross-CPU IPIs and lock
contention during a burst of rejects?

The commit message states:

  kick_defer_list_purge() already coalesces through sd->defer_ipi_scheduled,
  so a burst of rejects costs one IPI.

In net/core/dev.c, the IPI callback trigger_rx_softirq() runs in hardirq
context and clears sd->defer_ipi_scheduled immediately after raising
NET_RX_SOFTIRQ, before net_rx_action() and skb_defer_free_flush() run in
softirq context to drain sdn->defer_list:

net/core/dev.c:trigger_rx_softirq() {
	struct softnet_data *sd = data;

	__raise_softirq_irqoff(NET_RX_SOFTIRQ);
	smp_store_release(&sd->defer_ipi_scheduled, 0);
}

net/core/dev.c:kick_defer_list_purge() {
	struct softnet_data *sd = &per_cpu(softnet_data, cpu);
	unsigned long flags;

	if (use_backlog_threads()) {
		backlog_lock_irq_save(sd, &flags);

		if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state))
			__napi_schedule_irqoff(&sd->backlog);

		backlog_unlock_irq_restore(sd, flags);

	} else if (!cmpxchg(&sd->defer_ipi_scheduled, 0, 1)) {
		if (smp_call_function_single_async(cpu, &sd->defer_csd))
			WRITE_ONCE(sd->defer_ipi_scheduled, 0);
	}
}

Previously, kick_defer_list_purge() was gated on the exact equality
(defer_count - 1) == (defer_max >> 1), so it was invoked at most once per
(cpu, node) per flush cycle.

With this change, whenever the target CPU has hardirqs enabled but has not yet
reached skb_defer_free_flush() (for example, while executing in a
local_bh_disable() section, polling NAPI, or when softirqs are deferred to
ksoftirqd), each IPI immediately clears sd->defer_ipi_scheduled in hardirq
context while sdn->defer_list is still non-empty. Won't subsequent rejected
skbs on remote CPUs then repeatedly win cmpxchg(&sd->defer_ipi_scheduled, 0, 1)
and send back-to-back IPIs, or contend on backlog_lock_irq_save() on every
rejected skb when use_backlog_threads() is enabled?

>  
>  	llist_add(&skb->ll_node, &sdn->defer_list);
>  

[ ... ]

完美 — wait no extra text! Let me make sure I don't output anything after the blank line! Let me remove that accidental thought line before finishing! Wait—I am already in the output stream! Oh! Let me be careful. I cannot erase what was emitted, so I will just end with a blank line now.
$]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019174258.1375583-1-jkain@nuro.ai?part=1

  parent reply	other threads:[~2026-10-20 17:43 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19 17:42 Joseph Kain
2026-10-19 17:58 ` Eric Dumazet
2026-10-19 18:47   ` Joseph Kain
2026-10-20 17:43 ` sashiko-bot [this message]
2026-10-19 17:44 Joseph Kain
2026-10-20 17:44 ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-179437@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=jkain@nuro.ai \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-rt-devel@lists.linux.dev \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®