All of lore.kernel.org
 help / color / mirror / Atom feed
From: Zhang Yi <yizhang089@gmail.com>
To: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Cc: sashiko-reviews@lists.linux.dev,
	Zhang Yi <yi.zhang@huaweicloud.com>,
	linux-ext4@vger.kernel.org, tytso@mit.edu
Subject: Re: [PATCH v6 15/31] ext4: implement writeback path using iomap
Date: Wed, 30 Sep 2026 17:26:40 +0800	[thread overview]
Message-ID: <217106f8-bad8-4834-bc1a-30f3197bfc63@gmail.com> (raw)
In-Reply-To: <arzNeYSTqq-Z4gXM@li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com>

On 9/30/2026 4:51 PM, Ojaswin Mujoo wrote:
> On Sat, Sep 12, 2026 at 04:28:52PM +0800, Zhang Yi wrote:
>> On 9/3/2026 9:36 PM, sashiko-bot@kernel.org wrote:
>>> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
>>> - [Critical] Journal Credits Exhaustion / Panic in `ext4_iomap_finish_ioend()` due to arbitrarily large merged ioends.
>>> - [High] Infinite loop in `ext4_iomap_map_writeback_range` due to open-coded ENOSPC retry logic without limits.
>>> --
>>>
>>> commit e7cc5164579a940550c93afb2dfec1f186e079ed
>>> Author: Zhang Yi <yi.zhang@huawei.com>
>>> ext4: implement writeback path using iomap
>>>
>>> This commit adds the iomap writeback path for ext4 buffered I/O, introducing
>>> new writeback operations and end I/O handlers. It maps and allocates blocks
>>> as unwritten extents during writeback to prevent stale data exposure.
>>>
>>>> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
>>>> index 1f18854b9cdd3..f7d7610197a1c 100644
>>>> --- a/fs/ext4/inode.c
>>>> +++ b/fs/ext4/inode.c
>>>
>>> [ ... ]
>>>
>>>> +static int ext4_iomap_map_writeback_range(struct iomap_writepage_ctx *wpc,
>>>> +					  loff_t offset, unsigned int dirty_len)
>>>> +{
>>>
>>> [ ... ]
>>>
>>>> +retry:
>>>> +	map.m_lblk = index;
>>>> +	map.m_len = min_t(unsigned int, MAX_WRITEPAGES_EXTENT_LEN, blk_len);
>>>> +	ret = ext4_map_blocks(NULL, inode, &map,
>>>> +			      EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT |
>>>> +			      EXT4_GET_BLOCKS_METADATA_NOFAIL |
>>>> +			      EXT4_GET_BLOCKS_IO_SUBMIT |
>>>> +			      EXT4_EX_NOCACHE);
>>>> +	if (ret < 0) {
>>>> +		if (ext4_emergency_state(sb))
>>>> +			return ret;
>>>> +
>>>> +		/*
>>>> +		 * Retry transient ENOSPC errors, if
>>>> +		 * ext4_count_free_blocks() is non-zero, a commit
>>>> +		 * should free up blocks.
>>>> +		 */
>>>> +		if (ret == -ENOSPC && journal && ext4_count_free_clusters(sb)) {
>>>> +			jbd2_journal_force_commit_nested(journal);
>>>> +			goto retry;
>>>> +		}
>>>
>>> [Severity: High]
>>> Could this open-coded retry loop result in an infinite hang under certain
>>> conditions?
>>>
>>> If a non-root user fills the disk up to the threshold of root-reserved
>>> blocks, ext4_map_blocks() will return -ENOSPC because the block allocator
>>> denies access to reserved blocks for non-root users.
>>>
>>> However, ext4_count_free_clusters() returns the total free space including
>>> those root-reserved blocks. Because the count evaluates as non-zero, the code
>>> will force a journal commit and goto retry indefinitely, without any loop
>>> limits.
>>>
>>> Should this retry logic use a limit like ext4_should_retry_alloc(), or
>>> specifically account for root-reserved blocks to prevent a soft lockup in
>>> the writeback worker thread?
>>>
>>
>> This logic is inherited from the existing buffer_head writeback path
>> (the -ENOSPC handling in ext4_do_writepages()), which also retries after
>> a forced commit without any limit, so it does not introduce a new class
>> of problem.  That said, I agree that bounding the number of retries is
>> better, and I will add a limit in the next version.
>>
>>
>>>> diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
>>>> index 0236b6b9785a2..9b0e12b5463cf 100644
>>>> --- a/fs/ext4/page-io.c
>>>> +++ b/fs/ext4/page-io.c
>>>
>>> [ ... ]
>>>
>>>> +static void ext4_iomap_finish_ioend(struct iomap_ioend *ioend)
>>>> +{
>>>
>>> [ ... ]
>>>
>>>> +	/*
>>>> +	 * We may need to convert one extent, update the i_disksize and
>>>> +	 * dirty the inode.
>>>> +	 */
>>>> +	credits = ext4_chunk_trans_blocks(inode,
>>>> +			EXT4_MAX_BLOCKS(size, pos, inode->i_blkbits));
>>>> +	handle = ext4_journal_start(inode, EXT4_HT_EXT_CONVERT, credits);
>>>> +	if (IS_ERR(handle)) {
>>>> +		ret = PTR_ERR(handle);
>>>> +		goto out_err;
>>>> +	}
>>>
>>> [ ... ]
>>>
>>>> +void ext4_iomap_end_io(struct work_struct *work)
>>>> +{
>>>> +	struct ext4_inode_info *ei = container_of(work, struct ext4_inode_info,
>>>> +						  i_rsv_conversion_work);
>>>> +	struct iomap_ioend *ioend;
>>>> +	struct list_head ioend_list;
>>>> +	unsigned long flags;
>>>> +
>>>> +	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
>>>> +	list_replace_init(&ei->i_rsv_conversion_list, &ioend_list);
>>>> +	spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
>>>> +
>>>> +	iomap_sort_ioends(&ioend_list);
>>>> +	while (!list_empty(&ioend_list)) {
>>>> +		ioend = list_entry(ioend_list.next, struct iomap_ioend, io_list);
>>>> +		list_del_init(&ioend->io_list);
>>>> +		iomap_ioend_try_merge(ioend, &ioend_list);
>>>> +		ext4_iomap_finish_ioend(ioend);
>>>> +	}
>>>> +}
>>>
>>> [Severity: Critical]
>>> Is there a risk of exhausting journal credits and panicking JBD2 when ioends
>>> are merged?
>>>
>>> In ext4_iomap_end_io(), iomap_ioend_try_merge() merges contiguous completions
>>> into an arbitrarily large ioend. When this merged ioend is later passed to
>>> ext4_iomap_finish_ioend(), it requests credits using ext4_chunk_trans_blocks().
>>>
>>> However, ext4_chunk_trans_blocks() internally hardcodes the extent count to 1,
>>> allocating credits sufficient for modifying only a single extent on disk.
>>>
>>> If a user writes a very large contiguous file (e.g., larger than 128MB), the
>>> merged ioend will exceed the maximum size of a single unwritten extent in ext4
>>> (which is 32,768 blocks). This will cause ext4_convert_unwritten_extents() to
>>> loop over multiple extents using the same transaction handle.
>>>
>>> Could this exhaust the single-extent credits provided by the transaction
>>> handle, resulting in a JBD2 panic during writeback?
>>>
>>
>> This is right.  ext4_convert_unwritten_extents() converts every extent
>> covered by the ioend using a single transaction handle, but that handle
>> was sized by ext4_chunk_trans_blocks() for one extent only, so a large
>> enough merged ioend can run out of credits.  This is hard to hit in
>> practice, since the end I/O worker normally runs promptly and not many
>> completed ioends accumulate before the merge, but to be safe I will
>> still fix it: call ext4_journal_ensure_credits() before each extent in
>> the conversion loop, so that the transaction is extended, or restarted
>> once it is full, as needed, and correct the estimation to
>> ext4_meta_trans_blocks(inode, max_blocks, 1, 0) at the same time, as no
>> data blocks are allocated there.
> 
> Yes I agree that it seems like a valid issue. I'm a bit unfamiliar
> with the journal ensure credits logic but seems like it would be
> stopping the running txn and starting a new transaction right?

Yes, the change is also very simple, please see:

https://github.com/zhangyi089/linux/commit/d8078ca0dd93bd92c0fa0904e1c76b112339f48d

> 
> If so, maybe lets move the idisksize update after the the conversion so
> that its updated in the final transaction, once the conversion is
> complete, which is a bit cleaner.
> 

That won't work. Since the conversion may be split across different
transactions, a single transaction can no longer provide the atomicity
guarantee for both the conversion and the i_disksize update. If we do
the conversion first and then lose power after some of the conversion
transactions have committed, we would end up with written extents beyond
EOF, which would make fsck complain.

Thanks,
Yi.


  reply	other threads:[~2026-09-30  9:26 UTC|newest]

Thread overview: 87+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-03 12:35 [PATCH v6 00/31] ext4: use iomap for regular file's buffered I/O path Zhang Yi
2026-09-03 12:35 ` [PATCH v6 01/31] ext4: simplify size updating in ext4_setattr() Zhang Yi
2026-09-03 12:56   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 02/31] ext4: factor out ext4_truncate_[up|down]() Zhang Yi
2026-09-03 13:09   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 03/31] ext4: skip ordered I/O wait when zeroing beyond i_disksize block Zhang Yi
2026-09-03 13:14   ` sashiko-bot
2026-09-08 11:31     ` Zhang Yi
2026-09-24 11:40       ` Ojaswin Mujoo
2026-09-03 12:35 ` [PATCH v6 04/31] ext4: set EXT4_MAP_NEW flag for delayed allocated blocks Zhang Yi
2026-09-03 12:54   ` sashiko-bot
2026-09-24 11:42   ` Ojaswin Mujoo
2026-09-03 12:35 ` [PATCH v6 05/31] ext4: recheck extent status tree before block allocation Zhang Yi
2026-09-03 13:02   ` sashiko-bot
2026-09-09  9:27     ` Zhang Yi
2026-09-24 13:22   ` Ojaswin Mujoo
2026-09-28  7:15     ` Zhang Yi
2026-09-03 12:35 ` [PATCH v6 06/31] ext4: fix orig_mlen initialization in ext4_map_blocks() Zhang Yi
2026-09-03 12:55   ` sashiko-bot
2026-09-24 13:24   ` Ojaswin Mujoo
2026-09-03 12:35 ` [PATCH v6 07/31] ext4: allow ext4_map_blocks() to start its own transaction handle Zhang Yi
2026-09-03 13:02   ` sashiko-bot
2026-09-28 10:19   ` Ojaswin Mujoo
2026-09-03 12:35 ` [PATCH v6 08/31] ext4: avoid unnecessary transaction in ext4_map_blocks() for unwritten extents Zhang Yi
2026-09-03 13:06   ` sashiko-bot
2026-09-28 10:21   ` Ojaswin Mujoo
2026-09-28 11:57     ` Zhang Yi
2026-09-03 12:35 ` [PATCH v6 09/31] ext4: skip block allocation for holes in the data submission path Zhang Yi
2026-09-03 13:46   ` sashiko-bot
2026-09-09  7:13     ` Zhang Yi
2026-09-28 10:36   ` Ojaswin Mujoo
2026-09-28 12:10     ` Zhang Yi
2026-09-03 12:35 ` [PATCH v6 10/31] ext4: add iomap address space operations for buffered I/O Zhang Yi
2026-09-03 12:57   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 11/31] ext4: implement buffered read path using iomap Zhang Yi
2026-09-03 13:12   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 12/31] ext4: pass out extent seq counter when mapping da blocks Zhang Yi
2026-09-03 13:05   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 13/31] ext4: do not use data=ordered mode for inodes using buffered iomap path Zhang Yi
2026-09-03 13:13   ` sashiko-bot
2026-09-29 14:20   ` Ojaswin Mujoo
2026-09-03 12:35 ` [PATCH v6 14/31] ext4: implement buffered write path using iomap Zhang Yi
2026-09-03 13:23   ` sashiko-bot
2026-09-10 12:14     ` Zhang Yi
2026-09-03 12:35 ` [PATCH v6 15/31] ext4: implement writeback " Zhang Yi
2026-09-03 13:36   ` sashiko-bot
2026-09-12  8:28     ` Zhang Yi
2026-09-30  8:51       ` Ojaswin Mujoo
2026-09-30  9:26         ` Zhang Yi [this message]
2026-09-30 10:36           ` Ojaswin Mujoo
2026-09-03 12:35 ` [PATCH v6 16/31] ext4: implement mmap " Zhang Yi
2026-09-03 13:29   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 17/31] ext4: implement partial block zero range " Zhang Yi
2026-09-03 13:29   ` sashiko-bot
2026-09-30 12:24   ` Ojaswin Mujoo
2026-09-03 12:35 ` [PATCH v6 18/31] ext4: drain writeback before removing extents on the iomap path Zhang Yi
2026-09-03 13:19   ` sashiko-bot
2026-10-01 11:05   ` Ojaswin Mujoo
2026-09-03 12:35 ` [PATCH v6 19/31] ext4: add block mapping tracepoints for iomap buffered I/O path Zhang Yi
2026-09-03 13:19   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 20/31] ext4: disable online defrag when inode using " Zhang Yi
2026-09-03 13:25   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 21/31] ext4: add EXT4_STATE_DISKSIZE_GROW_PENDING state bit and helpers Zhang Yi
2026-09-03 13:19   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 22/31] ext4: submit and wait for pending disksize-grow I/O on writeback Zhang Yi
2026-09-03 13:40   ` sashiko-bot
2026-09-14  7:27     ` Zhang Yi
2026-09-03 12:35 ` [PATCH v6 23/31] ext4: advance i_disksize to i_size upon disksize-grow I/O completion Zhang Yi
2026-09-03 13:31   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 24/31] ext4: defer i_disksize update while DISKSIZE_GROW_PENDING is set Zhang Yi
2026-09-03 13:38   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 25/31] ext4: submit and wait for disksize-grow I/O in fallocate paths Zhang Yi
2026-09-03 13:42   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 26/31] ext4: clear DISKSIZE_GROW_PENDING on truncate or error Zhang Yi
2026-09-03 13:46   ` sashiko-bot
2026-09-14  7:37     ` Zhang Yi
2026-09-03 12:35 ` [PATCH v6 27/31] ext4: set DISKSIZE_GROW_PENDING after zeroing unaligned EOF block Zhang Yi
2026-09-03 13:36   ` sashiko-bot
2026-09-03 12:35 ` [PATCH v6 28/31] ext4: add tracepoints for DISKSIZE_GROW_PENDING set, clear, and wait Zhang Yi
2026-09-03 13:34   ` sashiko-bot
2026-09-03 12:40 ` [PATCH v6 29/31] ext4: add tracepoints for EOF block zeroing and disksize-grow I/O Zhang Yi
2026-09-03 13:32   ` sashiko-bot
2026-09-03 12:40 ` [PATCH v6 30/31] ext4: partially enable iomap for the buffered I/O path of regular files Zhang Yi
2026-09-03 14:04   ` sashiko-bot
2026-09-14  8:38     ` Zhang Yi
2026-09-03 12:40 ` [PATCH v6 31/31] ext4: introduce a mount option for iomap buffered I/O path Zhang Yi
2026-09-03 13:46   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=217106f8-bad8-4834-bc1a-30f3197bfc63@gmail.com \
    --to=yizhang089@gmail.com \
    --cc=linux-ext4@vger.kernel.org \
    --cc=ojaswin@linux.ibm.com \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=tytso@mit.edu \
    --cc=yi.zhang@huaweicloud.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.