* [PATCH] bpf: arena: reschedule while freeing the arena pages
@ 2026-08-24 16:54 Breno Leitao
2026-08-24 17:08 ` sashiko-bot
2026-08-24 17:48 ` bot+bpf-ci
0 siblings, 2 replies; 6+ messages in thread
From: Breno Leitao @ 2026-08-24 16:54 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Barret Rhoden
Cc: bpf, linux-kernel, kernel-team, Breno Leitao
On non-preemptible kernels that is long enough to trip both the RCU
stall detector and the softlockup watchdog. Observed on production arm64
hosts at Meta:
rcu: INFO: rcu_sched self-detected stall on CPU
rcu: 37-....: (20999 ticks this GP) idle=3834/1/0x4000000000000000
Workqueue: events_unbound bpf_map_free_deferred
Call trace:
__apply_to_page_range+0x404/0x518
apply_to_existing_page_range+0x18/0x28
arena_map_free+0x54/0x98
bpf_map_free_deferred+0x70/0xf8
process_scheduled_works+0x17c/0x400
worker_thread+0x198/0x498
[...]
watchdog: BUG: soft lockup - CPU#37 stuck for 44s! [kworker/u288:15]
Kernel panic - not syncing: softlockup: hung tasks
arena_map_free() sweeps the whole 4GiB arena address range in a single
apply_to_existing_page_range() call and drops every populated page from
the walk callback.
Give up the CPU after releasing each page, using cond_resched().
Fixes: 317460317a02 ("bpf: Introduce bpf_arena.")
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/bpf/arena.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 7b6847200b431..c5f6bebbb15c3 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -364,6 +364,7 @@ static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)
* the freed page. So it all relies on 1.
*/
__free_page(page);
+ cond_resched();
return 0;
}
---
base-commit: 0a0d1d55dad570724bf8c7ea83409639cfb4be9b
change-id: 20260824-b4-bpf-arena-free-resched-e20ba0eb8919
Best regards,
--
Breno Leitao <leitao@debian.org>
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH] bpf: arena: reschedule while freeing the arena pages
2026-08-24 16:54 [PATCH] bpf: arena: reschedule while freeing the arena pages Breno Leitao
@ 2026-08-24 17:08 ` sashiko-bot
2026-08-25 14:06 ` Breno Leitao
2026-08-24 17:48 ` bot+bpf-ci
1 sibling, 1 reply; 6+ messages in thread
From: sashiko-bot @ 2026-08-24 17:08 UTC (permalink / raw)
To: Breno Leitao; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Calling cond_resched() inside a pte_fn_t callback violates the lazy_mmu_mode contract, causing 'scheduling while atomic' bugs and failing to resolve softlockups on architectures where lazy MMU mode disables preemption.
--
--- Patch [1]: [PATCH] bpf: arena: reschedule while freeing the arena pages ---
commit aff4ff38f5c4bf3ce9d58e40df7c294fa156e787
Author: Breno Leitao <leitao@debian.org>
bpf: arena: reschedule while freeing the arena pages
This commit adds a cond_resched() call to the page freeing callback used
when tearing down a BPF arena to prevent softlockups.
> diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
> index 7b6847200b431..c5f6bebbb15c3 100644
> --- a/kernel/bpf/arena.c
> +++ b/kernel/bpf/arena.c
> @@ -364,6 +364,7 @@ static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)
> * the freed page. So it all relies on 1.
> */
> __free_page(page);
> + cond_resched();
[Severity: High]
Does adding cond_resched() here violate the lazy MMU mode contract?
The callback existing_page_cb() is a pte_fn_t called by apply_to_pte_range(),
which establishes an atomic context by wrapping the callback loop in
lazy_mmu_mode_enable():
mm/memory.c:apply_to_pte_range() {
...
lazy_mmu_mode_enable();
if (fn) {
do {
if (create || !pte_none(ptep_get(pte))) {
err = fn(pte, addr, data);
...
}
On architectures like PowerPC64 with hash MMUs, lazy_mmu_mode_enable()
explicitly disables preemption. Since preemption is disabled, cond_resched()
evaluates to a no-op, which means it won't actually yield the CPU and will
fail to resolve the original softlockup.
Additionally, if CONFIG_DEBUG_ATOMIC_SLEEP is enabled, this could trigger a
"sleeping function called from invalid context" bug during teardown.
> return 0;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260824-b4-bpf-arena-free-resched-v1-1-5c447477a64b@debian.org?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH] bpf: arena: reschedule while freeing the arena pages
2026-08-24 17:08 ` sashiko-bot
@ 2026-08-25 14:06 ` Breno Leitao
2026-08-25 16:35 ` Alexei Starovoitov
0 siblings, 1 reply; 6+ messages in thread
From: Breno Leitao @ 2026-08-25 14:06 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On Mon, Aug 24, 2026 at 05:08:30PM +0000, sashiko-bot@kernel.org wrote:
> > diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
> > index 7b6847200b431..c5f6bebbb15c3 100644
> > --- a/kernel/bpf/arena.c
> > +++ b/kernel/bpf/arena.c
> > @@ -364,6 +364,7 @@ static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)
> > * the freed page. So it all relies on 1.
> > */
> > __free_page(page);
> > + cond_resched();
>
> [Severity: High]
> Does adding cond_resched() here violate the lazy MMU mode contract?
>
> The callback existing_page_cb() is a pte_fn_t called by apply_to_pte_range(),
> which establishes an atomic context by wrapping the callback loop in
> lazy_mmu_mode_enable():
>
> mm/memory.c:apply_to_pte_range() {
> ...
> lazy_mmu_mode_enable();
> if (fn) {
> do {
> if (create || !pte_none(ptep_get(pte))) {
> err = fn(pte, addr, data);
> ...
> }
>
> On architectures like PowerPC64 with hash MMUs, lazy_mmu_mode_enable()
> explicitly disables preemption. Since preemption is disabled, cond_resched()
> evaluates to a no-op, which means it won't actually yield the CPU and will
> fail to resolve the original softlockup.
>
> Additionally, if CONFIG_DEBUG_ATOMIC_SLEEP is enabled, this could trigger a
> "sleeping function called from invalid context" bug during teardown.
That is a good point, I think a better approach would be to drop the
cond_resched() from the callback -- existing_page_cb() goes back to its
original form.
Then we walks the range in chunks from arena_map_free() instead, so the
resched happens outside the lazy MMU section (FREE_CHUNK_SZ is SZ_64M):
addr = bpf_arena_get_kern_vm_start(arena);
end = addr + SZ_4G + GUARD_SZ / 2;
while (addr < end) {
u64 size = min_t(u64, end - addr, FREE_CHUNK_SZ);
apply_to_existing_page_range(&init_mm, addr, size,
existing_page_cb, arena);
cond_resched();
addr += size;
}
Would this be a better approach?
--breno
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH] bpf: arena: reschedule while freeing the arena pages
2026-08-25 14:06 ` Breno Leitao
@ 2026-08-25 16:35 ` Alexei Starovoitov
2026-08-26 10:03 ` Breno Leitao
0 siblings, 1 reply; 6+ messages in thread
From: Alexei Starovoitov @ 2026-08-25 16:35 UTC (permalink / raw)
To: Breno Leitao; +Cc: sashiko-reviews, bpf
On Tue, Aug 25, 2026 at 7:29 AM Breno Leitao <leitao@debian.org> wrote:
>
> On Mon, Aug 24, 2026 at 05:08:30PM +0000, sashiko-bot@kernel.org wrote:
> > > diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
> > > index 7b6847200b431..c5f6bebbb15c3 100644
> > > --- a/kernel/bpf/arena.c
> > > +++ b/kernel/bpf/arena.c
> > > @@ -364,6 +364,7 @@ static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)
> > > * the freed page. So it all relies on 1.
> > > */
> > > __free_page(page);
> > > + cond_resched();
> >
> > [Severity: High]
> > Does adding cond_resched() here violate the lazy MMU mode contract?
> >
> > The callback existing_page_cb() is a pte_fn_t called by apply_to_pte_range(),
> > which establishes an atomic context by wrapping the callback loop in
> > lazy_mmu_mode_enable():
> >
> > mm/memory.c:apply_to_pte_range() {
> > ...
> > lazy_mmu_mode_enable();
> > if (fn) {
> > do {
> > if (create || !pte_none(ptep_get(pte))) {
> > err = fn(pte, addr, data);
> > ...
> > }
> >
> > On architectures like PowerPC64 with hash MMUs, lazy_mmu_mode_enable()
> > explicitly disables preemption. Since preemption is disabled, cond_resched()
> > evaluates to a no-op, which means it won't actually yield the CPU and will
> > fail to resolve the original softlockup.
> >
> > Additionally, if CONFIG_DEBUG_ATOMIC_SLEEP is enabled, this could trigger a
> > "sleeping function called from invalid context" bug during teardown.
>
> That is a good point, I think a better approach would be to drop the
> cond_resched() from the callback -- existing_page_cb() goes back to its
> original form.
>
> Then we walks the range in chunks from arena_map_free() instead, so the
> resched happens outside the lazy MMU section (FREE_CHUNK_SZ is SZ_64M):
>
> addr = bpf_arena_get_kern_vm_start(arena);
> end = addr + SZ_4G + GUARD_SZ / 2;
> while (addr < end) {
> u64 size = min_t(u64, end - addr, FREE_CHUNK_SZ);
>
> apply_to_existing_page_range(&init_mm, addr, size,
> existing_page_cb, arena);
> cond_resched();
> addr += size;
> }
>
> Would this be a better approach?
Just to be clear.. Is this needed to backport to some older kernel?
New kernel with CONFIG_PREEMPT_LAZY=y shouldn't need
this old practice of sprinkling cond_resched().
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH] bpf: arena: reschedule while freeing the arena pages
2026-08-25 16:35 ` Alexei Starovoitov
@ 2026-08-26 10:03 ` Breno Leitao
0 siblings, 0 replies; 6+ messages in thread
From: Breno Leitao @ 2026-08-26 10:03 UTC (permalink / raw)
To: Alexei Starovoitov; +Cc: sashiko-reviews, bpf
Hello Alexei,
On Tue, Aug 25, 2026 at 09:35:03AM -0700, Alexei Starovoitov wrote:
> Just to be clear.. Is this needed to backport to some older kernel?
> New kernel with CONFIG_PREEMPT_LAZY=y shouldn't need
> this old practice of sprinkling cond_resched().
Yes, this is happening on our Meta kernel, and it is crashing a lot on old
kernel in production.
It does crash a lot for us: 56780 crashes over 1538 hosts in the last 30
days, 41% of all our kernel crashes in that window. But it stops dead
at 6.13:
6.11.1 26060 crashes / 160 hosts
6.13.2 26014 crashes / 1119 hosts
Looking further, it seems that commit 517e8a7835e8c ("bpf: Fix softlockup in
arena_map_free on 64k page kernel") is what is what makes the
difference, which landed in v6.14. Meta's arm64 kernels use 64k pages,
so before that commit GUARD_SZ / 2 is 32768 and
bpf_arena_get_kern_vm_start() is not page aligned
So there is nothing to fix upstream, and nothing to fix in the teardown
loop. We just need 517e8a7835e8c in our 6.11 and 6.13 branches.
Sorry for the noise
Thanks,
--
pw-bot: rejected
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] bpf: arena: reschedule while freeing the arena pages
2026-08-24 16:54 [PATCH] bpf: arena: reschedule while freeing the arena pages Breno Leitao
2026-08-24 17:08 ` sashiko-bot
@ 2026-08-24 17:48 ` bot+bpf-ci
1 sibling, 0 replies; 6+ messages in thread
From: bot+bpf-ci @ 2026-08-24 17:48 UTC (permalink / raw)
To: leitao, ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, brho
Cc: bpf, linux-kernel, kernel-team, leitao, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1608 bytes --]
> bpf: arena: reschedule while freeing the arena pages
>
> On non-preemptible kernels that is long enough to trip both the RCU
> stall detector and the softlockup watchdog. Observed on production arm64
> hosts at Meta:
>
> rcu: INFO: rcu_sched self-detected stall on CPU
> rcu: 37-....: (20999 ticks this GP) idle=3834/1/0x4000000000000000
> Workqueue: events_unbound bpf_map_free_deferred
> Call trace:
> __apply_to_page_range+0x404/0x518
> apply_to_existing_page_range+0x18/0x28
> arena_map_free+0x54/0x98
> bpf_map_free_deferred+0x70/0xf8
> process_scheduled_works+0x17c/0x400
> worker_thread+0x198/0x498
> [...]
> watchdog: BUG: soft lockup - CPU#37 stuck for 44s! [kworker/u288:15]
> Kernel panic - not syncing: softlockup: hung tasks
>
> arena_map_free() sweeps the whole 4GiB arena address range in a single
> apply_to_existing_page_range() call and drops every populated page from
> the walk callback.
>
> Give up the CPU after releasing each page, using cond_resched().
This isn't a bug, but the opening sentence "On non-preemptible kernels
that is long enough ..." uses "that" without an antecedent. The phrase
that explains what takes long enough (the 4GiB arena sweep) appears two
paragraphs later, after the stall trace.
Would moving the arena_map_free() description ahead of the stall trace
make the flow clearer?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32755827346
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-08-26 10:03 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-24 16:54 [PATCH] bpf: arena: reschedule while freeing the arena pages Breno Leitao
2026-08-24 17:08 ` sashiko-bot
2026-08-25 14:06 ` Breno Leitao
2026-08-25 16:35 ` Alexei Starovoitov
2026-08-26 10:03 ` Breno Leitao
2026-08-24 17:48 ` bot+bpf-ci
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox