Re: [PATCH 1/3] ext4: Teach ext4_meta_trans_blocks() about number of allocated extents

Ojaswin Mujoo <[email protected]> Fri, 7 Aug 2026 12:58:05 +0530
Newsgroups org.kernel.vger.linux-ext4
Message-ID <[email protected]>
On Fri, Aug 07, 2026 at 03:05:09PM +0800, Zhang Yi wrote:
> On 8/7/2026 1:59 PM, Ojaswin Mujoo wrote:
> > On Fri, Aug 07, 2026 at 10:59:17AM +0800, Zhang Yi wrote:
> >> On 8/5/2026 11:35 PM, Jan Kara wrote:
> >>> So far ext4_meta_trans_blocks() expects that each extent counted in
> >>> @pextents will be allocated in the transaction we estimate credits for.
> >>> This is correct for the use in ext4_chunk_trans_blocks() and
> >>> ext4_chunk_trans_extent() however the use in atomic write path
> >>> (ext4_convert_unwritten_extents_atomic() and ext4_iomap_alloc() for
> >>> IOMAP_ATOMIC) unnecessarily overestimates the number of necessary
> >>> credits as neither of them allocates any data. Add argument to
> >>> ext4_meta_trans_blocks() for number of extents that are going to be
> >>> allocated in the transaction.
> >>>
> >>> Signed-off-by: Jan Kara <[email protected]>
> >>
> >> Hi Jan,
> >>
> >> I have some questions below.
> >>
> >>> ---
> >>>  fs/ext4/ext4.h    |  2 +-
> >>>  fs/ext4/extents.c |  2 +-
> >>>  fs/ext4/inode.c   | 32 +++++++++++++++++---------------
> >>>  3 files changed, 19 insertions(+), 17 deletions(-)
> >>>
> >>> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> >>> index b37c136ea3ab..6e0cc9b845ae 100644
> >>> --- a/fs/ext4/ext4.h
> >>> +++ b/fs/ext4/ext4.h
> >>> @@ -3184,7 +3184,7 @@ extern int ext4_normal_submit_inode_data_buffers(struct jbd2_inode *jinode);
> >>>  extern int ext4_chunk_trans_blocks(struct inode *, int nrblocks);
> >>>  extern int ext4_chunk_trans_extent(struct inode *inode, int nrblocks);
> >>>  extern int ext4_meta_trans_blocks(struct inode *inode, int lblocks,
> >>> -				  int pextents);
> >>> +				  int pextents, int alloc_extents);
> >>>  extern int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end);
> >>>  extern int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart,
> >>>  				    loff_t length, bool *did_zero);
> >>> diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
> >>> index 91c97af64b31..44ab246a3176 100644
> >>> --- a/fs/ext4/extents.c
> >>> +++ b/fs/ext4/extents.c
> >>> @@ -4976,7 +4976,7 @@ int ext4_convert_unwritten_extents_atomic(handle_t *handle, struct inode *inode,
> >>>  		 * it can tell if the extent in the cache is a split extent.
> >>>  		 * But for now let's assume pextents as 2 always.
> >>>  		 */
> >>> -		credits = ext4_meta_trans_blocks(inode, max_blocks, 2);
> >>> +		credits = ext4_meta_trans_blocks(inode, max_blocks, 2, 0);
> >>>  	}
> >>>  
> >>>  	if (credits) {
> >>> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> >>> index ce99807c5f5b..f324a54f1dae 100644
> >>> --- a/fs/ext4/inode.c
> >>> +++ b/fs/ext4/inode.c
> >>> @@ -3693,9 +3693,11 @@ static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map,
> >>>  		if (ret < 0)
> >>>  			return ret;
> >>>  		if (map->m_len < orig_mlen) {
> >>> +			int bpg = EXT4_BLOCKS_PER_GROUP(inode->i_sb);
> >>> +
> >>>  			map->m_len = orig_mlen;
> >>> -			dio_credits = ext4_meta_trans_blocks(inode, orig_mlen,
> >>> -							     map->m_len);
> >>> +			dio_credits = ext4_meta_trans_blocks(inode, map->m_len,
> >>> +							     map->m_len, 0);
> >>
> >> This pertains to the mixed map case, where holes may exist in between.
> >> My understanding is that we cannot assume all blocks are already
> >> allocated. Is this right?
> > 
> > Hi Yi,
> > 
> > With atomic writes, we can only allow multi block writes when bigalloc
> > is set. So the common cases are that the whole bigalloc cluster is
> > present or whole is missing.
> > 
> > If we do see map->m_len < orig_mlen that means the bigalloc cluster
> > range we queried has some allocation. This can only happen if someone
> > punched through the cluster because punch-hole can still happen at block
> > granularity.
> > 
> > Regardless, if we do see a mixed map here, it means the cluster is
> > allocated already, just a few blocks are punched in it, hence we won't
> > need to do any allocation.
> > 
> > Regards,
> > ojaswin
> 
> Hi, ojaswin,
> 
> Thank you for the clarification!
> 
> My initial concern wasn't about the new cluster allocation, but rather
> the possibility of a new extent being allocated in this cluster. Which
> means the hole-covered portion might require a new extent.
> 
> But after looking at ext4_map_blocks_atomic_write_slow(), I see that it
> guarantees both the old and new blocks in this cluster are written type,
> so the new range ought to merge into the existing extent. Furthermore,
> ext4_ext_insert_extent() does not perform merging across leaf extent
> blocks, so the extent count remains unchanged — no new extent
> allocation should happen. But this point is indeed subtle, as you
> suggested, adding a comment would be helpful.

Yes that's correct, we will keep merging into the existing extents so
there will be no new allocation (data or metadata) needed.

Regards,
ojaswin

> 
> Thanks,
> Yi.
> 
> > 
> >>
> >>>  		} else {
> >>>  			dio_credits = ext4_chunk_trans_blocks(inode,
> >>>  							      map->m_len);
> >>> @@ -6307,17 +6309,17 @@ static int ext4_index_trans_blocks(struct inode *inode, int lblocks,
> >>>  }
> >>>  
> >>>  /*
> >>> - * Account for index blocks, block groups bitmaps and block group
> >>> - * descriptor blocks if modify datablocks and index blocks
> >>> - * worse case, the indexs blocks spread over different block groups
> >>> - *
> >>> - * If datablocks are discontiguous, they are possible to spread over
> >>> - * different block groups too. If they are contiguous, with flexbg,
> >>> - * they could still across block group boundary.
> >>> - *
> >>> - * Also account for superblock, inode, quota and xattr blocks
> >>> + * Calculate number of credits needed in a transaction to:
> >>> + *   * Allocate data blocks from @alloc_extents different groups - note that
> >>> + *     with flexbg a single physical extent can span multiple groups but
> >>> + *     single mballoc request only returns extent within one group.
> >>> + *   * Allocate metatadata (extent tree blocks, indirect blocks) to store
> >>> + *     pointers to @pextents data extents having @lblocks in total.
> >>> + *   * Modify extent tree / indirect block tree, inode, superblock, quota
> >>> + *     tracking, xattr blocks
> >>>   */
> >>> -int ext4_meta_trans_blocks(struct inode *inode, int lblocks, int pextents)
> >>> +int ext4_meta_trans_blocks(struct inode *inode, int lblocks, int pextents,
> >>> +			   int alloc_extents)
> >>
> >> From what I can see, alloc_extents currently can only be 0 or equal to
> >> pextents — 0 means a pure conversion, and equal to pextents means a new
> >> allocation. pextents is supposed to cover the total count of
> >> alloc_extents. Should we add some sanity checks to guard against invalid
> >> inputs, like alloc_extents > pextents?
> >>
> >> Thanks,
> >> Yi.
> >>
> >>>  {
> >>>  	ext4_group_t groups, ngroups = ext4_get_groups_count(inode->i_sb);
> >>>  	int gdpblocks;
> >>> @@ -6334,7 +6336,7 @@ int ext4_meta_trans_blocks(struct inode *inode, int lblocks, int pextents)
> >>>  	 * Now let's see how many group bitmaps and group descriptors need
> >>>  	 * to account
> >>>  	 */
> >>> -	groups = idxblocks + pextents;
> >>> +	groups = idxblocks + alloc_extents;
> >>>  	gdpblocks = groups;
> >>>  	if (groups > ngroups)
> >>>  		groups = ngroups;
> >>> @@ -6360,7 +6362,7 @@ int ext4_chunk_trans_extent(struct inode *inode, int nrblocks)
> >>>  {
> >>>  	int ret;
> >>>  
> >>> -	ret = ext4_meta_trans_blocks(inode, nrblocks, 1);
> >>> +	ret = ext4_meta_trans_blocks(inode, nrblocks, 1, 1);
> >>>  	/* Account for data blocks for journalled mode */
> >>>  	if (ext4_should_journal_data(inode))
> >>>  		ret += nrblocks;
> >>> @@ -6378,7 +6380,7 @@ int ext4_chunk_trans_extent(struct inode *inode, int nrblocks)
> >>>   */
> >>>  int ext4_chunk_trans_blocks(struct inode *inode, int nrblocks)
> >>>  {
> >>> -	return ext4_meta_trans_blocks(inode, nrblocks, 1);
> >>> +	return ext4_meta_trans_blocks(inode, nrblocks, 1, 1);
> >>>  }
> >>>  
> >>>  /*
> >>
>