[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250611111625.1668035-4-yi.zhang@huaweicloud.com>
Date: Wed, 11 Jun 2025 19:16:22 +0800
From: Zhang Yi <yi.zhang@...weicloud.com>
To: linux-ext4@...r.kernel.org
Cc: linux-fsdevel@...r.kernel.org,
linux-kernel@...r.kernel.org,
tytso@....edu,
adilger.kernel@...ger.ca,
jack@...e.cz,
ojaswin@...ux.ibm.com,
yi.zhang@...wei.com,
yi.zhang@...weicloud.com,
libaokun1@...wei.com,
yukuai3@...wei.com,
yangerkun@...wei.com
Subject: [PATCH v2 3/6] ext4: restart handle if credits are insufficient during allocating blocks
From: Zhang Yi <yi.zhang@...wei.com>
After large folios are supported on ext4, writing back a sufficiently
large and discontinuous folio may consume a significant number of
journal credits, placing considerable strain on the journal. For
example, in a 20GB filesystem with 1K block size and 1MB journal size,
writing back a 2MB folio could require thousands of credits in the
worst-case scenario (when each block is discontinuous and distributed
across different block groups), potentially exceeding the journal size.
This issue can also occur in ext4_write_begin() and ext4_page_mkwrite()
when delalloc is not enabled.
Fix this by ensuring that there are sufficient journal credits before
allocating an extent in mpage_map_one_extent() and _ext4_get_block(). If
there are not enough credits, return -EAGAIN, exit the current mapping
loop, restart a new handle and a new transaction, and allocating blocks
on this folio again in the next iteration.
Suggested-by: Jan Kara <jack@...e.cz>
Signed-off-by: Zhang Yi <yi.zhang@...wei.com>
---
fs/ext4/inode.c | 45 +++++++++++++++++++++++++++++++++++++++------
1 file changed, 39 insertions(+), 6 deletions(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index d0db6e3bf158..b51de58518b2 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -877,20 +877,44 @@ static void ext4_update_bh_state(struct buffer_head *bh, unsigned long flags)
} while (unlikely(!try_cmpxchg(&bh->b_state, &old_state, new_state)));
}
+/*
+ * Make sure that the current journal transaction has enough credits to map
+ * one extent. Return -EAGAIN if it cannot extend the current running
+ * transaction.
+ */
+static inline int ext4_journal_ensure_extent_credits(handle_t *handle,
+ struct inode *inode)
+{
+ int needed_credits;
+ int ret;
+
+ needed_credits = ext4_chunk_trans_blocks(inode, 1);
+ ret = __ext4_journal_ensure_credits(handle, needed_credits,
+ needed_credits, 0);
+ return ret <= 0 ? ret : -EAGAIN;
+}
+
static int _ext4_get_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh, int flags)
{
struct ext4_map_blocks map;
+ handle_t *handle = ext4_journal_current_handle();
int ret = 0;
if (ext4_has_inline_data(inode))
return -ERANGE;
+ /* Make sure transaction has enough credits for this extent */
+ if (flags & EXT4_GET_BLOCKS_CREATE) {
+ ret = ext4_journal_ensure_extent_credits(handle, inode);
+ if (ret)
+ return ret;
+ }
+
map.m_lblk = iblock;
map.m_len = bh->b_size >> inode->i_blkbits;
- ret = ext4_map_blocks(ext4_journal_current_handle(), inode, &map,
- flags);
+ ret = ext4_map_blocks(handle, inode, &map, flags);
if (ret > 0) {
map_bh(bh, inode->i_sb, map.m_pblk);
ext4_update_bh_state(bh, map.m_flags);
@@ -1374,8 +1398,9 @@ static int ext4_write_begin(struct file *file, struct address_space *mapping,
ext4_orphan_del(NULL, inode);
}
- if (ret == -ENOSPC &&
- ext4_should_retry_alloc(inode->i_sb, &retries))
+ if (ret == -EAGAIN ||
+ (ret == -ENOSPC &&
+ ext4_should_retry_alloc(inode->i_sb, &retries)))
goto retry_journal;
folio_put(folio);
return ret;
@@ -2324,6 +2349,11 @@ static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)
int get_blocks_flags;
int err, dioread_nolock;
+ /* Make sure transaction has enough credits for this extent */
+ err = ext4_journal_ensure_extent_credits(handle, inode);
+ if (err < 0)
+ return err;
+
trace_ext4_da_write_pages_extent(inode, map);
/*
* Call ext4_map_blocks() to allocate any delayed allocation blocks, or
@@ -2446,7 +2476,7 @@ static int mpage_map_and_submit_extent(handle_t *handle,
* In the case of ENOSPC, if ext4_count_free_blocks()
* is non-zero, a commit should free up blocks.
*/
- if ((err == -ENOMEM) ||
+ if ((err == -ENOMEM) || (err == -EAGAIN) ||
(err == -ENOSPC && ext4_count_free_clusters(sb))) {
/*
* We may have already allocated extents for
@@ -2953,6 +2983,8 @@ static int ext4_do_writepages(struct mpage_da_data *mpd)
ret = 0;
continue;
}
+ if (ret == -EAGAIN)
+ ret = 0;
/* Fatal error - ENOMEM, EIO... */
if (ret)
break;
@@ -6722,7 +6754,8 @@ vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf)
}
}
ext4_journal_stop(handle);
- if (err == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
+ if (err == -EAGAIN ||
+ (err == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries)))
goto retry_alloc;
out_ret:
ret = vmf_fs_error(err);
--
2.46.1
Powered by blists - more mailing lists