Re: 7.2-rc1 regression Folio lock leak in writepage_delalloc()
Qu Wenruo <[email protected]> Wed, 22 Jul 2026 16:51:49 +0930
| Newsgroups | org.kernel.vger.linux-btrfs,org.kernel.vger.kvm,org.kernel.vger.linux-kernel,org.kernel.vger.linux-s390 |
|---|---|
| Message-ID | <[email protected]> |
在 2026/7/22 04:41, Christian Borntraeger 写道: > We have seen random hangs in our daily CI run where qemu/KVM > processes deadlocks guests with file-backed RAM on btrfs (large data folios) > > With the help of claude I think we found the/one problem on an s390 > KVM host running 7.2.0-rc3 (KASAN test kernel, but the issue is not > KASAN related). And to be honest here, most of the writeup was created > by claude and I added things where appropriate. Also the patch was > mostly done with the help of claude. > > A KVM guest with its RAM backed by a file on btrfs (zstd compression > enabled) locked up together with the host's writeback: two vCPU > threads, an irqfd worker, two flusher workers, khugepaged and a > syncfs caller (dnf) were all stuck in D state for hours. Analysis > of the crash dump shows a leaked folio lock in btrfs' > writepage_delalloc(); a proposed fix is in the reply mail. > > I still need to verify that this patches fixes the deadlock in our > CI but wanted some feedback first. > > Dump analysis (shortened) > ------------------------- > All blocked tasks funnel into one 64-page (256K) large data folio of > the guest RAM file: > > folio 0x800083fb000, inode 1881035 (the 1.25G s390.ram file) > flags: PG_locked | PG_waiters | PG_dirty | PG_private | PG_uptodate > (PG_writeback NOT set) > btrfs_folio_state: nr_locked == 0, subpage dirty bitmap empty > still mapped (63/64 PTEs) and on the LRU, no outstanding block I/O > > Waiters on that folio lock: > - 2 vCPU threads + 1 irqfd kworker, all in > btrfs_page_mkwrite() -> folio_lock, holding mmap_lock (read) > crash> bt 66448 > PID: 66448 TASK: 9e934a00 CPU: 9 COMMAND: "CPU 1/KVM" > #0 [b8b25dbe7d8] __schedule at c0b186d5e78 > #1 [b8b25dbe908] schedule at c0b186d7040 > #2 [b8b25dbe948] io_schedule at c0b186d723c > #3 [b8b25dbe978] folio_wait_bit_common at c0b167e719c > #4 [b8b25dbeaf0] btrfs_page_mkwrite at c0b172816fc > #5 [b8b25dbec98] do_page_mkwrite at c0b168a4ada > #6 [b8b25dbecf0] do_wp_page at c0b168b2350 > #7 [b8b25dbed70] handle_pte_fault at c0b168bfaf4 > #8 [b8b25dbee58] __handle_mm_fault at c0b168c003e > #9 [b8b25dbefc0] handle_mm_fault at c0b168c09b6 > #10 [b8b25dbf020] __get_user_pages at c0b16899cfc > #11 [b8b25dbf148] get_user_pages_unlocked at c0b1689af1c > #12 [b8b25dbf248] hva_to_pfn at c0a9711e20e [kvm] > #13 [b8b25dbf3f0] __kvm_faultin_pfn at c0a9711ea26 [kvm] > #14 [b8b25dbf4e8] kvm_s390_faultin_gfn at c0a971c092c [kvm] > #15 [b8b25dbf5f8] vcpu_post_run_handle_fault at c0a97148b5e [kvm] > #16 [b8b25dbf6f0] __vcpu_run at c0a9715c1f2 [kvm] > #17 [b8b25dbf808] kvm_arch_vcpu_ioctl_run at c0a9715d3e4 [kvm] > #18 [b8b25dbfbb8] kvm_vcpu_ioctl at c0a97117bd8 [kvm] > #19 [b8b25dbfdd8] __s390x_sys_ioctl at c0b16aa3614 > #20 [b8b25dbfe40] __do_syscall at c0b186cdaee > #21 [b8b25dbfe98] system_call at c0b186ebd42 > USER-MODE INTERRUPT FRAME; pt_regs at b8b25dbff38: > PSW: 0705000180000000 000003ff8a92662c (user space) > GPRS: 000003ff627faf50 0000000000000036 ffffffffffffffda 000000000000ae80 > 0000000000000000 000003ff627fc8c0 000002aa1f8a7880 000003ff8a8ad310 > 000002aa1e1f3c60 0000000000000000 000000000000ae80 000002aa1f8a2f60 > 000003ff8d3adfa8 000003ff627fc8c0 000003ff627faff0 000003ff627fae88 > > > - flusher: extent_write_cache_pages() -> folio_lock > - delalloc space reclaim worker: same, while holding > fs_info->delalloc_root_mutex (which in turn blocks > btrfs_async_reclaim_metadata_space on the mutex) > Behind those: khugepaged in down_write(mmap_lock), and syncfs. > > No task in the system owns the folio lock; nothing references the > folio except the six waiters. The lock was leaked. > > Root cause > ---------- > A folio can carry the folio-level dirty flag with an EMPTY btrfs > subpage dirty bitmap. btrfs data mappings use filemap_dirty_folio(), > so a generic folio_mark_dirty() sets only the folio flag and xarray > tag - no subpage dirty bits, no delalloc reservation. On s390 this > happens all the time: the KVM irq adapter path > (arch/s390/kvm/interrupt.c, adapter_indicators_set()) pins the guest > interrupt indicator page with pin_user_pages_remote(FOLL_WRITE), > sets the indicator bit and calls set_page_dirty_lock(). Once a > previously written folio has gone through one complete writeback > cycle (subpage dirty bitmap empty again), the next adapter interrupt > re-dirties it with only the folio flag. No, that's not how things should work. I have explained the problem in the RFC patch. I am only going to add some extra explanation inlined below. > > Writeback then does: > > extent_write_cache_pages(): folio_lock(), folio is dirty -> proceed > extent_writepage() -> writepage_delalloc(): > - btrfs_copy_subpage_dirty_bitmap() -> submit_bitmap is EMPTY > - the btrfs_folio_set_lock() loop sets nothing (nr_locked stays 0) > - find_lock_delalloc_range() finds nothing -> goto out > - out: bitmap_empty(submit_bitmap) is true -> return 1 > > The "return 1" path means "all dirty ranges were submitted > asynchronously, the async submission owns the folio unlock" - but > nothing was submitted, so extent_writepage() returns and the folio > stays locked forever. This matches every flag of the dump folio > (locked, dirty, nr_locked == 0, no writeback, still mapped/LRU). > > Verifying this in the dump: > > - uptodate = 0xffffffffffffffff — all 64 blocks uptodate (consistent with PG_uptodate) > - dirty = 0x0 — the subpage dirty bitmap is EMPTY, exactly as the root cause predicts > - writeback = 0x0 — no writeback in flight (consistent with PG_writeback clear) > > > Exposure > -------- > - Single-block folios are immune: btrfs_copy_subpage_dirty_bitmap() > unconditionally reports bit 0 for blocks_per_folio == 1. Unfortunately no. One of the biggest problem is, all the other things, from extent map to ordered extent are not properly prepared. E.g. even if btrfs_copy_subpage_dirty_bitmap() returns bit 0 set, later EXTENT_DELALLOC is not set. So find_lock_delalloc_range() will return false, and since we found no delalloc range, @last_delalloc_end is still zero, we goto out label, without creating any ordered extent/extent map. Then we go into extent_writepage_io(), which will rely on the extent_map created by run_delalloc_range() for IO submission. But since we have no OE/EM created, we will grab one from on-disk metadata, and if the original on-disk metadata shows there is a hole, we will trigger the ASSERT() inside submit_one_sector(), about the EM is a hole. Before v7.2-rc1, we have a lot of extra handling (folio ordered flag) to exactly catch such situation. But since we haven't really hit such case anymore for a while, in v7.2-rc we also remove the that flag, otherwise it should catch such problem much earilier. > - Subpage setups (e.g. 64K page size with 4K sectorsize) have been > exposed since the submission bitmap rework in v6.12 > (bd610c0937aa "btrfs: only unlock the to-be-submitted ranges > inside a folio"). > - 4K page size systems became exposed with large data folio support > in v7.2-rc1, which routes every large folio through the subpage > machinery. That is why we only started seeing this now. > > Any GUP-style dirtier can trigger it (KVM adapter interrupts on > s390, vfio, RDMA, io_uring fixed buffers, ...) as long as the target > is a multi-block folio of a btrfs data mapping that was clean at the > time of set_page_dirty_lock(). > > Reproducer outline: KVM guest on s390 with memory-backend-file on > btrfs + virtio devices using irqfd adapter indicators; I strongly doubt if it's a specific S390 feature breaking the assumption. As io uring is also heavily tested, and IIRC there is already a huge GUP work to address the long existing unexpected dirty page behavior in v5.15. So I strongly doubt if it's some S390x feature not properly following the existing scheme. At least on both x86_64 and arm64 (64K page size), since the introduction of experimental large folios, I haven't seen something similar like this. If it's S390X specific, then I do not think it's something we can handle by ourselves. Thanks, Qu > hangs within > ~25 minutes of guest uptime in our setup. A targeted reproducer > should also work on x86: mmap a file on btrfs, write it, fsync, let > writeback finish, then pin_user_pages(FOLL_WRITE) + > set_page_dirty_lock() on a page of a large folio and trigger sync. > > Proposed fix > ------------ > Detect the empty-at-entry bitmap right after it has been copied, > before any range lock is set up, clear the stale folio dirty flag > (nothing can ever be written back for it; all dirty flag setters > serialize on the folio lock we hold) and unlock the folio. Patch > attached below; it survives our compile test and we are preparing a > test run on the affected machine. Comments welcome - especially on > whether clearing the folio dirty flag is the desired semantic here, > versus e.g. routing such folios through the cow fixup worker to > actually persist GUP-written data. > > Thanks > > Christian > > > > >