[PATCH v2] ext4: cancel dirty accounting for folios without buffers

From: Zhu Jia

Date: Fri Jun 26 2026 - 06:07:58 EST


Since commit cc5095747edf ("ext4: don't BUG if someone dirty pages
without asking ext4 first"), mpage_prepare_extent_to_map() handles dirty
folios without buffer heads by warning, clearing PG_dirty, and skipping
them. ext4 cannot write these folios because there are no buffer heads to
map and submit.

That recovery leaves dirty accounting behind: folio_clear_dirty() clears
PG_dirty but does not undo the accounting charged when the folio was
dirtied. We have seen this in production as Dirty/nr_dirty staying high
while Writeback/nr_writeback and device write IO stayed near zero, with
many writer tasks blocked in balance_dirty_pages() throttling. Thus the
warning-and-skip recovery can still become a dirty-throttle DoS.

Use folio_cancel_dirty() so dropping PG_dirty also cancels the dirty
accounting. Then cycle the folio through writeback state so the generic
writeback helpers update the xarray DIRTY/TOWRITE tags.

Fixes: cc5095747edf ("ext4: don't BUG if someone dirty pages without asking ext4 first")
Cc: stable@xxxxxxxxxxxxxxx
Suggested-by: Zhang Yi <yizhang089@xxxxxxxxx>
Reviewed-by: Jan Kara <jack@xxxxxxx>
Signed-off-by: Zhu Jia <zhujia.zj@xxxxxxxxxxxxx>
---
Changes since v1:
- After folio_cancel_dirty(), cycle the folio through writeback state so
generic writeback helpers update PAGECACHE_TAG_DIRTY/TOWRITE, as
suggested by Yi and Jan.

fs/ext4/inode.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index c2c2d6ac7f3d1..4c25dcd47fb15 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -2715,7 +2715,15 @@ static int mpage_prepare_extent_to_map(struct mpage_da_data *mpd)
*/
if (!folio_buffers(folio)) {
ext4_warning_inode(mpd->inode, "page %lu does not have buffers attached", folio->index);
- folio_clear_dirty(folio);
+ /*
+ * folio_cancel_dirty() pairs the dropped dirty
+ * state with dirty accounting. Cycle through
+ * writeback state so the generic writeback
+ * helpers update the xarray tags.
+ */
+ folio_cancel_dirty(folio);
+ folio_start_writeback(folio);
+ folio_end_writeback(folio);
folio_unlock(folio);
continue;
}
--
2.20.1