[PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back

Christian Borntraeger posted 1 patch 3 days, 4 hours ago
[PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Christian Borntraeger 3 days, 4 hours ago
A folio can carry the folio-level dirty flag while its btrfs subpage
dirty bitmap is empty: btrfs data mappings use filemap_dirty_folio(),
so a generic folio_mark_dirty() call sets only the folio flag and the
xarray tag, without setting any subpage dirty bit and without a
delalloc reservation.  The typical source is set_page_dirty_lock() on
a GUP pin, e.g. the s390 KVM irq adapter path
(adapter_indicators_set()) which pins guest indicator pages living in
a file-backed guest RAM file, sets a bit and marks the page dirty.

When writeback then picks up such a folio, writepage_delalloc()
copies the empty subpage dirty bitmap into
bio_ctrl->submit_bitmap, sets up no range locks (nr_locked stays 0),
finds no delalloc range, and finally hits

	if (bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio)) {
		wbc->nr_to_write -= delalloc_to_write;
		return 1;
	}

which is meant for "all dirty ranges were submitted asynchronously,
the async paths own the folio unlock".  But nothing was submitted at
all, so extent_writepage() returns without anybody ever unlocking the
folio.  The folio stays locked forever and every subsequent locker
(page faults through btrfs_page_mkwrite(), other flushers, delalloc
space reclaim which then parks holding fs_info->delalloc_root_mutex,
syncfs, ...) blocks in D state.

This was debugged from a crash dump of a hung s390 KVM host: a KVM
guest with its RAM backed by a file on btrfs (zstd compression),
where a 64-page (256K) large data folio of the guest RAM file was
found locked and dirty, with an empty subpage dirty bitmap,
nr_locked == 0, no PG_writeback set and no outstanding block I/O,
with two vCPU threads, the irqfd worker, two flusher workers,
khugepaged and syncfs all queued behind it.

Small folios are not affected because
btrfs_copy_subpage_dirty_bitmap() unconditionally reports bit 0 set
for single-block folios.  Affected are subpage setups (sectorsize <
PAGE_SIZE, e.g. 64K page size kernels with 4K sectorsize) since the
introduction of the submission bitmap in v6.12, and - much easier to
hit - 4K page size systems since btrfs gained large data folio
support, which makes every large folio take the subpage paths.

Fix it by detecting the empty-at-entry case right after the dirty
bitmap has been copied, before any range lock is set up: there is
nothing that can be submitted for such a folio, so clear the stale
folio-level dirty flag (nothing will ever be written back for it,
and all dirty flag setters serialize on the folio lock we hold, so
this cannot race with a new dirtier) and unlock the folio.  Since
folio_clear_dirty_for_io() intentionally leaves PAGECACHE_TAG_DIRTY
in the xarray, also run the same set/clear writeback dance that
extent_writepage_io() uses for the submitted-nothing case, so the
stale tag is dropped and the inode can go clean again.

The data written through the GUP pin is not lost; it sits in the
mapped page cache page.  It is simply not persisted until a proper
btrfs write path dirties the folio again - the same long-standing
semantics as any pin_user_pages() write to a file mapping that the
filesystem was not informed about.

Fixes: bd610c0937aa ("btrfs: only unlock the to-be-submitted ranges inside a folio")
Assisted-by: Claude 
Signed-off-by: Christian Borntraeger <borntraeger@linux.ibm.com>
---
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 7d604524e83c3..6a4a00ad43321 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -1492,6 +1492,33 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
 	/* Save the dirty bitmap as our submission bitmap will be a subset of it. */
 	btrfs_copy_subpage_dirty_bitmap(fs_info, folio, bio_ctrl->submit_bitmap);
 
+	/*
+	 * The dirty bitmap can be empty even though the folio is dirty: data
+	 * mappings use filemap_dirty_folio(), so a generic folio_mark_dirty()
+	 * call (e.g. set_page_dirty_lock() after GUP) only sets the folio
+	 * flag, without any subpage dirty bit nor a delalloc reservation.
+	 *
+	 * There is nothing to submit for such a folio.  Bail out now,
+	 * otherwise the bitmap_empty() check at the end would mistake it for
+	 * "all ranges submitted asynchronously" and return with the folio
+	 * lock never released, deadlocking every subsequent locker.
+	 *
+	 * Also clear the stale dirty flag: with no subpage dirty bits nothing
+	 * will ever be written back for it, and leaving the flag would make
+	 * writeback rescan the folio forever.  All dirty flag setters hold
+	 * the folio lock, which we own, so this cannot race with a new
+	 * dirtier.  As folio_clear_dirty_for_io() keeps PAGECACHE_TAG_DIRTY,
+	 * use the same set/clear writeback dance as extent_writepage_io() to
+	 * also drop the stale tag, otherwise the inode would never go clean.
+	 */
+	if (unlikely(bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio))) {
+		folio_clear_dirty_for_io(folio);
+		btrfs_folio_set_writeback(fs_info, folio, page_start, folio_size(folio));
+		btrfs_folio_clear_writeback(fs_info, folio, page_start, folio_size(folio));
+		folio_unlock(folio);
+		return 1;
+	}
+
 	for_each_set_bitrange(start_bit, end_bit, bio_ctrl->submit_bitmap,
 			      blocks_per_folio) {
 		u64 start = page_start + (start_bit << fs_info->sectorsize_bits);
-- 
2.51.0
Re: [PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Qu Wenruo 3 days, 2 hours ago

在 2026/7/22 04:41, Christian Borntraeger 写道:
> A folio can carry the folio-level dirty flag while its btrfs subpage
> dirty bitmap is empty: btrfs data mappings use filemap_dirty_folio(),
> so a generic folio_mark_dirty() call sets only the folio flag and the
> xarray tag, without setting any subpage dirty bit and without a
> delalloc reservation.  The typical source is set_page_dirty_lock() on
> a GUP pin,

Shouldn't such folio got its ->page_mkwrite() callback get called first?


> e.g. the s390 KVM irq adapter path
> (adapter_indicators_set()) which pins guest indicator pages living in
> a file-backed guest RAM file, sets a bit and marks the page dirty.

Thus I think this is a bigger problem for S390.

> 
> When writeback then picks up such a folio, writepage_delalloc()
> copies the empty subpage dirty bitmap into
> bio_ctrl->submit_bitmap, sets up no range locks (nr_locked stays 0),
> finds no delalloc range, and finally hits
> 
> 	if (bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio)) {
> 		wbc->nr_to_write -= delalloc_to_write;
> 		return 1;
> 	}
> 
> which is meant for "all dirty ranges were submitted asynchronously,
> the async paths own the folio unlock".  But nothing was submitted at
> all, so extent_writepage() returns without anybody ever unlocking the
> folio.  The folio stays locked forever and every subsequent locker
> (page faults through btrfs_page_mkwrite(), other flushers, delalloc
> space reclaim which then parks holding fs_info->delalloc_root_mutex,
> syncfs, ...) blocks in D state.
> 
> This was debugged from a crash dump of a hung s390 KVM host: a KVM
> guest with its RAM backed by a file on btrfs (zstd compression),
> where a 64-page (256K) large data folio of the guest RAM file was
> found locked and dirty, with an empty subpage dirty bitmap,
> nr_locked == 0, no PG_writeback set and no outstanding block I/O,
> with two vCPU threads, the irqfd worker, two flusher workers,
> khugepaged and syncfs all queued behind it.
> 
> Small folios are not affected because
> btrfs_copy_subpage_dirty_bitmap() unconditionally reports bit 0 set
> for single-block folios.

Unfortunately this means btrfs can get a dirty folio that is dirtied 
without notifying the fs.

This will trigger a lot of other warnings, e.g. space reservation problems.

Furthermore, such dirtying folio without notifying the fs behavior can 
also lead to problems in other fses.

IIRC ext4 will give warning for such cases too.

So I do not think this is the correct fix, but something went totally 
wrong in the S390 GUP behavior that allows a fs folio to be marked dirty 
without calling page_mkwrite().

Thanks,
Qu
Re: [PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Christian Borntraeger 2 days, 14 hours ago
Am 21.07.26 um 23:07 schrieb Qu Wenruo:

First, thank you for taking the time to look into this and trying to understand
things and trying to explain things. this is highly appreciated.
I am still trying to fully understand this myself. A question:

> 在 2026/7/22 04:41, Christian Borntraeger 写道:
>> A folio can carry the folio-level dirty flag while its btrfs subpage
>> dirty bitmap is empty: btrfs data mappings use filemap_dirty_folio(),
>> so a generic folio_mark_dirty() call sets only the folio flag and the
>> xarray tag, without setting any subpage dirty bit and without a
>> delalloc reservation.  The typical source is set_page_dirty_lock() on
>> a GUP pin,
> 
> Shouldn't such folio got its ->page_mkwrite() callback get called first?

Isnt that called implicitely at pin time?
At unpin, set_page_dirty_lock() re-dirties the now-clean folio.
 From what I can see, this is not an s390 invention but a sanctioned pattern.

But I can certainly not exclude that this is still an s390 specific problem.
Let me dig a bit deeper.

Thanks again for your quick response.

Christian
Re: [PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Qu Wenruo 2 days, 14 hours ago

在 2026/7/22 18:05, Christian Borntraeger 写道:
> Am 21.07.26 um 23:07 schrieb Qu Wenruo:
> 
> First, thank you for taking the time to look into this and trying to 
> understand
> things and trying to explain things. this is highly appreciated.
> I am still trying to fully understand this myself. A question:
> 
>> 在 2026/7/22 04:41, Christian Borntraeger 写道:
>>> A folio can carry the folio-level dirty flag while its btrfs subpage
>>> dirty bitmap is empty: btrfs data mappings use filemap_dirty_folio(),
>>> so a generic folio_mark_dirty() call sets only the folio flag and the
>>> xarray tag, without setting any subpage dirty bit and without a
>>> delalloc reservation.  The typical source is set_page_dirty_lock() on
>>> a GUP pin,
>>
>> Shouldn't such folio got its ->page_mkwrite() callback get called first?
> 
> Isnt that called implicitely at pin time?

I have to admit, I'm not an expert on the MM part, I'm mostly a simple 
user of the existing MM interfaces.

AFAIK, the last time I brought this thing up, Christoph mentioned that 
dirtying-folio-without-notifying-fs is a bug, and fs should not and is 
not able to handle such situation anyway.

And that idea makes a lot of sense to me.

So adding MM list for more help.

Thanks,
Qu

> At unpin, set_page_dirty_lock() re-dirties the now-clean folio.
>  From what I can see, this is not an s390 invention but a sanctioned 
> pattern.
> 
> But I can certainly not exclude that this is still an s390 specific 
> problem.
> Let me dig a bit deeper.
> 
> Thanks again for your quick response.
> 
> Christian
> 
Re: [PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Christian Borntraeger 2 days, 14 hours ago
Am 22.07.26 um 10:59 schrieb Qu Wenruo:
> 
> 
> 在 2026/7/22 18:05, Christian Borntraeger 写道:
>> Am 21.07.26 um 23:07 schrieb Qu Wenruo:
>>
>> First, thank you for taking the time to look into this and trying to understand
>> things and trying to explain things. this is highly appreciated.
>> I am still trying to fully understand this myself. A question:
>>
>>> 在 2026/7/22 04:41, Christian Borntraeger 写道:
>>>> A folio can carry the folio-level dirty flag while its btrfs subpage
>>>> dirty bitmap is empty: btrfs data mappings use filemap_dirty_folio(),
>>>> so a generic folio_mark_dirty() call sets only the folio flag and the
>>>> xarray tag, without setting any subpage dirty bit and without a
>>>> delalloc reservation.  The typical source is set_page_dirty_lock() on
>>>> a GUP pin,
>>>
>>> Shouldn't such folio got its ->page_mkwrite() callback get called first?
>>
>> Isnt that called implicitely at pin time?
> 
> I have to admit, I'm not an expert on the MM part, I'm mostly a simple user of the existing MM interfaces.
> 
> AFAIK, the last time I brought this thing up, Christoph mentioned that dirtying-folio-without-notifying-fs is a bug, and fs should not and is not able to handle such situation anyway.
> 
> And that idea makes a lot of sense to me.
> 
> So adding MM list for more help.
So I now have an userspace O_DIRECT reproducer outside of KVM. (attached) which gave me (on an s390 system, though)

[  189.067731] sysrq: Show Blocked State
[  189.067872] task:kworker/u1665:5 state:D stack:0     pid:1363  tgid:1363  ppid:2      task_flags:0x4208060 flags:0x00000000
[  189.067877] Workqueue: writeback wb_workfn (flush-btrfs-1)
[  189.067884] Call Trace:
[  189.067886]  [<000003a8b2e19284>] __schedule+0x374/0x900
[  189.067891]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
[  189.067894]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
[  189.067896]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
[  189.067902]  [<000003a8b263e2e8>] extent_write_cache_pages+0x348/0x4d0
[  189.067907]  [<000003a8b263ea0a>] btrfs_writepages+0x6a/0xd0
[  189.067910]  [<000003a8b21ad444>] do_writepages+0xd4/0x190
[  189.067915]  [<000003a8b2324590>] __writeback_single_inode+0x50/0x310
[  189.067917]  [<000003a8b2324b1a>] writeback_sb_inodes+0x2ca/0x690
[  189.067919]  [<000003a8b2324f36>] __writeback_inodes_wb+0x56/0x140
[  189.067921]  [<000003a8b2325498>] wb_writeback+0x368/0x460
[  189.067923]  [<000003a8b23258c6>] wb_do_writeback+0x336/0x3d0
[  189.067925]  [<000003a8b2325d2a>] wb_workfn+0x5a/0x1d0
[  189.067927]  [<000003a8b1f0cee2>] process_one_work+0x1d2/0x480
[  189.067932]  [<000003a8b1f0df30>] worker_thread+0x210/0x420
[  189.067935]  [<000003a8b1f190b8>] kthread+0x148/0x170
[  189.067939]  [<000003a8b1e90358>] __ret_from_fork+0x48/0x220
[  189.067941]  [<000003a8b2e2426a>] ret_from_fork+0xa/0x30
[  189.067994] task:oob-dirty-repro state:D stack:0     pid:8152  tgid:8151  ppid:7260   task_flags:0x400040 flags:0x00000800
[  189.067997] Call Trace:
[  189.067998]  [<000003a8b2e19284>] __schedule+0x374/0x900
[  189.068050]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
[  189.068052]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
[  189.068054]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
[  189.068057]  [<000003a8b263e2e8>] extent_write_cache_pages+0x348/0x4d0
[  189.068061]  [<000003a8b263ea0a>] btrfs_writepages+0x6a/0xd0
[  189.068063]  [<000003a8b21ad444>] do_writepages+0xd4/0x190
[  189.068066]  [<000003a8b2199dec>] filemap_writeback+0xbc/0x100
[  189.068069]  [<000003a8b2199e5a>] filemap_fdatawrite_range+0x2a/0x40
[  189.068072]  [<000003a8b2637d3c>] btrfs_start_ordered_extent_nowriteback+0x18c/0x1d0
[  189.068074]  [<000003a8b26260ce>] btrfs_page_mkwrite+0x22e/0x8d0
[  189.068079]  [<000003a8b21f89d0>] do_page_mkwrite+0x60/0xf0
[  189.068084]  [<000003a8b21ffa64>] do_wp_page+0x134/0x660
[  189.068086]  [<000003a8b2206584>] __handle_mm_fault+0x1c4/0x5a0
[  189.068088]  [<000003a8b22069f0>] handle_mm_fault+0x90/0x230
[  189.068090]  [<000003a8b1ebfbd0>] do_exception+0x190/0x560
[  189.068094]  [<000003a8b2e136c0>] __do_pgm_check+0x1b0/0x370
[  189.068098]  [<000003a8b2e243a4>] pgm_check_handler+0x114/0x160
[  189.068101]  [<000003a8b283135e>] _copy_to_iter+0x6e/0x870
[  189.068106]  [<000003a8b2831c4a>] copy_page_to_iter+0xea/0x160
[  189.068108]  [<000003a8b219ff8c>] filemap_read+0x1ec/0x400
[  189.068109]  [<000003a8b22d0a70>] vfs_read+0x210/0x370
[  189.068112]  [<000003a8b22d1694>] ksys_pread64+0xa4/0xd0
[  189.068114]  [<000003a8b2e13b7e>] __do_syscall+0x14e/0x590
[  189.068117]  [<000003a8b2e24242>] system_call+0x72/0x90
[  189.068120] task:oob-dirty-repro state:D stack:0     pid:8153  tgid:8151  ppid:7260   task_flags:0x400040 flags:0x00000000
[  189.068123] Call Trace:
[  189.068124]  [<000003a8b2e19284>] __schedule+0x374/0x900
[  189.068126]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
[  189.068128]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
[  189.068130]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
[  189.068133]  [<000003a8b263e2e8>] extent_write_cache_pages+0x348/0x4d0
[  189.068136]  [<000003a8b263ea0a>] btrfs_writepages+0x6a/0xd0
[  189.068138]  [<000003a8b21ad444>] do_writepages+0xd4/0x190
[  189.068141]  [<000003a8b2199dec>] filemap_writeback+0xbc/0x100
[  189.068144]  [<000003a8b2199e5a>] filemap_fdatawrite_range+0x2a/0x40
[  189.068146]  [<000003a8b2637d3c>] btrfs_start_ordered_extent_nowriteback+0x18c/0x1d0
[  189.068149]  [<000003a8b26260ce>] btrfs_page_mkwrite+0x22e/0x8d0
[  189.068152]  [<000003a8b21f89d0>] do_page_mkwrite+0x60/0xf0
[  189.068154]  [<000003a8b21ffa64>] do_wp_page+0x134/0x660
[  189.068156]  [<000003a8b2206584>] __handle_mm_fault+0x1c4/0x5a0
[  189.068158]  [<000003a8b22069f0>] handle_mm_fault+0x90/0x230
[  189.068160]  [<000003a8b1ebfbd0>] do_exception+0x190/0x560
[  189.068163]  [<000003a8b2e136c0>] __do_pgm_check+0x1b0/0x370
[  189.068165]  [<000003a8b2e243a4>] pgm_check_handler+0x114/0x160
[  189.068168]  [<000003a8b283135e>] _copy_to_iter+0x6e/0x870
[  189.068170]  [<000003a8b2831c4a>] copy_page_to_iter+0xea/0x160
[  189.068172]  [<000003a8b219ff8c>] filemap_read+0x1ec/0x400
[  189.068174]  [<000003a8b22d0a70>] vfs_read+0x210/0x370
[  189.068176]  [<000003a8b22d1694>] ksys_pread64+0xa4/0xd0
[  189.068178]  [<000003a8b2e13b7e>] __do_syscall+0x14e/0x590
[  189.068181]  [<000003a8b2e24242>] system_call+0x72/0x90
[  189.068184] task:oob-dirty-repro state:D stack:0     pid:8154  tgid:8151  ppid:7260   task_flags:0x400040 flags:0x00000000
[  189.068187] Call Trace:
[  189.068188]  [<000003a8b2e19284>] __schedule+0x374/0x900
[  189.068190]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
[  189.068192]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
[  189.068193]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
[  189.068196]  [<000003a8b26260fc>] btrfs_page_mkwrite+0x25c/0x8d0
[  189.068199]  [<000003a8b21f89d0>] do_page_mkwrite+0x60/0xf0
[  189.068202]  [<000003a8b21ffa64>] do_wp_page+0x134/0x660
[  189.068203]  [<000003a8b2206584>] __handle_mm_fault+0x1c4/0x5a0
[  189.068206]  [<000003a8b22069f0>] handle_mm_fault+0x90/0x230
[  189.068208]  [<000003a8b1ebfbd0>] do_exception+0x190/0x560
[  189.068210]  [<000003a8b2e136c0>] __do_pgm_check+0x1b0/0x370
[  189.068213]  [<000003a8b2e243a4>] pgm_check_handler+0x114/0x160
[  189.068215]  [<000003a8b283135e>] _copy_to_iter+0x6e/0x870
[  189.068218]  [<000003a8b2831c4a>] copy_page_to_iter+0xea/0x160
[  189.068219]  [<000003a8b219ff8c>] filemap_read+0x1ec/0x400
[  189.068221]  [<000003a8b22d0a70>] vfs_read+0x210/0x370
[  189.068223]  [<000003a8b22d1694>] ksys_pread64+0xa4/0xd0
[  189.068225]  [<000003a8b2e13b7e>] __do_syscall+0x14e/0x590
[  189.068228]  [<000003a8b2e24242>] system_call+0x72/0x90
[  189.068231] task:oob-dirty-repro state:D stack:0     pid:8155  tgid:8151  ppid:7260   task_flags:0x400040 flags:0x00000000
[  189.068234] Call Trace:
[  189.068235]  [<000003a8b2e19284>] __schedule+0x374/0x900
[  189.068237]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
[  189.068239]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
[  189.068241]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
[  189.068244]  [<000003a8b263e2e8>] extent_write_cache_pages+0x348/0x4d0
[  189.068246]  [<000003a8b263ea0a>] btrfs_writepages+0x6a/0xd0
[  189.068249]  [<000003a8b21ad444>] do_writepages+0xd4/0x190
[  189.068252]  [<000003a8b2199dec>] filemap_writeback+0xbc/0x100
[  189.068255]  [<000003a8b2199e5a>] filemap_fdatawrite_range+0x2a/0x40
[  189.068257]  [<000003a8b2637d3c>] btrfs_start_ordered_extent_nowriteback+0x18c/0x1d0
[  189.068260]  [<000003a8b26260ce>] btrfs_page_mkwrite+0x22e/0x8d0
[  189.068263]  [<000003a8b21f89d0>] do_page_mkwrite+0x60/0xf0
[  189.068265]  [<000003a8b21ffa64>] do_wp_page+0x134/0x660
[  189.068267]  [<000003a8b2206584>] __handle_mm_fault+0x1c4/0x5a0
[  189.068269]  [<000003a8b22069f0>] handle_mm_fault+0x90/0x230
[  189.068271]  [<000003a8b1ebfbd0>] do_exception+0x190/0x560
[  189.068274]  [<000003a8b2e136c0>] __do_pgm_check+0x1b0/0x370
[  189.068277]  [<000003a8b2e243a4>] pgm_check_handler+0x114/0x160
[  189.068279]  [<000003a8b283135e>] _copy_to_iter+0x6e/0x870
[  189.068281]  [<000003a8b2831c4a>] copy_page_to_iter+0xea/0x160
[  189.068283]  [<000003a8b219ff8c>] filemap_read+0x1ec/0x400
[  189.068285]  [<000003a8b22d0a70>] vfs_read+0x210/0x370
[  189.068287]  [<000003a8b22d1694>] ksys_pread64+0xa4/0xd0
[  189.068289]  [<000003a8b2e13b7e>] __do_syscall+0x14e/0x590
[  189.068292]  [<000003a8b2e24242>] system_call+0x72/0x90
[  189.068294] task:oob-dirty-repro state:D stack:0     pid:8156  tgid:8151  ppid:7260   task_flags:0x400040 flags:0x00000000
[  189.068297] Call Trace:
[  189.068298]  [<000003a8b2e19284>] __schedule+0x374/0x900
[  189.068300]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
[  189.068302]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
[  189.068304]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
[  189.068307]  [<000003a8b263e2e8>] extent_write_cache_pages+0x348/0x4d0
[  189.068310]  [<000003a8b263ea0a>] btrfs_writepages+0x6a/0xd0
[  189.068313]  [<000003a8b21ad444>] do_writepages+0xd4/0x190
[  189.068315]  [<000003a8b2199dec>] filemap_writeback+0xbc/0x100
[  189.068318]  [<000003a8b2199e5a>] filemap_fdatawrite_range+0x2a/0x40
[  189.068320]  [<000003a8b232bbf2>] sync_file_range+0x122/0x150
[  189.068323]  [<000003a8b232bc84>] ksys_sync_file_range+0x64/0xb0
[  189.068326]  [<000003a8b232bd0a>] __s390x_sys_sync_file_range+0x3a/0x50
[  189.068328]  [<000003a8b2e13b7e>] __do_syscall+0x14e/0x590
[  189.068331]  [<000003a8b2e24242>] system_call+0x72/0x90
Re: [PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Matthew Wilcox 2 days, 10 hours ago
On Wed, Jul 22, 2026 at 11:29:36AM +0200, Christian Borntraeger wrote:
>  *   4. Thread B loops sync_file_range(WRITE|WAIT) on the target file.
>  *      Whenever a full clean cycle (clear_page_dirty_for_io(),
>  *      writeback, bits cleared) completes inside thread A's
>  *      submission->completion window, the completion-time
>  *      set_page_dirty_lock() hits a *clean* folio: filemap_dirty_folio()
>  *      sets only the folio flag and the xarray tag - no btrfs subpage
>  *      dirty bit, no delalloc reservation.  See the 20-year-old comment
>  *      above bio_set_pages_dirty() in block/bio.c describing exactly
>  *      this ("other code (eg, flusher threads) could clean the pages").

There's your problem.  filemap_dirty_folio() documents that btrfs is
doing it wrongly:

 * Filesystems which do not use buffer heads should call this function
 * from their dirty_folio address space operation.  It ignores the
 * contents of folio_get_private(), so if the filesystem marks individual
 * blocks as dirty, the filesystem should handle that itself.

fs/btrfs/inode.c:       .dirty_folio    = filemap_dirty_folio,

so btrfs should have its own btrfs_dirty_folio() which does whatever
metadata updates it needs to and then call filemap_dirty_folio() to
take care of the page cache business.  See iomap_dirty_folio() as
an example, but many other filesystems also do this.
Re: [PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Qu Wenruo 1 day, 22 hours ago

在 2026/7/22 22:27, Matthew Wilcox 写道:
> On Wed, Jul 22, 2026 at 11:29:36AM +0200, Christian Borntraeger wrote:
>>   *   4. Thread B loops sync_file_range(WRITE|WAIT) on the target file.
>>   *      Whenever a full clean cycle (clear_page_dirty_for_io(),
>>   *      writeback, bits cleared) completes inside thread A's
>>   *      submission->completion window, the completion-time
>>   *      set_page_dirty_lock() hits a *clean* folio: filemap_dirty_folio()
>>   *      sets only the folio flag and the xarray tag - no btrfs subpage
>>   *      dirty bit, no delalloc reservation.  See the 20-year-old comment
>>   *      above bio_set_pages_dirty() in block/bio.c describing exactly
>>   *      this ("other code (eg, flusher threads) could clean the pages").
> 
> There's your problem.  filemap_dirty_folio() documents that btrfs is
> doing it wrongly:
> 
>   * Filesystems which do not use buffer heads should call this function
>   * from their dirty_folio address space operation.  It ignores the
>   * contents of folio_get_private(), so if the filesystem marks individual
>   * blocks as dirty, the filesystem should handle that itself.
> 
> fs/btrfs/inode.c:       .dirty_folio    = filemap_dirty_folio,
> 
> so btrfs should have its own btrfs_dirty_folio() which does whatever
> metadata updates it needs to and then call filemap_dirty_folio() to
> take care of the page cache business.  See iomap_dirty_folio() as
> an example, but many other filesystems also do this.

Thanks a lot for the advice.

However it looks like the sub-folio dirty block tracking is a little 
different between iomap and btrfs.

E.g. iomap will mark the full folio range dirty even if the EOF is 
inside the folio, but btrfs will only mark the range inside EOF as dirty.


Another thing is, even if we follow iomap to mark the full folio dirty, 
it's still not the end of the story.

We have other supporting mechanisms required to tracking the dirty 
range. E.g. EXTENT_DELALLOC flags inside extent-io-tree, indicating we 
have already reserved space for the dirty range.

Only with EXTENT_DELALLOC flag set, we will do the real delayed 
allocation, allocating the on-disk extents etc.

So even if we always mark the full folio dirty, the writeback path will 
not handle them correctly either.


Finally, even without large folios, the reproducer can already cause 
problems on btrfs. E.g. on x86_64, with mapping_set_folio_order_range() 
disabled.

The symptom there is that, ordered extent accounting underflows, which 
may also contribute to the stall observed.

I'll keep digging for this bug.

Thanks,
Qu
Re: [PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Matthew Wilcox 1 day, 11 hours ago
On Thu, Jul 23, 2026 at 10:12:27AM +0930, Qu Wenruo wrote:
> 
> 
> 在 2026/7/22 22:27, Matthew Wilcox 写道:
> > On Wed, Jul 22, 2026 at 11:29:36AM +0200, Christian Borntraeger wrote:
> > >   *   4. Thread B loops sync_file_range(WRITE|WAIT) on the target file.
> > >   *      Whenever a full clean cycle (clear_page_dirty_for_io(),
> > >   *      writeback, bits cleared) completes inside thread A's
> > >   *      submission->completion window, the completion-time
> > >   *      set_page_dirty_lock() hits a *clean* folio: filemap_dirty_folio()
> > >   *      sets only the folio flag and the xarray tag - no btrfs subpage
> > >   *      dirty bit, no delalloc reservation.  See the 20-year-old comment
> > >   *      above bio_set_pages_dirty() in block/bio.c describing exactly
> > >   *      this ("other code (eg, flusher threads) could clean the pages").
> > 
> > There's your problem.  filemap_dirty_folio() documents that btrfs is
> > doing it wrongly:
> > 
> >   * Filesystems which do not use buffer heads should call this function
> >   * from their dirty_folio address space operation.  It ignores the
> >   * contents of folio_get_private(), so if the filesystem marks individual
> >   * blocks as dirty, the filesystem should handle that itself.
> > 
> > fs/btrfs/inode.c:       .dirty_folio    = filemap_dirty_folio,
> > 
> > so btrfs should have its own btrfs_dirty_folio() which does whatever
> > metadata updates it needs to and then call filemap_dirty_folio() to
> > take care of the page cache business.  See iomap_dirty_folio() as
> > an example, but many other filesystems also do this.
> 
> Thanks a lot for the advice.
> 
> However it looks like the sub-folio dirty block tracking is a little
> different between iomap and btrfs.

My point is not that "you should do it the exact same way as iomap".
Rather "the dirty_folio op is the entry point to tell the filesystem
that a folio is being dirtied".  And you aren't taking advantage of
that, you're just calling the VFS so the VFS can do its own tracking.

> E.g. iomap will mark the full folio range dirty even if the EOF is inside
> the folio, but btrfs will only mark the range inside EOF as dirty.
> 
> 
> Another thing is, even if we follow iomap to mark the full folio dirty, it's
> still not the end of the story.
> 
> We have other supporting mechanisms required to tracking the dirty range.
> E.g. EXTENT_DELALLOC flags inside extent-io-tree, indicating we have already
> reserved space for the dirty range.
> 
> Only with EXTENT_DELALLOC flag set, we will do the real delayed allocation,
> allocating the on-disk extents etc.
> 
> So even if we always mark the full folio dirty, the writeback path will not
> handle them correctly either.
> 
> 
> Finally, even without large folios, the reproducer can already cause
> problems on btrfs. E.g. on x86_64, with mapping_set_folio_order_range()
> disabled.
> 
> The symptom there is that, ordered extent accounting underflows, which may
> also contribute to the stall observed.
> 
> I'll keep digging for this bug.
> 
> Thanks,
> Qu
> 
> 
Re: [PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Qu Wenruo 1 day ago

在 2026/7/23 21:30, Matthew Wilcox 写道:
> On Thu, Jul 23, 2026 at 10:12:27AM +0930, Qu Wenruo wrote:
>>
>>
>> 在 2026/7/22 22:27, Matthew Wilcox 写道:
>>> On Wed, Jul 22, 2026 at 11:29:36AM +0200, Christian Borntraeger wrote:
>>>>    *   4. Thread B loops sync_file_range(WRITE|WAIT) on the target file.
>>>>    *      Whenever a full clean cycle (clear_page_dirty_for_io(),
>>>>    *      writeback, bits cleared) completes inside thread A's
>>>>    *      submission->completion window, the completion-time
>>>>    *      set_page_dirty_lock() hits a *clean* folio: filemap_dirty_folio()
>>>>    *      sets only the folio flag and the xarray tag - no btrfs subpage
>>>>    *      dirty bit, no delalloc reservation.  See the 20-year-old comment
>>>>    *      above bio_set_pages_dirty() in block/bio.c describing exactly
>>>>    *      this ("other code (eg, flusher threads) could clean the pages").
>>>
>>> There's your problem.  filemap_dirty_folio() documents that btrfs is
>>> doing it wrongly:
>>>
>>>    * Filesystems which do not use buffer heads should call this function
>>>    * from their dirty_folio address space operation.  It ignores the
>>>    * contents of folio_get_private(), so if the filesystem marks individual
>>>    * blocks as dirty, the filesystem should handle that itself.
>>>
>>> fs/btrfs/inode.c:       .dirty_folio    = filemap_dirty_folio,
>>>
>>> so btrfs should have its own btrfs_dirty_folio() which does whatever
>>> metadata updates it needs to and then call filemap_dirty_folio() to
>>> take care of the page cache business.  See iomap_dirty_folio() as
>>> an example, but many other filesystems also do this.
>>
>> Thanks a lot for the advice.
>>
>> However it looks like the sub-folio dirty block tracking is a little
>> different between iomap and btrfs.
> 
> My point is not that "you should do it the exact same way as iomap".
> Rather "the dirty_folio op is the entry point to tell the filesystem
> that a folio is being dirtied".
And since dirty_folio() is not allowed to sleep, we should introduce 
some extra mechanism, e.g. page private 2/checked, to notify the fs that 
the folio is marked dirty without proper preparation.

Then during writeback, detect such folio and do needed preparation for 
it since at writeback we're allowed to sleep.

That sounds feasible, but I haven't seen anyone doing that (including 
the older btrfs cow fixup).

Will explore that path. Thanks a lot again for the dirty_folio() help.

Thanks,
Qu
Re: [PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Qu Wenruo 2 days, 13 hours ago

在 2026/7/22 18:59, Christian Borntraeger 写道:
> Am 22.07.26 um 10:59 schrieb Qu Wenruo:
>>
>>
>> 在 2026/7/22 18:05, Christian Borntraeger 写道:
>>> Am 21.07.26 um 23:07 schrieb Qu Wenruo:
>>>
>>> First, thank you for taking the time to look into this and trying to 
>>> understand
>>> things and trying to explain things. this is highly appreciated.
>>> I am still trying to fully understand this myself. A question:
>>>
>>>> 在 2026/7/22 04:41, Christian Borntraeger 写道:
>>>>> A folio can carry the folio-level dirty flag while its btrfs subpage
>>>>> dirty bitmap is empty: btrfs data mappings use filemap_dirty_folio(),
>>>>> so a generic folio_mark_dirty() call sets only the folio flag and the
>>>>> xarray tag, without setting any subpage dirty bit and without a
>>>>> delalloc reservation.  The typical source is set_page_dirty_lock() on
>>>>> a GUP pin,
>>>>
>>>> Shouldn't such folio got its ->page_mkwrite() callback get called 
>>>> first?
>>>
>>> Isnt that called implicitely at pin time?
>>
>> I have to admit, I'm not an expert on the MM part, I'm mostly a simple 
>> user of the existing MM interfaces.
>>
>> AFAIK, the last time I brought this thing up, Christoph mentioned that 
>> dirtying-folio-without-notifying-fs is a bug, and fs should not and is 
>> not able to handle such situation anyway.
>>
>> And that idea makes a lot of sense to me.
>>
>> So adding MM list for more help.
> So I now have an userspace O_DIRECT reproducer outside of KVM. 
> (attached) which gave me (on an s390 system, though)

Thanks a lot, I can also reproduce it on arm64 (64K page size).

This is super bad, as we have just removed a lot of folio ordered 
related code to detect such problem.

I'll also check if it's some recent btrfs changes making it worse.

Thanks,
Qu
> 
> [  189.067731] sysrq: Show Blocked State
> [  189.067872] task:kworker/u1665:5 state:D stack:0     pid:1363  
> tgid:1363  ppid:2      task_flags:0x4208060 flags:0x00000000
> [  189.067877] Workqueue: writeback wb_workfn (flush-btrfs-1)
> [  189.067884] Call Trace:
> [  189.067886]  [<000003a8b2e19284>] __schedule+0x374/0x900
> [  189.067891]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
> [  189.067894]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
> [  189.067896]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
> [  189.067902]  [<000003a8b263e2e8>] extent_write_cache_pages+0x348/0x4d0
> [  189.067907]  [<000003a8b263ea0a>] btrfs_writepages+0x6a/0xd0
> [  189.067910]  [<000003a8b21ad444>] do_writepages+0xd4/0x190
> [  189.067915]  [<000003a8b2324590>] __writeback_single_inode+0x50/0x310
> [  189.067917]  [<000003a8b2324b1a>] writeback_sb_inodes+0x2ca/0x690
> [  189.067919]  [<000003a8b2324f36>] __writeback_inodes_wb+0x56/0x140
> [  189.067921]  [<000003a8b2325498>] wb_writeback+0x368/0x460
> [  189.067923]  [<000003a8b23258c6>] wb_do_writeback+0x336/0x3d0
> [  189.067925]  [<000003a8b2325d2a>] wb_workfn+0x5a/0x1d0
> [  189.067927]  [<000003a8b1f0cee2>] process_one_work+0x1d2/0x480
> [  189.067932]  [<000003a8b1f0df30>] worker_thread+0x210/0x420
> [  189.067935]  [<000003a8b1f190b8>] kthread+0x148/0x170
> [  189.067939]  [<000003a8b1e90358>] __ret_from_fork+0x48/0x220
> [  189.067941]  [<000003a8b2e2426a>] ret_from_fork+0xa/0x30
> [  189.067994] task:oob-dirty-repro state:D stack:0     pid:8152  
> tgid:8151  ppid:7260   task_flags:0x400040 flags:0x00000800
> [  189.067997] Call Trace:
> [  189.067998]  [<000003a8b2e19284>] __schedule+0x374/0x900
> [  189.068050]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
> [  189.068052]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
> [  189.068054]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
> [  189.068057]  [<000003a8b263e2e8>] extent_write_cache_pages+0x348/0x4d0
> [  189.068061]  [<000003a8b263ea0a>] btrfs_writepages+0x6a/0xd0
> [  189.068063]  [<000003a8b21ad444>] do_writepages+0xd4/0x190
> [  189.068066]  [<000003a8b2199dec>] filemap_writeback+0xbc/0x100
> [  189.068069]  [<000003a8b2199e5a>] filemap_fdatawrite_range+0x2a/0x40
> [  189.068072]  [<000003a8b2637d3c>] 
> btrfs_start_ordered_extent_nowriteback+0x18c/0x1d0
> [  189.068074]  [<000003a8b26260ce>] btrfs_page_mkwrite+0x22e/0x8d0
> [  189.068079]  [<000003a8b21f89d0>] do_page_mkwrite+0x60/0xf0
> [  189.068084]  [<000003a8b21ffa64>] do_wp_page+0x134/0x660
> [  189.068086]  [<000003a8b2206584>] __handle_mm_fault+0x1c4/0x5a0
> [  189.068088]  [<000003a8b22069f0>] handle_mm_fault+0x90/0x230
> [  189.068090]  [<000003a8b1ebfbd0>] do_exception+0x190/0x560
> [  189.068094]  [<000003a8b2e136c0>] __do_pgm_check+0x1b0/0x370
> [  189.068098]  [<000003a8b2e243a4>] pgm_check_handler+0x114/0x160
> [  189.068101]  [<000003a8b283135e>] _copy_to_iter+0x6e/0x870
> [  189.068106]  [<000003a8b2831c4a>] copy_page_to_iter+0xea/0x160
> [  189.068108]  [<000003a8b219ff8c>] filemap_read+0x1ec/0x400
> [  189.068109]  [<000003a8b22d0a70>] vfs_read+0x210/0x370
> [  189.068112]  [<000003a8b22d1694>] ksys_pread64+0xa4/0xd0
> [  189.068114]  [<000003a8b2e13b7e>] __do_syscall+0x14e/0x590
> [  189.068117]  [<000003a8b2e24242>] system_call+0x72/0x90
> [  189.068120] task:oob-dirty-repro state:D stack:0     pid:8153  
> tgid:8151  ppid:7260   task_flags:0x400040 flags:0x00000000
> [  189.068123] Call Trace:
> [  189.068124]  [<000003a8b2e19284>] __schedule+0x374/0x900
> [  189.068126]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
> [  189.068128]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
> [  189.068130]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
> [  189.068133]  [<000003a8b263e2e8>] extent_write_cache_pages+0x348/0x4d0
> [  189.068136]  [<000003a8b263ea0a>] btrfs_writepages+0x6a/0xd0
> [  189.068138]  [<000003a8b21ad444>] do_writepages+0xd4/0x190
> [  189.068141]  [<000003a8b2199dec>] filemap_writeback+0xbc/0x100
> [  189.068144]  [<000003a8b2199e5a>] filemap_fdatawrite_range+0x2a/0x40
> [  189.068146]  [<000003a8b2637d3c>] 
> btrfs_start_ordered_extent_nowriteback+0x18c/0x1d0
> [  189.068149]  [<000003a8b26260ce>] btrfs_page_mkwrite+0x22e/0x8d0
> [  189.068152]  [<000003a8b21f89d0>] do_page_mkwrite+0x60/0xf0
> [  189.068154]  [<000003a8b21ffa64>] do_wp_page+0x134/0x660
> [  189.068156]  [<000003a8b2206584>] __handle_mm_fault+0x1c4/0x5a0
> [  189.068158]  [<000003a8b22069f0>] handle_mm_fault+0x90/0x230
> [  189.068160]  [<000003a8b1ebfbd0>] do_exception+0x190/0x560
> [  189.068163]  [<000003a8b2e136c0>] __do_pgm_check+0x1b0/0x370
> [  189.068165]  [<000003a8b2e243a4>] pgm_check_handler+0x114/0x160
> [  189.068168]  [<000003a8b283135e>] _copy_to_iter+0x6e/0x870
> [  189.068170]  [<000003a8b2831c4a>] copy_page_to_iter+0xea/0x160
> [  189.068172]  [<000003a8b219ff8c>] filemap_read+0x1ec/0x400
> [  189.068174]  [<000003a8b22d0a70>] vfs_read+0x210/0x370
> [  189.068176]  [<000003a8b22d1694>] ksys_pread64+0xa4/0xd0
> [  189.068178]  [<000003a8b2e13b7e>] __do_syscall+0x14e/0x590
> [  189.068181]  [<000003a8b2e24242>] system_call+0x72/0x90
> [  189.068184] task:oob-dirty-repro state:D stack:0     pid:8154  
> tgid:8151  ppid:7260   task_flags:0x400040 flags:0x00000000
> [  189.068187] Call Trace:
> [  189.068188]  [<000003a8b2e19284>] __schedule+0x374/0x900
> [  189.068190]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
> [  189.068192]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
> [  189.068193]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
> [  189.068196]  [<000003a8b26260fc>] btrfs_page_mkwrite+0x25c/0x8d0
> [  189.068199]  [<000003a8b21f89d0>] do_page_mkwrite+0x60/0xf0
> [  189.068202]  [<000003a8b21ffa64>] do_wp_page+0x134/0x660
> [  189.068203]  [<000003a8b2206584>] __handle_mm_fault+0x1c4/0x5a0
> [  189.068206]  [<000003a8b22069f0>] handle_mm_fault+0x90/0x230
> [  189.068208]  [<000003a8b1ebfbd0>] do_exception+0x190/0x560
> [  189.068210]  [<000003a8b2e136c0>] __do_pgm_check+0x1b0/0x370
> [  189.068213]  [<000003a8b2e243a4>] pgm_check_handler+0x114/0x160
> [  189.068215]  [<000003a8b283135e>] _copy_to_iter+0x6e/0x870
> [  189.068218]  [<000003a8b2831c4a>] copy_page_to_iter+0xea/0x160
> [  189.068219]  [<000003a8b219ff8c>] filemap_read+0x1ec/0x400
> [  189.068221]  [<000003a8b22d0a70>] vfs_read+0x210/0x370
> [  189.068223]  [<000003a8b22d1694>] ksys_pread64+0xa4/0xd0
> [  189.068225]  [<000003a8b2e13b7e>] __do_syscall+0x14e/0x590
> [  189.068228]  [<000003a8b2e24242>] system_call+0x72/0x90
> [  189.068231] task:oob-dirty-repro state:D stack:0     pid:8155  
> tgid:8151  ppid:7260   task_flags:0x400040 flags:0x00000000
> [  189.068234] Call Trace:
> [  189.068235]  [<000003a8b2e19284>] __schedule+0x374/0x900
> [  189.068237]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
> [  189.068239]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
> [  189.068241]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
> [  189.068244]  [<000003a8b263e2e8>] extent_write_cache_pages+0x348/0x4d0
> [  189.068246]  [<000003a8b263ea0a>] btrfs_writepages+0x6a/0xd0
> [  189.068249]  [<000003a8b21ad444>] do_writepages+0xd4/0x190
> [  189.068252]  [<000003a8b2199dec>] filemap_writeback+0xbc/0x100
> [  189.068255]  [<000003a8b2199e5a>] filemap_fdatawrite_range+0x2a/0x40
> [  189.068257]  [<000003a8b2637d3c>] 
> btrfs_start_ordered_extent_nowriteback+0x18c/0x1d0
> [  189.068260]  [<000003a8b26260ce>] btrfs_page_mkwrite+0x22e/0x8d0
> [  189.068263]  [<000003a8b21f89d0>] do_page_mkwrite+0x60/0xf0
> [  189.068265]  [<000003a8b21ffa64>] do_wp_page+0x134/0x660
> [  189.068267]  [<000003a8b2206584>] __handle_mm_fault+0x1c4/0x5a0
> [  189.068269]  [<000003a8b22069f0>] handle_mm_fault+0x90/0x230
> [  189.068271]  [<000003a8b1ebfbd0>] do_exception+0x190/0x560
> [  189.068274]  [<000003a8b2e136c0>] __do_pgm_check+0x1b0/0x370
> [  189.068277]  [<000003a8b2e243a4>] pgm_check_handler+0x114/0x160
> [  189.068279]  [<000003a8b283135e>] _copy_to_iter+0x6e/0x870
> [  189.068281]  [<000003a8b2831c4a>] copy_page_to_iter+0xea/0x160
> [  189.068283]  [<000003a8b219ff8c>] filemap_read+0x1ec/0x400
> [  189.068285]  [<000003a8b22d0a70>] vfs_read+0x210/0x370
> [  189.068287]  [<000003a8b22d1694>] ksys_pread64+0xa4/0xd0
> [  189.068289]  [<000003a8b2e13b7e>] __do_syscall+0x14e/0x590
> [  189.068292]  [<000003a8b2e24242>] system_call+0x72/0x90
> [  189.068294] task:oob-dirty-repro state:D stack:0     pid:8156  
> tgid:8151  ppid:7260   task_flags:0x400040 flags:0x00000000
> [  189.068297] Call Trace:
> [  189.068298]  [<000003a8b2e19284>] __schedule+0x374/0x900
> [  189.068300]  [<000003a8b2e1984c>] schedule+0x3c/0xf0
> [  189.068302]  [<000003a8b2e1996c>] io_schedule+0x2c/0x40
> [  189.068304]  [<000003a8b219d158>] folio_wait_bit_common+0x198/0x3b0
> [  189.068307]  [<000003a8b263e2e8>] extent_write_cache_pages+0x348/0x4d0
> [  189.068310]  [<000003a8b263ea0a>] btrfs_writepages+0x6a/0xd0
> [  189.068313]  [<000003a8b21ad444>] do_writepages+0xd4/0x190
> [  189.068315]  [<000003a8b2199dec>] filemap_writeback+0xbc/0x100
> [  189.068318]  [<000003a8b2199e5a>] filemap_fdatawrite_range+0x2a/0x40
> [  189.068320]  [<000003a8b232bbf2>] sync_file_range+0x122/0x150
> [  189.068323]  [<000003a8b232bc84>] ksys_sync_file_range+0x64/0xb0
> [  189.068326]  [<000003a8b232bd0a>] __s390x_sys_sync_file_range+0x3a/0x50
> [  189.068328]  [<000003a8b2e13b7e>] __do_syscall+0x14e/0x590
> [  189.068331]  [<000003a8b2e24242>] system_call+0x72/0x90

Re: [PATCH/RFC] btrfs: fix folio lock leak in writepage_delalloc() for folios dirtied behind btrfs' back
Posted by Christian Borntraeger 2 days, 12 hours ago
Am 22.07.26 um 11:35 schrieb Qu Wenruo:
> 
> 
> 在 2026/7/22 18:59, Christian Borntraeger 写道:
>> Am 22.07.26 um 10:59 schrieb Qu Wenruo:
>>>
>>>
>>> 在 2026/7/22 18:05, Christian Borntraeger 写道:
>>>> Am 21.07.26 um 23:07 schrieb Qu Wenruo:
>>>>
>>>> First, thank you for taking the time to look into this and trying to understand
>>>> things and trying to explain things. this is highly appreciated.
>>>> I am still trying to fully understand this myself. A question:
>>>>
>>>>> 在 2026/7/22 04:41, Christian Borntraeger 写道:
>>>>>> A folio can carry the folio-level dirty flag while its btrfs subpage
>>>>>> dirty bitmap is empty: btrfs data mappings use filemap_dirty_folio(),
>>>>>> so a generic folio_mark_dirty() call sets only the folio flag and the
>>>>>> xarray tag, without setting any subpage dirty bit and without a
>>>>>> delalloc reservation.  The typical source is set_page_dirty_lock() on
>>>>>> a GUP pin,
>>>>>
>>>>> Shouldn't such folio got its ->page_mkwrite() callback get called first?
>>>>
>>>> Isnt that called implicitely at pin time?
>>>
>>> I have to admit, I'm not an expert on the MM part, I'm mostly a simple user of the existing MM interfaces.
>>>
>>> AFAIK, the last time I brought this thing up, Christoph mentioned that dirtying-folio-without-notifying-fs is a bug, and fs should not and is not able to handle such situation anyway.
>>>
>>> And that idea makes a lot of sense to me.
>>>
>>> So adding MM list for more help.
>> So I now have an userspace O_DIRECT reproducer outside of KVM. (attached) which gave me (on an s390 system, though)
> 
> Thanks a lot, I can also reproduce it on arm64 (64K page size).
> 
> This is super bad, as we have just removed a lot of folio ordered related code to detect such problem.
> 
> I'll also check if it's some recent btrfs changes making it worse.


The RFC patch from yesterday seems to fix _THIS_ problem, but as you outlined, there might be other issues that break and are not handled by my patch.