Re: [PATCH v2] ext4: cancel dirty accounting for folios without buffers
From: Zhang Yi
Date: Fri Jun 26 2026 - 23:47:58 EST
On 6/26/2026 6:07 PM, Zhu Jia wrote:
> 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>
Looks good to me.
Reviewed-by: Zhang Yi <yi.zhang@xxxxxxxxxx>
> ---
> 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;
> }