* [PATCH -next v2 0/7] ext4: defer unwritten splitting until I/O completion
@ 2025-12-23 1:17 Zhang Yi
2025-12-23 1:17 ` [PATCH -next v2 1/7] ext4: use reserved metadata blocks when splitting extent on endio Zhang Yi
` (6 more replies)
0 siblings, 7 replies; 25+ messages in thread
From: Zhang Yi @ 2025-12-23 1:17 UTC (permalink / raw)
To: linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yi.zhang, yizhang089, libaokun1,
yangerkun, yukuai
From: Zhang Yi <yi.zhang@huawei.com>
Changes sicne v1:
- In patch 03, add a comment explaining how DAX writes to unwritten
extents, as Jan suggested.
v1: https://lore.kernel.org/linux-ext4/20251213022008.1766912-1-yi.zhang@huaweicloud.com/
This series proposes deferring the splitting of unwritten extents from
the point of I/O submission until I/O completion when partially writing
to a preallocated file.
This change primarily needs to address whether it will increase the
likelihood of extent conversion failure due to the inability to split
extents in scenarios with insufficient space, which could result in I/O
write failures and data loss. After analysis, it has been confirmed that
two existing mechanisms ensure I/O operations do not fail.
The first is the EXT4_GET_BLOCKS_METADATA_NOFAIL flag, which is a best
effort, it permits the use of 2% of the reserved space or 4,096 blocks
in the file system when splitting extents. This flag covers most
scenarios where extent splitting might fail. The second is the
EXT4_EXT_MAY_ZEROOUT flag, which is also set during extent splitting. If
the reserved space is insufficient and splitting fails, it does not
retry the allocation. Instead, it directly zeros out the extra part of
the extent, thereby avoiding splitting and directly converting the
entire extent to the written type.
These two mechanisms currently have no difference before I/O submission
or after I/O completion. Therefore, Although deferring extent splitting
will add pressure on reserved space after I/O completion, but it won't
increase the risk of I/O failure and data loss. On the contrary, if some
I/Os can be merged when I/O completion during writeback, it can also
reduce unnecessary splitting operations, thereby alleviating the
pressure on reserved space.
In addition, deferring extent splitting until I/O completion can also
simplify the I/O submission process and avoid initiating unnecessary
journal handles when writing unwritten extents.
Patch 01-03: defer splitting extent until I/O completion.
Patch 04-07: do some cleanup of the DIO path and remove
EXT4_GET_BLOCKS_IO_CREATE_EXT.
Tests:
- Run xfstests with the -g enospc option approximately 50 times. Before
applying this series, the reserved blocks were used over 6000/7000
times on a 1 GB filesystem with a 4 KB / 1 KB block size. After
applying this series, the counts remain nearly the same. In both
cases, there were no splitting failures.
- Run xfstests with the -g enospc option about one day, no regressions
occurred.
- Intentionally create a scenario in which reserved blocks are
exhausted. Before applying the patch, zero out the extent before I/O
submission; after applying the patch, zero out the extent after I/O
completion. There are no other differences.
- xfstests-bld shows no regression.
Performance:
This can improve the write performance of concurrent DIO for multiple
files. The fio tests below show a ~25% performance improvement when
wirting to unwritten files on my VM with a 100G memory backed disk.
[unwritten]
direct=1
ioengine=psync
numjobs=16
rw=write # write/randwrite
bs=4K
iodepth=1
directory=/mnt
size=5G
runtime=30s
overwrite=0
norandommap=1
fallocate=native
ramp_time=5s
group_reporting=1
[w/o]
w: IOPS=62.5k, BW=244MiB/s
rw: IOPS=56.7k, BW=221MiB/s
[w]
w: IOPS=79.6k, BW=311MiB/s
rw: IOPS=70.2k, BW=274MiB/s
TODO:
Next, we can investigate whether, during the buffer I/O write-back
process, writing an unwritten extent can also avoid initiating a journal
handle.
Thank,
Yi.
Zhang Yi (7):
ext4: use reserved metadata blocks when splitting extent on endio
ext4: don't split extent before submitting I/O
ext4: avoid starting handle when dio writing an unwritten extent
ext4: remove useless ext4_iomap_overwrite_ops
ext4: remove unused unwritten parameter in ext4_dio_write_iter()
ext4: simply the mapping query logic in ext4_iomap_begin()
ext4: remove EXT4_GET_BLOCKS_IO_CREATE_EXT
fs/ext4/ext4.h | 10 ---------
fs/ext4/extents.c | 46 ++++----------------------------------
fs/ext4/file.c | 23 ++++++++-----------
fs/ext4/inode.c | 56 ++++++++++++-----------------------------------
4 files changed, 27 insertions(+), 108 deletions(-)
--
2.52.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH -next v2 1/7] ext4: use reserved metadata blocks when splitting extent on endio
2025-12-23 1:17 [PATCH -next v2 0/7] ext4: defer unwritten splitting until I/O completion Zhang Yi
@ 2025-12-23 1:17 ` Zhang Yi
2025-12-31 7:34 ` Baokun Li
2026-01-03 13:42 ` Ojaswin Mujoo
2025-12-23 1:17 ` [PATCH -next v2 2/7] ext4: don't split extent before submitting I/O Zhang Yi
` (5 subsequent siblings)
6 siblings, 2 replies; 25+ messages in thread
From: Zhang Yi @ 2025-12-23 1:17 UTC (permalink / raw)
To: linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yi.zhang, yizhang089, libaokun1,
yangerkun, yukuai
From: Zhang Yi <yi.zhang@huawei.com>
When performing buffered writes, we may need to split and convert an
unwritten extent into a written one during the end I/O process. However,
we do not reserve space specifically for these metadata changes, we only
reserve 2% of space or 4096 blocks. To address this, we use
EXT4_GET_BLOCKS_PRE_IO to potentially split extents in advance and
EXT4_GET_BLOCKS_METADATA_NOFAIL to utilize reserved space if necessary.
These two approaches can reduce the likelihood of running out of space
and losing data. However, these methods are merely best efforts, we
could still run out of space, and there is not much difference between
converting an extent during the writeback process and the end I/O
process, it won't increase the rick of losing data if we postpone the
conversion.
Therefore, also use EXT4_GET_BLOCKS_METADATA_NOFAIL in
ext4_convert_unwritten_extents_endio() to prepare for the buffered I/O
iomap conversion, which may perform extent conversion during the end I/O
process.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
---
fs/ext4/extents.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index 27eb2c1df012..e53959120b04 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -3794,6 +3794,8 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
* illegal.
*/
if (ee_block != map->m_lblk || ee_len > map->m_len) {
+ int flags = EXT4_GET_BLOCKS_CONVERT |
+ EXT4_GET_BLOCKS_METADATA_NOFAIL;
#ifdef CONFIG_EXT4_DEBUG
ext4_warning(inode->i_sb, "Inode (%ld) finished: extent logical block %llu,"
" len %u; IO logical block %llu, len %u",
@@ -3801,7 +3803,7 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
(unsigned long long)map->m_lblk, map->m_len);
#endif
path = ext4_split_convert_extents(handle, inode, map, path,
- EXT4_GET_BLOCKS_CONVERT, NULL);
+ flags, NULL);
if (IS_ERR(path))
return path;
--
2.52.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH -next v2 2/7] ext4: don't split extent before submitting I/O
2025-12-23 1:17 [PATCH -next v2 0/7] ext4: defer unwritten splitting until I/O completion Zhang Yi
2025-12-23 1:17 ` [PATCH -next v2 1/7] ext4: use reserved metadata blocks when splitting extent on endio Zhang Yi
@ 2025-12-23 1:17 ` Zhang Yi
2025-12-31 7:35 ` Baokun Li
2026-01-03 13:47 ` Ojaswin Mujoo
2025-12-23 1:17 ` [PATCH -next v2 3/7] ext4: avoid starting handle when dio writing an unwritten extent Zhang Yi
` (4 subsequent siblings)
6 siblings, 2 replies; 25+ messages in thread
From: Zhang Yi @ 2025-12-23 1:17 UTC (permalink / raw)
To: linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yi.zhang, yizhang089, libaokun1,
yangerkun, yukuai
From: Zhang Yi <yi.zhang@huawei.com>
Currently, when writing back dirty pages to the filesystem with the
dioread_nolock feature enabled and when doing DIO, if the area to be
written back is part of an unwritten extent, the
EXT4_GET_BLOCKS_IO_CREATE_EXT flag is set during block allocation before
submitting I/O. The function ext4_split_convert_extents() then attempts
to split this extent in advance. This approach is designed to prevents
extent splitting and conversion to the written type from failing due to
insufficient disk space at the time of I/O completion, which could
otherwise result in data loss.
However, we already have two mechanisms to ensure successful extent
conversion. The first is the EXT4_GET_BLOCKS_METADATA_NOFAIL flag, which
is a best effort, it permits the use of 2% of the reserved space or
4,096 blocks in the file system when splitting extents. This flag covers
most scenarios where extent splitting might fail. The second is the
EXT4_EXT_MAY_ZEROOUT flag, which is also set during extent splitting. If
the reserved space is insufficient and splitting fails, it does not
retry the allocation. Instead, it directly zeros out the extra part of
the extent, thereby avoiding splitting and directly converting the
entire extent to the written type.
These two mechanisms also exist when I/Os are completed because there is
a concurrency window between write-back and fallocate, which may still
require us to split extents upon I/O completion. There is no much
difference between splitting extents before submitting I/O. Therefore,
It seems possible to defer the splitting until I/O completion, it won't
increase the risk of I/O failure and data loss. On the contrary, if some
I/Os can be merged when I/O completion, it can also reduce unnecessary
splitting operations, thereby alleviating the pressure on reserved
space.
In addition, deferring extent splitting until I/O completion can
also simplify the IO submission process and avoid initiating unnecessary
journal handles when writing unwritten extents.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
---
fs/ext4/extents.c | 13 +------------
fs/ext4/inode.c | 4 ++--
2 files changed, 3 insertions(+), 14 deletions(-)
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index e53959120b04..c98f7c5482b4 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -3787,21 +3787,10 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
ext_debug(inode, "logical block %llu, max_blocks %u\n",
(unsigned long long)ee_block, ee_len);
- /* If extent is larger than requested it is a clear sign that we still
- * have some extent state machine issues left. So extent_split is still
- * required.
- * TODO: Once all related issues will be fixed this situation should be
- * illegal.
- */
if (ee_block != map->m_lblk || ee_len > map->m_len) {
int flags = EXT4_GET_BLOCKS_CONVERT |
EXT4_GET_BLOCKS_METADATA_NOFAIL;
-#ifdef CONFIG_EXT4_DEBUG
- ext4_warning(inode->i_sb, "Inode (%ld) finished: extent logical block %llu,"
- " len %u; IO logical block %llu, len %u",
- inode->i_ino, (unsigned long long)ee_block, ee_len,
- (unsigned long long)map->m_lblk, map->m_len);
-#endif
+
path = ext4_split_convert_extents(handle, inode, map, path,
flags, NULL);
if (IS_ERR(path))
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index bb8165582840..ffde24ff7347 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -2376,7 +2376,7 @@ static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)
dioread_nolock = ext4_should_dioread_nolock(inode);
if (dioread_nolock)
- get_blocks_flags |= EXT4_GET_BLOCKS_IO_CREATE_EXT;
+ get_blocks_flags |= EXT4_GET_BLOCKS_UNWRIT_EXT;
err = ext4_map_blocks(handle, inode, map, get_blocks_flags);
if (err < 0)
@@ -3744,7 +3744,7 @@ static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map,
else if (EXT4_LBLK_TO_B(inode, map->m_lblk) >= i_size_read(inode))
m_flags = EXT4_GET_BLOCKS_CREATE;
else if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
- m_flags = EXT4_GET_BLOCKS_IO_CREATE_EXT;
+ m_flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT;
if (flags & IOMAP_ATOMIC)
ret = ext4_map_blocks_atomic_write(handle, inode, map, m_flags,
--
2.52.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH -next v2 3/7] ext4: avoid starting handle when dio writing an unwritten extent
2025-12-23 1:17 [PATCH -next v2 0/7] ext4: defer unwritten splitting until I/O completion Zhang Yi
2025-12-23 1:17 ` [PATCH -next v2 1/7] ext4: use reserved metadata blocks when splitting extent on endio Zhang Yi
2025-12-23 1:17 ` [PATCH -next v2 2/7] ext4: don't split extent before submitting I/O Zhang Yi
@ 2025-12-23 1:17 ` Zhang Yi
2025-12-31 7:36 ` Baokun Li
2026-01-03 14:06 ` Ojaswin Mujoo
2025-12-23 1:17 ` [PATCH -next v2 4/7] ext4: remove useless ext4_iomap_overwrite_ops Zhang Yi
` (3 subsequent siblings)
6 siblings, 2 replies; 25+ messages in thread
From: Zhang Yi @ 2025-12-23 1:17 UTC (permalink / raw)
To: linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yi.zhang, yizhang089, libaokun1,
yangerkun, yukuai
From: Zhang Yi <yi.zhang@huawei.com>
Since we have deferred the split of the unwritten extent until after I/O
completion, it is not necessary to initiate the journal handle when
submitting the I/O.
This can improve the write performance of concurrent DIO for multiple
files. The fio tests below show a ~25% performance improvement when
wirting to unwritten files on my VM with a mem disk.
[unwritten]
direct=1
ioengine=psync
numjobs=16
rw=write # write/randwrite
bs=4K
iodepth=1
directory=/mnt
size=5G
runtime=30s
overwrite=0
norandommap=1
fallocate=native
ramp_time=5s
group_reporting=1
[w/o]
w: IOPS=62.5k, BW=244MiB/s
rw: IOPS=56.7k, BW=221MiB/s
[w]
w: IOPS=79.6k, BW=311MiB/s
rw: IOPS=70.2k, BW=274MiB/s
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
---
fs/ext4/file.c | 4 +---
fs/ext4/inode.c | 9 +++++++--
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/fs/ext4/file.c b/fs/ext4/file.c
index 7a8b30932189..9f571acc7782 100644
--- a/fs/ext4/file.c
+++ b/fs/ext4/file.c
@@ -418,9 +418,7 @@ static const struct iomap_dio_ops ext4_dio_write_ops = {
* updating inode i_disksize and/or orphan handling with exclusive lock.
*
* - shared locking will only be true mostly with overwrites, including
- * initialized blocks and unwritten blocks. For overwrite unwritten blocks
- * we protect splitting extents by i_data_sem in ext4_inode_info, so we can
- * also release exclusive i_rwsem lock.
+ * initialized blocks and unwritten blocks.
*
* - Otherwise we will switch to exclusive i_rwsem lock.
*/
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index ffde24ff7347..ff3ad1a2df45 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -3817,9 +3817,14 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
ret = ext4_map_blocks(NULL, inode, &map, 0);
/*
* For atomic writes the entire requested length should
- * be mapped.
+ * be mapped. For DAX we convert extents to initialized
+ * ones before copying the data, otherwise we do it
+ * after I/O so there's no need to call into
+ * ext4_iomap_alloc().
*/
- if (map.m_flags & EXT4_MAP_MAPPED) {
+ if ((map.m_flags & EXT4_MAP_MAPPED) ||
+ (!(flags & IOMAP_DAX) &&
+ (map.m_flags & EXT4_MAP_UNWRITTEN))) {
if ((!(flags & IOMAP_ATOMIC) && ret > 0) ||
(flags & IOMAP_ATOMIC && ret >= orig_mlen))
goto out;
--
2.52.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH -next v2 4/7] ext4: remove useless ext4_iomap_overwrite_ops
2025-12-23 1:17 [PATCH -next v2 0/7] ext4: defer unwritten splitting until I/O completion Zhang Yi
` (2 preceding siblings ...)
2025-12-23 1:17 ` [PATCH -next v2 3/7] ext4: avoid starting handle when dio writing an unwritten extent Zhang Yi
@ 2025-12-23 1:17 ` Zhang Yi
2025-12-31 7:40 ` Baokun Li
2026-01-03 14:14 ` Ojaswin Mujoo
2025-12-23 1:18 ` [PATCH -next v2 5/7] ext4: remove unused unwritten parameter in ext4_dio_write_iter() Zhang Yi
` (2 subsequent siblings)
6 siblings, 2 replies; 25+ messages in thread
From: Zhang Yi @ 2025-12-23 1:17 UTC (permalink / raw)
To: linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yi.zhang, yizhang089, libaokun1,
yangerkun, yukuai
From: Zhang Yi <yi.zhang@huawei.com>
ext4_iomap_overwrite_ops was introduced in commit 8cd115bdda17 ("ext4:
Optimize ext4 DIO overwrites"), which can optimize pure overwrite
performance by dropping the IOMAP_WRITE flag to only query the mapped
mapping information. This avoids starting a new journal handle, thereby
improving speed. Later, commit 9faac62d4013 ("ext4: optimize file
overwrites") also optimized similar scenarios, but it performs the check
later, examining the mappings status only when the actual block mapping
is needed. Thus, it can handle the previous commit scenario. That means
in the case of an overwrite scenario, the condition
"offset + length <= i_size_read(inode)" in the write path must always be
true.
Therefore, it is acceptable to remove the ext4_iomap_overwrite_ops,
which will also clarify the write and read paths of ext4_iomap_begin.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
---
fs/ext4/ext4.h | 1 -
fs/ext4/file.c | 5 +----
fs/ext4/inode.c | 24 ------------------------
3 files changed, 1 insertion(+), 29 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 56112f201cac..9a71357f192d 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3909,7 +3909,6 @@ static inline void ext4_clear_io_unwritten_flag(ext4_io_end_t *io_end)
}
extern const struct iomap_ops ext4_iomap_ops;
-extern const struct iomap_ops ext4_iomap_overwrite_ops;
extern const struct iomap_ops ext4_iomap_report_ops;
static inline int ext4_buffer_uptodate(struct buffer_head *bh)
diff --git a/fs/ext4/file.c b/fs/ext4/file.c
index 9f571acc7782..6b4b68f830d5 100644
--- a/fs/ext4/file.c
+++ b/fs/ext4/file.c
@@ -506,7 +506,6 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
struct inode *inode = file_inode(iocb->ki_filp);
loff_t offset = iocb->ki_pos;
size_t count = iov_iter_count(from);
- const struct iomap_ops *iomap_ops = &ext4_iomap_ops;
bool extend = false, unwritten = false;
bool ilock_shared = true;
int dio_flags = 0;
@@ -573,9 +572,7 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
goto out;
}
- if (ilock_shared && !unwritten)
- iomap_ops = &ext4_iomap_overwrite_ops;
- ret = iomap_dio_rw(iocb, from, iomap_ops, &ext4_dio_write_ops,
+ ret = iomap_dio_rw(iocb, from, &ext4_iomap_ops, &ext4_dio_write_ops,
dio_flags, NULL, 0);
if (ret == -ENOTBLK)
ret = 0;
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index ff3ad1a2df45..b84a2a10dfb8 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -3833,10 +3833,6 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
}
ret = ext4_iomap_alloc(inode, &map, flags);
} else {
- /*
- * This can be called for overwrites path from
- * ext4_iomap_overwrite_begin().
- */
ret = ext4_map_blocks(NULL, inode, &map, 0);
}
@@ -3865,30 +3861,10 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
return 0;
}
-static int ext4_iomap_overwrite_begin(struct inode *inode, loff_t offset,
- loff_t length, unsigned flags, struct iomap *iomap,
- struct iomap *srcmap)
-{
- int ret;
-
- /*
- * Even for writes we don't need to allocate blocks, so just pretend
- * we are reading to save overhead of starting a transaction.
- */
- flags &= ~IOMAP_WRITE;
- ret = ext4_iomap_begin(inode, offset, length, flags, iomap, srcmap);
- WARN_ON_ONCE(!ret && iomap->type != IOMAP_MAPPED);
- return ret;
-}
-
const struct iomap_ops ext4_iomap_ops = {
.iomap_begin = ext4_iomap_begin,
};
-const struct iomap_ops ext4_iomap_overwrite_ops = {
- .iomap_begin = ext4_iomap_overwrite_begin,
-};
-
static int ext4_iomap_begin_report(struct inode *inode, loff_t offset,
loff_t length, unsigned int flags,
struct iomap *iomap, struct iomap *srcmap)
--
2.52.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH -next v2 5/7] ext4: remove unused unwritten parameter in ext4_dio_write_iter()
2025-12-23 1:17 [PATCH -next v2 0/7] ext4: defer unwritten splitting until I/O completion Zhang Yi
` (3 preceding siblings ...)
2025-12-23 1:17 ` [PATCH -next v2 4/7] ext4: remove useless ext4_iomap_overwrite_ops Zhang Yi
@ 2025-12-23 1:18 ` Zhang Yi
2025-12-31 7:42 ` Baokun Li
2026-01-03 14:16 ` Ojaswin Mujoo
2025-12-23 1:18 ` [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin() Zhang Yi
2025-12-23 1:18 ` [PATCH -next v2 7/7] ext4: remove EXT4_GET_BLOCKS_IO_CREATE_EXT Zhang Yi
6 siblings, 2 replies; 25+ messages in thread
From: Zhang Yi @ 2025-12-23 1:18 UTC (permalink / raw)
To: linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yi.zhang, yizhang089, libaokun1,
yangerkun, yukuai
From: Zhang Yi <yi.zhang@huawei.com>
The parameter unwritten in ext4_dio_write_iter() is no longer needed,
simply remove it.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
---
fs/ext4/file.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/fs/ext4/file.c b/fs/ext4/file.c
index 6b4b68f830d5..fa22fc0e45f3 100644
--- a/fs/ext4/file.c
+++ b/fs/ext4/file.c
@@ -424,14 +424,14 @@ static const struct iomap_dio_ops ext4_dio_write_ops = {
*/
static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
bool *ilock_shared, bool *extend,
- bool *unwritten, int *dio_flags)
+ int *dio_flags)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
loff_t offset;
size_t count;
ssize_t ret;
- bool overwrite, unaligned_io;
+ bool overwrite, unaligned_io, unwritten;
restart:
ret = ext4_generic_write_checks(iocb, from);
@@ -443,7 +443,7 @@ static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
unaligned_io = ext4_unaligned_io(inode, from, offset);
*extend = ext4_extending_io(inode, offset, count);
- overwrite = ext4_overwrite_io(inode, offset, count, unwritten);
+ overwrite = ext4_overwrite_io(inode, offset, count, &unwritten);
/*
* Determine whether we need to upgrade to an exclusive lock. This is
@@ -458,7 +458,7 @@ static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
*/
if (*ilock_shared &&
((!IS_NOSEC(inode) || *extend || !overwrite ||
- (unaligned_io && *unwritten)))) {
+ (unaligned_io && unwritten)))) {
if (iocb->ki_flags & IOCB_NOWAIT) {
ret = -EAGAIN;
goto out;
@@ -481,7 +481,7 @@ static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
ret = -EAGAIN;
goto out;
}
- if (unaligned_io && (!overwrite || *unwritten))
+ if (unaligned_io && (!overwrite || unwritten))
inode_dio_wait(inode);
*dio_flags = IOMAP_DIO_FORCE_WAIT;
}
@@ -506,7 +506,7 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
struct inode *inode = file_inode(iocb->ki_filp);
loff_t offset = iocb->ki_pos;
size_t count = iov_iter_count(from);
- bool extend = false, unwritten = false;
+ bool extend = false;
bool ilock_shared = true;
int dio_flags = 0;
@@ -552,7 +552,7 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
ret = ext4_dio_write_checks(iocb, from, &ilock_shared, &extend,
- &unwritten, &dio_flags);
+ &dio_flags);
if (ret <= 0)
return ret;
--
2.52.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin()
2025-12-23 1:17 [PATCH -next v2 0/7] ext4: defer unwritten splitting until I/O completion Zhang Yi
` (4 preceding siblings ...)
2025-12-23 1:18 ` [PATCH -next v2 5/7] ext4: remove unused unwritten parameter in ext4_dio_write_iter() Zhang Yi
@ 2025-12-23 1:18 ` Zhang Yi
2025-12-31 7:46 ` Baokun Li
` (2 more replies)
2025-12-23 1:18 ` [PATCH -next v2 7/7] ext4: remove EXT4_GET_BLOCKS_IO_CREATE_EXT Zhang Yi
6 siblings, 3 replies; 25+ messages in thread
From: Zhang Yi @ 2025-12-23 1:18 UTC (permalink / raw)
To: linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yi.zhang, yizhang089, libaokun1,
yangerkun, yukuai
From: Zhang Yi <yi.zhang@huawei.com>
In the write path mapping check of ext4_iomap_begin(), the return value
'ret' should never greater than orig_mlen. If 'ret' equals 'orig_mlen',
it can be returned directly without checking IOMAP_ATOMIC.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
---
fs/ext4/inode.c | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index b84a2a10dfb8..67fe7d0f47e3 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -3816,17 +3816,19 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
if (offset + length <= i_size_read(inode)) {
ret = ext4_map_blocks(NULL, inode, &map, 0);
/*
- * For atomic writes the entire requested length should
- * be mapped. For DAX we convert extents to initialized
- * ones before copying the data, otherwise we do it
- * after I/O so there's no need to call into
- * ext4_iomap_alloc().
+ * For DAX we convert extents to initialized ones before
+ * copying the data, otherwise we do it after I/O so
+ * there's no need to call into ext4_iomap_alloc().
*/
if ((map.m_flags & EXT4_MAP_MAPPED) ||
(!(flags & IOMAP_DAX) &&
(map.m_flags & EXT4_MAP_UNWRITTEN))) {
- if ((!(flags & IOMAP_ATOMIC) && ret > 0) ||
- (flags & IOMAP_ATOMIC && ret >= orig_mlen))
+ /*
+ * For atomic writes the entire requested
+ * length should be mapped.
+ */
+ if (ret == orig_mlen ||
+ (!(flags & IOMAP_ATOMIC) && ret > 0))
goto out;
}
map.m_len = orig_mlen;
--
2.52.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH -next v2 7/7] ext4: remove EXT4_GET_BLOCKS_IO_CREATE_EXT
2025-12-23 1:17 [PATCH -next v2 0/7] ext4: defer unwritten splitting until I/O completion Zhang Yi
` (5 preceding siblings ...)
2025-12-23 1:18 ` [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin() Zhang Yi
@ 2025-12-23 1:18 ` Zhang Yi
2025-12-31 7:55 ` Baokun Li
2026-01-03 14:20 ` Ojaswin Mujoo
6 siblings, 2 replies; 25+ messages in thread
From: Zhang Yi @ 2025-12-23 1:18 UTC (permalink / raw)
To: linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yi.zhang, yizhang089, libaokun1,
yangerkun, yukuai
From: Zhang Yi <yi.zhang@huawei.com>
We do not use EXT4_GET_BLOCKS_IO_CREATE_EXT or split extents before
submitting I/O; therefore, remove the related code.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
---
fs/ext4/ext4.h | 9 ---------
fs/ext4/extents.c | 29 -----------------------------
fs/ext4/inode.c | 11 -----------
3 files changed, 49 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 9a71357f192d..174c51402864 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -707,15 +707,6 @@ enum {
* found an unwritten extent, we need to split it.
*/
#define EXT4_GET_BLOCKS_SPLIT_NOMERGE 0x0008
- /*
- * Caller is from the dio or dioread_nolock buffered IO, reqest to
- * create an unwritten extent if it does not exist or split the
- * found unwritten extent. Also do not merge the newly created
- * unwritten extent, io end will convert unwritten to written,
- * and try to merge the written extent.
- */
-#define EXT4_GET_BLOCKS_IO_CREATE_EXT (EXT4_GET_BLOCKS_SPLIT_NOMERGE|\
- EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT)
/* Convert unwritten extent to initialized. */
#define EXT4_GET_BLOCKS_CONVERT 0x0010
/* Eventual metadata allocation (due to growing extent tree)
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index c98f7c5482b4..c7c66ab825e7 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -3925,34 +3925,6 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
trace_ext4_ext_handle_unwritten_extents(inode, map, flags,
*allocated, newblock);
- /* get_block() before submitting IO, split the extent */
- if (flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE) {
- int depth;
-
- path = ext4_split_convert_extents(handle, inode, map, path,
- flags, allocated);
- if (IS_ERR(path))
- return path;
- /*
- * shouldn't get a 0 allocated when splitting an extent unless
- * m_len is 0 (bug) or extent has been corrupted
- */
- if (unlikely(*allocated == 0)) {
- EXT4_ERROR_INODE(inode,
- "unexpected allocated == 0, m_len = %u",
- map->m_len);
- err = -EFSCORRUPTED;
- goto errout;
- }
- /* Don't mark unwritten if the extent has been zeroed out. */
- path = ext4_find_extent(inode, map->m_lblk, path, flags);
- if (IS_ERR(path))
- return path;
- depth = ext_depth(inode);
- if (ext4_ext_is_unwritten(path[depth].p_ext))
- map->m_flags |= EXT4_MAP_UNWRITTEN;
- goto out;
- }
/* IO end_io complete, convert the filled extent to written */
if (flags & EXT4_GET_BLOCKS_CONVERT) {
path = ext4_convert_unwritten_extents_endio(handle, inode,
@@ -4006,7 +3978,6 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
goto errout;
}
-out:
map->m_flags |= EXT4_MAP_NEW;
map_out:
map->m_flags |= EXT4_MAP_MAPPED;
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 67fe7d0f47e3..2e79b09fe2f0 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -588,7 +588,6 @@ static int ext4_map_query_blocks(handle_t *handle, struct inode *inode,
static int ext4_map_create_blocks(handle_t *handle, struct inode *inode,
struct ext4_map_blocks *map, int flags)
{
- struct extent_status es;
unsigned int status;
int err, retval = 0;
@@ -649,16 +648,6 @@ static int ext4_map_create_blocks(handle_t *handle, struct inode *inode,
return err;
}
- /*
- * If the extent has been zeroed out, we don't need to update
- * extent status tree.
- */
- if (flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE &&
- ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, &map->m_seq)) {
- if (ext4_es_is_written(&es))
- return retval;
- }
-
status = map->m_flags & EXT4_MAP_UNWRITTEN ?
EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
ext4_es_insert_extent(inode, map->m_lblk, map->m_len, map->m_pblk,
--
2.52.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 1/7] ext4: use reserved metadata blocks when splitting extent on endio
2025-12-23 1:17 ` [PATCH -next v2 1/7] ext4: use reserved metadata blocks when splitting extent on endio Zhang Yi
@ 2025-12-31 7:34 ` Baokun Li
2026-01-03 13:42 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Baokun Li @ 2025-12-31 7:34 UTC (permalink / raw)
To: Zhang Yi, linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yizhang089, yangerkun, yukuai
On 2025-12-23 09:17, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> When performing buffered writes, we may need to split and convert an
> unwritten extent into a written one during the end I/O process. However,
> we do not reserve space specifically for these metadata changes, we only
> reserve 2% of space or 4096 blocks. To address this, we use
> EXT4_GET_BLOCKS_PRE_IO to potentially split extents in advance and
> EXT4_GET_BLOCKS_METADATA_NOFAIL to utilize reserved space if necessary.
>
> These two approaches can reduce the likelihood of running out of space
> and losing data. However, these methods are merely best efforts, we
> could still run out of space, and there is not much difference between
> converting an extent during the writeback process and the end I/O
> process, it won't increase the rick of losing data if we postpone the
> conversion.
>
> Therefore, also use EXT4_GET_BLOCKS_METADATA_NOFAIL in
> ext4_convert_unwritten_extents_endio() to prepare for the buffered I/O
> iomap conversion, which may perform extent conversion during the end I/O
> process.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Fair point, that is consistent with how
ext4_ext_handle_unwritten_extents() handles it.
Reviewed-by: Baokun Li <libaokun1@huawei.com>
> ---
> fs/ext4/extents.c | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
> index 27eb2c1df012..e53959120b04 100644
> --- a/fs/ext4/extents.c
> +++ b/fs/ext4/extents.c
> @@ -3794,6 +3794,8 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
> * illegal.
> */
> if (ee_block != map->m_lblk || ee_len > map->m_len) {
> + int flags = EXT4_GET_BLOCKS_CONVERT |
> + EXT4_GET_BLOCKS_METADATA_NOFAIL;
> #ifdef CONFIG_EXT4_DEBUG
> ext4_warning(inode->i_sb, "Inode (%ld) finished: extent logical block %llu,"
> " len %u; IO logical block %llu, len %u",
> @@ -3801,7 +3803,7 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
> (unsigned long long)map->m_lblk, map->m_len);
> #endif
> path = ext4_split_convert_extents(handle, inode, map, path,
> - EXT4_GET_BLOCKS_CONVERT, NULL);
> + flags, NULL);
> if (IS_ERR(path))
> return path;
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 2/7] ext4: don't split extent before submitting I/O
2025-12-23 1:17 ` [PATCH -next v2 2/7] ext4: don't split extent before submitting I/O Zhang Yi
@ 2025-12-31 7:35 ` Baokun Li
2026-01-03 13:47 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Baokun Li @ 2025-12-31 7:35 UTC (permalink / raw)
To: Zhang Yi, linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yizhang089, yangerkun, yukuai
On 2025-12-23 09:17, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> Currently, when writing back dirty pages to the filesystem with the
> dioread_nolock feature enabled and when doing DIO, if the area to be
> written back is part of an unwritten extent, the
> EXT4_GET_BLOCKS_IO_CREATE_EXT flag is set during block allocation before
> submitting I/O. The function ext4_split_convert_extents() then attempts
> to split this extent in advance. This approach is designed to prevents
> extent splitting and conversion to the written type from failing due to
> insufficient disk space at the time of I/O completion, which could
> otherwise result in data loss.
>
> However, we already have two mechanisms to ensure successful extent
> conversion. The first is the EXT4_GET_BLOCKS_METADATA_NOFAIL flag, which
> is a best effort, it permits the use of 2% of the reserved space or
> 4,096 blocks in the file system when splitting extents. This flag covers
> most scenarios where extent splitting might fail. The second is the
> EXT4_EXT_MAY_ZEROOUT flag, which is also set during extent splitting. If
> the reserved space is insufficient and splitting fails, it does not
> retry the allocation. Instead, it directly zeros out the extra part of
> the extent, thereby avoiding splitting and directly converting the
> entire extent to the written type.
>
> These two mechanisms also exist when I/Os are completed because there is
> a concurrency window between write-back and fallocate, which may still
> require us to split extents upon I/O completion. There is no much
> difference between splitting extents before submitting I/O. Therefore,
> It seems possible to defer the splitting until I/O completion, it won't
> increase the risk of I/O failure and data loss. On the contrary, if some
> I/Os can be merged when I/O completion, it can also reduce unnecessary
> splitting operations, thereby alleviating the pressure on reserved
> space.
>
> In addition, deferring extent splitting until I/O completion can
> also simplify the IO submission process and avoid initiating unnecessary
> journal handles when writing unwritten extents.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Looks good to me. Feel free to add:
Reviewed-by: Baokun Li <libaokun1@huawei.com>
> ---
> fs/ext4/extents.c | 13 +------------
> fs/ext4/inode.c | 4 ++--
> 2 files changed, 3 insertions(+), 14 deletions(-)
>
> diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
> index e53959120b04..c98f7c5482b4 100644
> --- a/fs/ext4/extents.c
> +++ b/fs/ext4/extents.c
> @@ -3787,21 +3787,10 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
> ext_debug(inode, "logical block %llu, max_blocks %u\n",
> (unsigned long long)ee_block, ee_len);
>
> - /* If extent is larger than requested it is a clear sign that we still
> - * have some extent state machine issues left. So extent_split is still
> - * required.
> - * TODO: Once all related issues will be fixed this situation should be
> - * illegal.
> - */
> if (ee_block != map->m_lblk || ee_len > map->m_len) {
> int flags = EXT4_GET_BLOCKS_CONVERT |
> EXT4_GET_BLOCKS_METADATA_NOFAIL;
> -#ifdef CONFIG_EXT4_DEBUG
> - ext4_warning(inode->i_sb, "Inode (%ld) finished: extent logical block %llu,"
> - " len %u; IO logical block %llu, len %u",
> - inode->i_ino, (unsigned long long)ee_block, ee_len,
> - (unsigned long long)map->m_lblk, map->m_len);
> -#endif
> +
> path = ext4_split_convert_extents(handle, inode, map, path,
> flags, NULL);
> if (IS_ERR(path))
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index bb8165582840..ffde24ff7347 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -2376,7 +2376,7 @@ static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)
>
> dioread_nolock = ext4_should_dioread_nolock(inode);
> if (dioread_nolock)
> - get_blocks_flags |= EXT4_GET_BLOCKS_IO_CREATE_EXT;
> + get_blocks_flags |= EXT4_GET_BLOCKS_UNWRIT_EXT;
>
> err = ext4_map_blocks(handle, inode, map, get_blocks_flags);
> if (err < 0)
> @@ -3744,7 +3744,7 @@ static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map,
> else if (EXT4_LBLK_TO_B(inode, map->m_lblk) >= i_size_read(inode))
> m_flags = EXT4_GET_BLOCKS_CREATE;
> else if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
> - m_flags = EXT4_GET_BLOCKS_IO_CREATE_EXT;
> + m_flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT;
>
> if (flags & IOMAP_ATOMIC)
> ret = ext4_map_blocks_atomic_write(handle, inode, map, m_flags,
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 3/7] ext4: avoid starting handle when dio writing an unwritten extent
2025-12-23 1:17 ` [PATCH -next v2 3/7] ext4: avoid starting handle when dio writing an unwritten extent Zhang Yi
@ 2025-12-31 7:36 ` Baokun Li
2026-01-03 14:06 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Baokun Li @ 2025-12-31 7:36 UTC (permalink / raw)
To: Zhang Yi, linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yizhang089, yangerkun, yukuai
On 2025-12-23 09:17, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> Since we have deferred the split of the unwritten extent until after I/O
> completion, it is not necessary to initiate the journal handle when
> submitting the I/O.
>
> This can improve the write performance of concurrent DIO for multiple
> files. The fio tests below show a ~25% performance improvement when
> wirting to unwritten files on my VM with a mem disk.
>
> [unwritten]
> direct=1
> ioengine=psync
> numjobs=16
> rw=write # write/randwrite
> bs=4K
> iodepth=1
> directory=/mnt
> size=5G
> runtime=30s
> overwrite=0
> norandommap=1
> fallocate=native
> ramp_time=5s
> group_reporting=1
>
> [w/o]
> w: IOPS=62.5k, BW=244MiB/s
> rw: IOPS=56.7k, BW=221MiB/s
>
> [w]
> w: IOPS=79.6k, BW=311MiB/s
> rw: IOPS=70.2k, BW=274MiB/s
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Looks good. Feel free to add:
Reviewed-by: Baokun Li <libaokun1@huawei.com>
> ---
> fs/ext4/file.c | 4 +---
> fs/ext4/inode.c | 9 +++++++--
> 2 files changed, 8 insertions(+), 5 deletions(-)
>
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index 7a8b30932189..9f571acc7782 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -418,9 +418,7 @@ static const struct iomap_dio_ops ext4_dio_write_ops = {
> * updating inode i_disksize and/or orphan handling with exclusive lock.
> *
> * - shared locking will only be true mostly with overwrites, including
> - * initialized blocks and unwritten blocks. For overwrite unwritten blocks
> - * we protect splitting extents by i_data_sem in ext4_inode_info, so we can
> - * also release exclusive i_rwsem lock.
> + * initialized blocks and unwritten blocks.
> *
> * - Otherwise we will switch to exclusive i_rwsem lock.
> */
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index ffde24ff7347..ff3ad1a2df45 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -3817,9 +3817,14 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> ret = ext4_map_blocks(NULL, inode, &map, 0);
> /*
> * For atomic writes the entire requested length should
> - * be mapped.
> + * be mapped. For DAX we convert extents to initialized
> + * ones before copying the data, otherwise we do it
> + * after I/O so there's no need to call into
> + * ext4_iomap_alloc().
> */
> - if (map.m_flags & EXT4_MAP_MAPPED) {
> + if ((map.m_flags & EXT4_MAP_MAPPED) ||
> + (!(flags & IOMAP_DAX) &&
> + (map.m_flags & EXT4_MAP_UNWRITTEN))) {
> if ((!(flags & IOMAP_ATOMIC) && ret > 0) ||
> (flags & IOMAP_ATOMIC && ret >= orig_mlen))
> goto out;
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 4/7] ext4: remove useless ext4_iomap_overwrite_ops
2025-12-23 1:17 ` [PATCH -next v2 4/7] ext4: remove useless ext4_iomap_overwrite_ops Zhang Yi
@ 2025-12-31 7:40 ` Baokun Li
2026-01-03 14:14 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Baokun Li @ 2025-12-31 7:40 UTC (permalink / raw)
To: Zhang Yi, linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yizhang089, yangerkun, yukuai
On 2025-12-23 09:17, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> ext4_iomap_overwrite_ops was introduced in commit 8cd115bdda17 ("ext4:
> Optimize ext4 DIO overwrites"), which can optimize pure overwrite
> performance by dropping the IOMAP_WRITE flag to only query the mapped
> mapping information. This avoids starting a new journal handle, thereby
> improving speed. Later, commit 9faac62d4013 ("ext4: optimize file
> overwrites") also optimized similar scenarios, but it performs the check
> later, examining the mappings status only when the actual block mapping
> is needed. Thus, it can handle the previous commit scenario. That means
> in the case of an overwrite scenario, the condition
> "offset + length <= i_size_read(inode)" in the write path must always be
> true.
>
> Therefore, it is acceptable to remove the ext4_iomap_overwrite_ops,
> which will also clarify the write and read paths of ext4_iomap_begin.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Nice cleanup! Feel free to add:
Reviewed-by: Baokun Li <libaokun1@huawei.com>
> ---
> fs/ext4/ext4.h | 1 -
> fs/ext4/file.c | 5 +----
> fs/ext4/inode.c | 24 ------------------------
> 3 files changed, 1 insertion(+), 29 deletions(-)
>
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index 56112f201cac..9a71357f192d 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -3909,7 +3909,6 @@ static inline void ext4_clear_io_unwritten_flag(ext4_io_end_t *io_end)
> }
>
> extern const struct iomap_ops ext4_iomap_ops;
> -extern const struct iomap_ops ext4_iomap_overwrite_ops;
> extern const struct iomap_ops ext4_iomap_report_ops;
>
> static inline int ext4_buffer_uptodate(struct buffer_head *bh)
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index 9f571acc7782..6b4b68f830d5 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -506,7 +506,6 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
> struct inode *inode = file_inode(iocb->ki_filp);
> loff_t offset = iocb->ki_pos;
> size_t count = iov_iter_count(from);
> - const struct iomap_ops *iomap_ops = &ext4_iomap_ops;
> bool extend = false, unwritten = false;
> bool ilock_shared = true;
> int dio_flags = 0;
> @@ -573,9 +572,7 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
> goto out;
> }
>
> - if (ilock_shared && !unwritten)
> - iomap_ops = &ext4_iomap_overwrite_ops;
> - ret = iomap_dio_rw(iocb, from, iomap_ops, &ext4_dio_write_ops,
> + ret = iomap_dio_rw(iocb, from, &ext4_iomap_ops, &ext4_dio_write_ops,
> dio_flags, NULL, 0);
> if (ret == -ENOTBLK)
> ret = 0;
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index ff3ad1a2df45..b84a2a10dfb8 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -3833,10 +3833,6 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> }
> ret = ext4_iomap_alloc(inode, &map, flags);
> } else {
> - /*
> - * This can be called for overwrites path from
> - * ext4_iomap_overwrite_begin().
> - */
> ret = ext4_map_blocks(NULL, inode, &map, 0);
> }
>
> @@ -3865,30 +3861,10 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> return 0;
> }
>
> -static int ext4_iomap_overwrite_begin(struct inode *inode, loff_t offset,
> - loff_t length, unsigned flags, struct iomap *iomap,
> - struct iomap *srcmap)
> -{
> - int ret;
> -
> - /*
> - * Even for writes we don't need to allocate blocks, so just pretend
> - * we are reading to save overhead of starting a transaction.
> - */
> - flags &= ~IOMAP_WRITE;
> - ret = ext4_iomap_begin(inode, offset, length, flags, iomap, srcmap);
> - WARN_ON_ONCE(!ret && iomap->type != IOMAP_MAPPED);
> - return ret;
> -}
> -
> const struct iomap_ops ext4_iomap_ops = {
> .iomap_begin = ext4_iomap_begin,
> };
>
> -const struct iomap_ops ext4_iomap_overwrite_ops = {
> - .iomap_begin = ext4_iomap_overwrite_begin,
> -};
> -
> static int ext4_iomap_begin_report(struct inode *inode, loff_t offset,
> loff_t length, unsigned int flags,
> struct iomap *iomap, struct iomap *srcmap)
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 5/7] ext4: remove unused unwritten parameter in ext4_dio_write_iter()
2025-12-23 1:18 ` [PATCH -next v2 5/7] ext4: remove unused unwritten parameter in ext4_dio_write_iter() Zhang Yi
@ 2025-12-31 7:42 ` Baokun Li
2026-01-03 14:16 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Baokun Li @ 2025-12-31 7:42 UTC (permalink / raw)
To: Zhang Yi, linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yizhang089, yangerkun, yukuai
On 2025-12-23 09:18, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> The parameter unwritten in ext4_dio_write_iter() is no longer needed,
> simply remove it.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Looks good. Feel free to add:
Reviewed-by: Baokun Li <libaokun1@huawei.com>
> ---
> fs/ext4/file.c | 14 +++++++-------
> 1 file changed, 7 insertions(+), 7 deletions(-)
>
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index 6b4b68f830d5..fa22fc0e45f3 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -424,14 +424,14 @@ static const struct iomap_dio_ops ext4_dio_write_ops = {
> */
> static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
> bool *ilock_shared, bool *extend,
> - bool *unwritten, int *dio_flags)
> + int *dio_flags)
> {
> struct file *file = iocb->ki_filp;
> struct inode *inode = file_inode(file);
> loff_t offset;
> size_t count;
> ssize_t ret;
> - bool overwrite, unaligned_io;
> + bool overwrite, unaligned_io, unwritten;
>
> restart:
> ret = ext4_generic_write_checks(iocb, from);
> @@ -443,7 +443,7 @@ static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
>
> unaligned_io = ext4_unaligned_io(inode, from, offset);
> *extend = ext4_extending_io(inode, offset, count);
> - overwrite = ext4_overwrite_io(inode, offset, count, unwritten);
> + overwrite = ext4_overwrite_io(inode, offset, count, &unwritten);
>
> /*
> * Determine whether we need to upgrade to an exclusive lock. This is
> @@ -458,7 +458,7 @@ static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
> */
> if (*ilock_shared &&
> ((!IS_NOSEC(inode) || *extend || !overwrite ||
> - (unaligned_io && *unwritten)))) {
> + (unaligned_io && unwritten)))) {
> if (iocb->ki_flags & IOCB_NOWAIT) {
> ret = -EAGAIN;
> goto out;
> @@ -481,7 +481,7 @@ static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
> ret = -EAGAIN;
> goto out;
> }
> - if (unaligned_io && (!overwrite || *unwritten))
> + if (unaligned_io && (!overwrite || unwritten))
> inode_dio_wait(inode);
> *dio_flags = IOMAP_DIO_FORCE_WAIT;
> }
> @@ -506,7 +506,7 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
> struct inode *inode = file_inode(iocb->ki_filp);
> loff_t offset = iocb->ki_pos;
> size_t count = iov_iter_count(from);
> - bool extend = false, unwritten = false;
> + bool extend = false;
> bool ilock_shared = true;
> int dio_flags = 0;
>
> @@ -552,7 +552,7 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
> ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
>
> ret = ext4_dio_write_checks(iocb, from, &ilock_shared, &extend,
> - &unwritten, &dio_flags);
> + &dio_flags);
> if (ret <= 0)
> return ret;
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin()
2025-12-23 1:18 ` [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin() Zhang Yi
@ 2025-12-31 7:46 ` Baokun Li
2026-01-03 14:17 ` Ojaswin Mujoo
2026-01-04 13:00 ` Markus Elfring
2 siblings, 0 replies; 25+ messages in thread
From: Baokun Li @ 2025-12-31 7:46 UTC (permalink / raw)
To: Zhang Yi, linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yizhang089, yangerkun, yukuai
On 2025-12-23 09:18, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> In the write path mapping check of ext4_iomap_begin(), the return value
> 'ret' should never greater than orig_mlen. If 'ret' equals 'orig_mlen',
> it can be returned directly without checking IOMAP_ATOMIC.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Looks good. Feel free to add:
Reviewed-by: Baokun Li <libaokun1@huawei.com>
> ---
> fs/ext4/inode.c | 16 +++++++++-------
> 1 file changed, 9 insertions(+), 7 deletions(-)
>
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index b84a2a10dfb8..67fe7d0f47e3 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -3816,17 +3816,19 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> if (offset + length <= i_size_read(inode)) {
> ret = ext4_map_blocks(NULL, inode, &map, 0);
> /*
> - * For atomic writes the entire requested length should
> - * be mapped. For DAX we convert extents to initialized
> - * ones before copying the data, otherwise we do it
> - * after I/O so there's no need to call into
> - * ext4_iomap_alloc().
> + * For DAX we convert extents to initialized ones before
> + * copying the data, otherwise we do it after I/O so
> + * there's no need to call into ext4_iomap_alloc().
> */
> if ((map.m_flags & EXT4_MAP_MAPPED) ||
> (!(flags & IOMAP_DAX) &&
> (map.m_flags & EXT4_MAP_UNWRITTEN))) {
> - if ((!(flags & IOMAP_ATOMIC) && ret > 0) ||
> - (flags & IOMAP_ATOMIC && ret >= orig_mlen))
> + /*
> + * For atomic writes the entire requested
> + * length should be mapped.
> + */
> + if (ret == orig_mlen ||
> + (!(flags & IOMAP_ATOMIC) && ret > 0))
> goto out;
> }
> map.m_len = orig_mlen;
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 7/7] ext4: remove EXT4_GET_BLOCKS_IO_CREATE_EXT
2025-12-23 1:18 ` [PATCH -next v2 7/7] ext4: remove EXT4_GET_BLOCKS_IO_CREATE_EXT Zhang Yi
@ 2025-12-31 7:55 ` Baokun Li
2026-01-03 14:20 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Baokun Li @ 2025-12-31 7:55 UTC (permalink / raw)
To: Zhang Yi, linux-ext4
Cc: linux-fsdevel, linux-kernel, tytso, adilger.kernel, jack,
ojaswin, ritesh.list, yi.zhang, yizhang089, yangerkun, yukuai
On 2025-12-23 09:18, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> We do not use EXT4_GET_BLOCKS_IO_CREATE_EXT or split extents before
> submitting I/O; therefore, remove the related code.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Looks good. Feel free to add:
Reviewed-by: Baokun Li <libaokun1@huawei.com>
> ---
> fs/ext4/ext4.h | 9 ---------
> fs/ext4/extents.c | 29 -----------------------------
> fs/ext4/inode.c | 11 -----------
> 3 files changed, 49 deletions(-)
>
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index 9a71357f192d..174c51402864 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -707,15 +707,6 @@ enum {
> * found an unwritten extent, we need to split it.
> */
> #define EXT4_GET_BLOCKS_SPLIT_NOMERGE 0x0008
> - /*
> - * Caller is from the dio or dioread_nolock buffered IO, reqest to
> - * create an unwritten extent if it does not exist or split the
> - * found unwritten extent. Also do not merge the newly created
> - * unwritten extent, io end will convert unwritten to written,
> - * and try to merge the written extent.
> - */
> -#define EXT4_GET_BLOCKS_IO_CREATE_EXT (EXT4_GET_BLOCKS_SPLIT_NOMERGE|\
> - EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT)
> /* Convert unwritten extent to initialized. */
> #define EXT4_GET_BLOCKS_CONVERT 0x0010
> /* Eventual metadata allocation (due to growing extent tree)
> diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
> index c98f7c5482b4..c7c66ab825e7 100644
> --- a/fs/ext4/extents.c
> +++ b/fs/ext4/extents.c
> @@ -3925,34 +3925,6 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
> trace_ext4_ext_handle_unwritten_extents(inode, map, flags,
> *allocated, newblock);
>
> - /* get_block() before submitting IO, split the extent */
> - if (flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE) {
> - int depth;
> -
> - path = ext4_split_convert_extents(handle, inode, map, path,
> - flags, allocated);
> - if (IS_ERR(path))
> - return path;
> - /*
> - * shouldn't get a 0 allocated when splitting an extent unless
> - * m_len is 0 (bug) or extent has been corrupted
> - */
> - if (unlikely(*allocated == 0)) {
> - EXT4_ERROR_INODE(inode,
> - "unexpected allocated == 0, m_len = %u",
> - map->m_len);
> - err = -EFSCORRUPTED;
> - goto errout;
> - }
> - /* Don't mark unwritten if the extent has been zeroed out. */
> - path = ext4_find_extent(inode, map->m_lblk, path, flags);
> - if (IS_ERR(path))
> - return path;
> - depth = ext_depth(inode);
> - if (ext4_ext_is_unwritten(path[depth].p_ext))
> - map->m_flags |= EXT4_MAP_UNWRITTEN;
> - goto out;
> - }
> /* IO end_io complete, convert the filled extent to written */
> if (flags & EXT4_GET_BLOCKS_CONVERT) {
> path = ext4_convert_unwritten_extents_endio(handle, inode,
> @@ -4006,7 +3978,6 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
> goto errout;
> }
>
> -out:
> map->m_flags |= EXT4_MAP_NEW;
> map_out:
> map->m_flags |= EXT4_MAP_MAPPED;
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index 67fe7d0f47e3..2e79b09fe2f0 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -588,7 +588,6 @@ static int ext4_map_query_blocks(handle_t *handle, struct inode *inode,
> static int ext4_map_create_blocks(handle_t *handle, struct inode *inode,
> struct ext4_map_blocks *map, int flags)
> {
> - struct extent_status es;
> unsigned int status;
> int err, retval = 0;
>
> @@ -649,16 +648,6 @@ static int ext4_map_create_blocks(handle_t *handle, struct inode *inode,
> return err;
> }
>
> - /*
> - * If the extent has been zeroed out, we don't need to update
> - * extent status tree.
> - */
> - if (flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE &&
> - ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, &map->m_seq)) {
> - if (ext4_es_is_written(&es))
> - return retval;
> - }
> -
> status = map->m_flags & EXT4_MAP_UNWRITTEN ?
> EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
> ext4_es_insert_extent(inode, map->m_lblk, map->m_len, map->m_pblk,
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 1/7] ext4: use reserved metadata blocks when splitting extent on endio
2025-12-23 1:17 ` [PATCH -next v2 1/7] ext4: use reserved metadata blocks when splitting extent on endio Zhang Yi
2025-12-31 7:34 ` Baokun Li
@ 2026-01-03 13:42 ` Ojaswin Mujoo
2026-01-05 1:17 ` Zhang Yi
1 sibling, 1 reply; 25+ messages in thread
From: Ojaswin Mujoo @ 2026-01-03 13:42 UTC (permalink / raw)
To: Zhang Yi
Cc: linux-ext4, linux-fsdevel, linux-kernel, tytso, adilger.kernel,
jack, ritesh.list, yi.zhang, yizhang089, libaokun1, yangerkun,
yukuai
On Tue, Dec 23, 2025 at 09:17:56AM +0800, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> When performing buffered writes, we may need to split and convert an
> unwritten extent into a written one during the end I/O process. However,
> we do not reserve space specifically for these metadata changes, we only
> reserve 2% of space or 4096 blocks. To address this, we use
> EXT4_GET_BLOCKS_PRE_IO to potentially split extents in advance and
> EXT4_GET_BLOCKS_METADATA_NOFAIL to utilize reserved space if necessary.
>
> These two approaches can reduce the likelihood of running out of space
> and losing data. However, these methods are merely best efforts, we
> could still run out of space, and there is not much difference between
> converting an extent during the writeback process and the end I/O
> process, it won't increase the rick of losing data if we postpone the
^^^^ risk
Other than the minor typo above, feel free to add:
Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
> conversion.
>
> Therefore, also use EXT4_GET_BLOCKS_METADATA_NOFAIL in
> ext4_convert_unwritten_extents_endio() to prepare for the buffered I/O
> iomap conversion, which may perform extent conversion during the end I/O
> process.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
> ---
> fs/ext4/extents.c | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
> index 27eb2c1df012..e53959120b04 100644
> --- a/fs/ext4/extents.c
> +++ b/fs/ext4/extents.c
> @@ -3794,6 +3794,8 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
> * illegal.
> */
> if (ee_block != map->m_lblk || ee_len > map->m_len) {
> + int flags = EXT4_GET_BLOCKS_CONVERT |
> + EXT4_GET_BLOCKS_METADATA_NOFAIL;
> #ifdef CONFIG_EXT4_DEBUG
> ext4_warning(inode->i_sb, "Inode (%ld) finished: extent logical block %llu,"
> " len %u; IO logical block %llu, len %u",
> @@ -3801,7 +3803,7 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
> (unsigned long long)map->m_lblk, map->m_len);
> #endif
> path = ext4_split_convert_extents(handle, inode, map, path,
> - EXT4_GET_BLOCKS_CONVERT, NULL);
> + flags, NULL);
> if (IS_ERR(path))
> return path;
>
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 2/7] ext4: don't split extent before submitting I/O
2025-12-23 1:17 ` [PATCH -next v2 2/7] ext4: don't split extent before submitting I/O Zhang Yi
2025-12-31 7:35 ` Baokun Li
@ 2026-01-03 13:47 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Ojaswin Mujoo @ 2026-01-03 13:47 UTC (permalink / raw)
To: Zhang Yi
Cc: linux-ext4, linux-fsdevel, linux-kernel, tytso, adilger.kernel,
jack, ritesh.list, yi.zhang, yizhang089, libaokun1, yangerkun,
yukuai
On Tue, Dec 23, 2025 at 09:17:57AM +0800, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> Currently, when writing back dirty pages to the filesystem with the
> dioread_nolock feature enabled and when doing DIO, if the area to be
> written back is part of an unwritten extent, the
> EXT4_GET_BLOCKS_IO_CREATE_EXT flag is set during block allocation before
> submitting I/O. The function ext4_split_convert_extents() then attempts
> to split this extent in advance. This approach is designed to prevents
> extent splitting and conversion to the written type from failing due to
> insufficient disk space at the time of I/O completion, which could
> otherwise result in data loss.
>
> However, we already have two mechanisms to ensure successful extent
> conversion. The first is the EXT4_GET_BLOCKS_METADATA_NOFAIL flag, which
> is a best effort, it permits the use of 2% of the reserved space or
> 4,096 blocks in the file system when splitting extents. This flag covers
> most scenarios where extent splitting might fail. The second is the
> EXT4_EXT_MAY_ZEROOUT flag, which is also set during extent splitting. If
> the reserved space is insufficient and splitting fails, it does not
> retry the allocation. Instead, it directly zeros out the extra part of
> the extent, thereby avoiding splitting and directly converting the
> entire extent to the written type.
>
> These two mechanisms also exist when I/Os are completed because there is
> a concurrency window between write-back and fallocate, which may still
> require us to split extents upon I/O completion. There is no much
> difference between splitting extents before submitting I/O. Therefore,
> It seems possible to defer the splitting until I/O completion, it won't
> increase the risk of I/O failure and data loss. On the contrary, if some
> I/Os can be merged when I/O completion, it can also reduce unnecessary
> splitting operations, thereby alleviating the pressure on reserved
> space.
>
> In addition, deferring extent splitting until I/O completion can
> also simplify the IO submission process and avoid initiating unnecessary
> journal handles when writing unwritten extents.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Looks good, feel free to add:
Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Regards,
ojaswin
> ---
> fs/ext4/extents.c | 13 +------------
> fs/ext4/inode.c | 4 ++--
> 2 files changed, 3 insertions(+), 14 deletions(-)
>
> diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
> index e53959120b04..c98f7c5482b4 100644
> --- a/fs/ext4/extents.c
> +++ b/fs/ext4/extents.c
> @@ -3787,21 +3787,10 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
> ext_debug(inode, "logical block %llu, max_blocks %u\n",
> (unsigned long long)ee_block, ee_len);
>
> - /* If extent is larger than requested it is a clear sign that we still
> - * have some extent state machine issues left. So extent_split is still
> - * required.
> - * TODO: Once all related issues will be fixed this situation should be
> - * illegal.
> - */
> if (ee_block != map->m_lblk || ee_len > map->m_len) {
> int flags = EXT4_GET_BLOCKS_CONVERT |
> EXT4_GET_BLOCKS_METADATA_NOFAIL;
> -#ifdef CONFIG_EXT4_DEBUG
> - ext4_warning(inode->i_sb, "Inode (%ld) finished: extent logical block %llu,"
> - " len %u; IO logical block %llu, len %u",
> - inode->i_ino, (unsigned long long)ee_block, ee_len,
> - (unsigned long long)map->m_lblk, map->m_len);
> -#endif
> +
> path = ext4_split_convert_extents(handle, inode, map, path,
> flags, NULL);
> if (IS_ERR(path))
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index bb8165582840..ffde24ff7347 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -2376,7 +2376,7 @@ static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)
>
> dioread_nolock = ext4_should_dioread_nolock(inode);
> if (dioread_nolock)
> - get_blocks_flags |= EXT4_GET_BLOCKS_IO_CREATE_EXT;
> + get_blocks_flags |= EXT4_GET_BLOCKS_UNWRIT_EXT;
>
> err = ext4_map_blocks(handle, inode, map, get_blocks_flags);
> if (err < 0)
> @@ -3744,7 +3744,7 @@ static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map,
> else if (EXT4_LBLK_TO_B(inode, map->m_lblk) >= i_size_read(inode))
> m_flags = EXT4_GET_BLOCKS_CREATE;
> else if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
> - m_flags = EXT4_GET_BLOCKS_IO_CREATE_EXT;
> + m_flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT;
>
> if (flags & IOMAP_ATOMIC)
> ret = ext4_map_blocks_atomic_write(handle, inode, map, m_flags,
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 3/7] ext4: avoid starting handle when dio writing an unwritten extent
2025-12-23 1:17 ` [PATCH -next v2 3/7] ext4: avoid starting handle when dio writing an unwritten extent Zhang Yi
2025-12-31 7:36 ` Baokun Li
@ 2026-01-03 14:06 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Ojaswin Mujoo @ 2026-01-03 14:06 UTC (permalink / raw)
To: Zhang Yi
Cc: linux-ext4, linux-fsdevel, linux-kernel, tytso, adilger.kernel,
jack, ritesh.list, yi.zhang, yizhang089, libaokun1, yangerkun,
yukuai
On Tue, Dec 23, 2025 at 09:17:58AM +0800, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> Since we have deferred the split of the unwritten extent until after I/O
> completion, it is not necessary to initiate the journal handle when
> submitting the I/O.
>
> This can improve the write performance of concurrent DIO for multiple
> files. The fio tests below show a ~25% performance improvement when
> wirting to unwritten files on my VM with a mem disk.
>
> [unwritten]
> direct=1
> ioengine=psync
> numjobs=16
> rw=write # write/randwrite
> bs=4K
> iodepth=1
> directory=/mnt
> size=5G
> runtime=30s
> overwrite=0
> norandommap=1
> fallocate=native
> ramp_time=5s
> group_reporting=1
>
> [w/o]
> w: IOPS=62.5k, BW=244MiB/s
> rw: IOPS=56.7k, BW=221MiB/s
>
> [w]
> w: IOPS=79.6k, BW=311MiB/s
> rw: IOPS=70.2k, BW=274MiB/s
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Looks good, feel free to add:
Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Regards,
ojaswin
> ---
> fs/ext4/file.c | 4 +---
> fs/ext4/inode.c | 9 +++++++--
> 2 files changed, 8 insertions(+), 5 deletions(-)
>
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index 7a8b30932189..9f571acc7782 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -418,9 +418,7 @@ static const struct iomap_dio_ops ext4_dio_write_ops = {
> * updating inode i_disksize and/or orphan handling with exclusive lock.
> *
> * - shared locking will only be true mostly with overwrites, including
> - * initialized blocks and unwritten blocks. For overwrite unwritten blocks
> - * we protect splitting extents by i_data_sem in ext4_inode_info, so we can
> - * also release exclusive i_rwsem lock.
> + * initialized blocks and unwritten blocks.
> *
> * - Otherwise we will switch to exclusive i_rwsem lock.
> */
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index ffde24ff7347..ff3ad1a2df45 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -3817,9 +3817,14 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> ret = ext4_map_blocks(NULL, inode, &map, 0);
> /*
> * For atomic writes the entire requested length should
> - * be mapped.
> + * be mapped. For DAX we convert extents to initialized
> + * ones before copying the data, otherwise we do it
> + * after I/O so there's no need to call into
> + * ext4_iomap_alloc().
> */
> - if (map.m_flags & EXT4_MAP_MAPPED) {
> + if ((map.m_flags & EXT4_MAP_MAPPED) ||
> + (!(flags & IOMAP_DAX) &&
> + (map.m_flags & EXT4_MAP_UNWRITTEN))) {
> if ((!(flags & IOMAP_ATOMIC) && ret > 0) ||
> (flags & IOMAP_ATOMIC && ret >= orig_mlen))
> goto out;
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 4/7] ext4: remove useless ext4_iomap_overwrite_ops
2025-12-23 1:17 ` [PATCH -next v2 4/7] ext4: remove useless ext4_iomap_overwrite_ops Zhang Yi
2025-12-31 7:40 ` Baokun Li
@ 2026-01-03 14:14 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Ojaswin Mujoo @ 2026-01-03 14:14 UTC (permalink / raw)
To: Zhang Yi
Cc: linux-ext4, linux-fsdevel, linux-kernel, tytso, adilger.kernel,
jack, ritesh.list, yi.zhang, yizhang089, libaokun1, yangerkun,
yukuai
On Tue, Dec 23, 2025 at 09:17:59AM +0800, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> ext4_iomap_overwrite_ops was introduced in commit 8cd115bdda17 ("ext4:
> Optimize ext4 DIO overwrites"), which can optimize pure overwrite
> performance by dropping the IOMAP_WRITE flag to only query the mapped
> mapping information. This avoids starting a new journal handle, thereby
> improving speed. Later, commit 9faac62d4013 ("ext4: optimize file
> overwrites") also optimized similar scenarios, but it performs the check
> later, examining the mappings status only when the actual block mapping
> is needed. Thus, it can handle the previous commit scenario. That means
> in the case of an overwrite scenario, the condition
> "offset + length <= i_size_read(inode)" in the write path must always be
> true.
>
> Therefore, it is acceptable to remove the ext4_iomap_overwrite_ops,
> which will also clarify the write and read paths of ext4_iomap_begin.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Looks good, feel free to add:
Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Regards,
ojaswin
> ---
> fs/ext4/ext4.h | 1 -
> fs/ext4/file.c | 5 +----
> fs/ext4/inode.c | 24 ------------------------
> 3 files changed, 1 insertion(+), 29 deletions(-)
>
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index 56112f201cac..9a71357f192d 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -3909,7 +3909,6 @@ static inline void ext4_clear_io_unwritten_flag(ext4_io_end_t *io_end)
> }
>
> extern const struct iomap_ops ext4_iomap_ops;
> -extern const struct iomap_ops ext4_iomap_overwrite_ops;
> extern const struct iomap_ops ext4_iomap_report_ops;
>
> static inline int ext4_buffer_uptodate(struct buffer_head *bh)
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index 9f571acc7782..6b4b68f830d5 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -506,7 +506,6 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
> struct inode *inode = file_inode(iocb->ki_filp);
> loff_t offset = iocb->ki_pos;
> size_t count = iov_iter_count(from);
> - const struct iomap_ops *iomap_ops = &ext4_iomap_ops;
> bool extend = false, unwritten = false;
> bool ilock_shared = true;
> int dio_flags = 0;
> @@ -573,9 +572,7 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
> goto out;
> }
>
> - if (ilock_shared && !unwritten)
> - iomap_ops = &ext4_iomap_overwrite_ops;
> - ret = iomap_dio_rw(iocb, from, iomap_ops, &ext4_dio_write_ops,
> + ret = iomap_dio_rw(iocb, from, &ext4_iomap_ops, &ext4_dio_write_ops,
> dio_flags, NULL, 0);
> if (ret == -ENOTBLK)
> ret = 0;
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index ff3ad1a2df45..b84a2a10dfb8 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -3833,10 +3833,6 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> }
> ret = ext4_iomap_alloc(inode, &map, flags);
> } else {
> - /*
> - * This can be called for overwrites path from
> - * ext4_iomap_overwrite_begin().
> - */
> ret = ext4_map_blocks(NULL, inode, &map, 0);
> }
>
> @@ -3865,30 +3861,10 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> return 0;
> }
>
> -static int ext4_iomap_overwrite_begin(struct inode *inode, loff_t offset,
> - loff_t length, unsigned flags, struct iomap *iomap,
> - struct iomap *srcmap)
> -{
> - int ret;
> -
> - /*
> - * Even for writes we don't need to allocate blocks, so just pretend
> - * we are reading to save overhead of starting a transaction.
> - */
> - flags &= ~IOMAP_WRITE;
> - ret = ext4_iomap_begin(inode, offset, length, flags, iomap, srcmap);
> - WARN_ON_ONCE(!ret && iomap->type != IOMAP_MAPPED);
> - return ret;
> -}
> -
> const struct iomap_ops ext4_iomap_ops = {
> .iomap_begin = ext4_iomap_begin,
> };
>
> -const struct iomap_ops ext4_iomap_overwrite_ops = {
> - .iomap_begin = ext4_iomap_overwrite_begin,
> -};
> -
> static int ext4_iomap_begin_report(struct inode *inode, loff_t offset,
> loff_t length, unsigned int flags,
> struct iomap *iomap, struct iomap *srcmap)
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 5/7] ext4: remove unused unwritten parameter in ext4_dio_write_iter()
2025-12-23 1:18 ` [PATCH -next v2 5/7] ext4: remove unused unwritten parameter in ext4_dio_write_iter() Zhang Yi
2025-12-31 7:42 ` Baokun Li
@ 2026-01-03 14:16 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Ojaswin Mujoo @ 2026-01-03 14:16 UTC (permalink / raw)
To: Zhang Yi
Cc: linux-ext4, linux-fsdevel, linux-kernel, tytso, adilger.kernel,
jack, ritesh.list, yi.zhang, yizhang089, libaokun1, yangerkun,
yukuai
On Tue, Dec 23, 2025 at 09:18:00AM +0800, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> The parameter unwritten in ext4_dio_write_iter() is no longer needed,
> simply remove it.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Looks good, feel free to add:
Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Regards,
ojaswin
> ---
> fs/ext4/file.c | 14 +++++++-------
> 1 file changed, 7 insertions(+), 7 deletions(-)
>
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index 6b4b68f830d5..fa22fc0e45f3 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -424,14 +424,14 @@ static const struct iomap_dio_ops ext4_dio_write_ops = {
> */
> static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
> bool *ilock_shared, bool *extend,
> - bool *unwritten, int *dio_flags)
> + int *dio_flags)
> {
> struct file *file = iocb->ki_filp;
> struct inode *inode = file_inode(file);
> loff_t offset;
> size_t count;
> ssize_t ret;
> - bool overwrite, unaligned_io;
> + bool overwrite, unaligned_io, unwritten;
>
> restart:
> ret = ext4_generic_write_checks(iocb, from);
> @@ -443,7 +443,7 @@ static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
>
> unaligned_io = ext4_unaligned_io(inode, from, offset);
> *extend = ext4_extending_io(inode, offset, count);
> - overwrite = ext4_overwrite_io(inode, offset, count, unwritten);
> + overwrite = ext4_overwrite_io(inode, offset, count, &unwritten);
>
> /*
> * Determine whether we need to upgrade to an exclusive lock. This is
> @@ -458,7 +458,7 @@ static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
> */
> if (*ilock_shared &&
> ((!IS_NOSEC(inode) || *extend || !overwrite ||
> - (unaligned_io && *unwritten)))) {
> + (unaligned_io && unwritten)))) {
> if (iocb->ki_flags & IOCB_NOWAIT) {
> ret = -EAGAIN;
> goto out;
> @@ -481,7 +481,7 @@ static ssize_t ext4_dio_write_checks(struct kiocb *iocb, struct iov_iter *from,
> ret = -EAGAIN;
> goto out;
> }
> - if (unaligned_io && (!overwrite || *unwritten))
> + if (unaligned_io && (!overwrite || unwritten))
> inode_dio_wait(inode);
> *dio_flags = IOMAP_DIO_FORCE_WAIT;
> }
> @@ -506,7 +506,7 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
> struct inode *inode = file_inode(iocb->ki_filp);
> loff_t offset = iocb->ki_pos;
> size_t count = iov_iter_count(from);
> - bool extend = false, unwritten = false;
> + bool extend = false;
> bool ilock_shared = true;
> int dio_flags = 0;
>
> @@ -552,7 +552,7 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
> ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
>
> ret = ext4_dio_write_checks(iocb, from, &ilock_shared, &extend,
> - &unwritten, &dio_flags);
> + &dio_flags);
> if (ret <= 0)
> return ret;
>
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin()
2025-12-23 1:18 ` [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin() Zhang Yi
2025-12-31 7:46 ` Baokun Li
@ 2026-01-03 14:17 ` Ojaswin Mujoo
2026-01-04 13:00 ` Markus Elfring
2 siblings, 0 replies; 25+ messages in thread
From: Ojaswin Mujoo @ 2026-01-03 14:17 UTC (permalink / raw)
To: Zhang Yi
Cc: linux-ext4, linux-fsdevel, linux-kernel, tytso, adilger.kernel,
jack, ritesh.list, yi.zhang, yizhang089, libaokun1, yangerkun,
yukuai
On Tue, Dec 23, 2025 at 09:18:01AM +0800, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> In the write path mapping check of ext4_iomap_begin(), the return value
> 'ret' should never greater than orig_mlen. If 'ret' equals 'orig_mlen',
> it can be returned directly without checking IOMAP_ATOMIC.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Looks good, feel free to add:
Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Regards,
ojaswin
> ---
> fs/ext4/inode.c | 16 +++++++++-------
> 1 file changed, 9 insertions(+), 7 deletions(-)
>
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index b84a2a10dfb8..67fe7d0f47e3 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -3816,17 +3816,19 @@ static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> if (offset + length <= i_size_read(inode)) {
> ret = ext4_map_blocks(NULL, inode, &map, 0);
> /*
> - * For atomic writes the entire requested length should
> - * be mapped. For DAX we convert extents to initialized
> - * ones before copying the data, otherwise we do it
> - * after I/O so there's no need to call into
> - * ext4_iomap_alloc().
> + * For DAX we convert extents to initialized ones before
> + * copying the data, otherwise we do it after I/O so
> + * there's no need to call into ext4_iomap_alloc().
> */
> if ((map.m_flags & EXT4_MAP_MAPPED) ||
> (!(flags & IOMAP_DAX) &&
> (map.m_flags & EXT4_MAP_UNWRITTEN))) {
> - if ((!(flags & IOMAP_ATOMIC) && ret > 0) ||
> - (flags & IOMAP_ATOMIC && ret >= orig_mlen))
> + /*
> + * For atomic writes the entire requested
> + * length should be mapped.
> + */
> + if (ret == orig_mlen ||
> + (!(flags & IOMAP_ATOMIC) && ret > 0))
> goto out;
> }
> map.m_len = orig_mlen;
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 7/7] ext4: remove EXT4_GET_BLOCKS_IO_CREATE_EXT
2025-12-23 1:18 ` [PATCH -next v2 7/7] ext4: remove EXT4_GET_BLOCKS_IO_CREATE_EXT Zhang Yi
2025-12-31 7:55 ` Baokun Li
@ 2026-01-03 14:20 ` Ojaswin Mujoo
1 sibling, 0 replies; 25+ messages in thread
From: Ojaswin Mujoo @ 2026-01-03 14:20 UTC (permalink / raw)
To: Zhang Yi
Cc: linux-ext4, linux-fsdevel, linux-kernel, tytso, adilger.kernel,
jack, ritesh.list, yi.zhang, yizhang089, libaokun1, yangerkun,
yukuai
On Tue, Dec 23, 2025 at 09:18:02AM +0800, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> We do not use EXT4_GET_BLOCKS_IO_CREATE_EXT or split extents before
> submitting I/O; therefore, remove the related code.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> Reviewed-by: Jan Kara <jack@suse.cz>
Nice, feel free to add:
Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Regards,
ojaswin
> ---
> fs/ext4/ext4.h | 9 ---------
> fs/ext4/extents.c | 29 -----------------------------
> fs/ext4/inode.c | 11 -----------
> 3 files changed, 49 deletions(-)
>
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index 9a71357f192d..174c51402864 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -707,15 +707,6 @@ enum {
> * found an unwritten extent, we need to split it.
> */
> #define EXT4_GET_BLOCKS_SPLIT_NOMERGE 0x0008
> - /*
> - * Caller is from the dio or dioread_nolock buffered IO, reqest to
> - * create an unwritten extent if it does not exist or split the
> - * found unwritten extent. Also do not merge the newly created
> - * unwritten extent, io end will convert unwritten to written,
> - * and try to merge the written extent.
> - */
> -#define EXT4_GET_BLOCKS_IO_CREATE_EXT (EXT4_GET_BLOCKS_SPLIT_NOMERGE|\
> - EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT)
> /* Convert unwritten extent to initialized. */
> #define EXT4_GET_BLOCKS_CONVERT 0x0010
> /* Eventual metadata allocation (due to growing extent tree)
> diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
> index c98f7c5482b4..c7c66ab825e7 100644
> --- a/fs/ext4/extents.c
> +++ b/fs/ext4/extents.c
> @@ -3925,34 +3925,6 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
> trace_ext4_ext_handle_unwritten_extents(inode, map, flags,
> *allocated, newblock);
>
> - /* get_block() before submitting IO, split the extent */
> - if (flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE) {
> - int depth;
> -
> - path = ext4_split_convert_extents(handle, inode, map, path,
> - flags, allocated);
> - if (IS_ERR(path))
> - return path;
> - /*
> - * shouldn't get a 0 allocated when splitting an extent unless
> - * m_len is 0 (bug) or extent has been corrupted
> - */
> - if (unlikely(*allocated == 0)) {
> - EXT4_ERROR_INODE(inode,
> - "unexpected allocated == 0, m_len = %u",
> - map->m_len);
> - err = -EFSCORRUPTED;
> - goto errout;
> - }
> - /* Don't mark unwritten if the extent has been zeroed out. */
> - path = ext4_find_extent(inode, map->m_lblk, path, flags);
> - if (IS_ERR(path))
> - return path;
> - depth = ext_depth(inode);
> - if (ext4_ext_is_unwritten(path[depth].p_ext))
> - map->m_flags |= EXT4_MAP_UNWRITTEN;
> - goto out;
> - }
> /* IO end_io complete, convert the filled extent to written */
> if (flags & EXT4_GET_BLOCKS_CONVERT) {
> path = ext4_convert_unwritten_extents_endio(handle, inode,
> @@ -4006,7 +3978,6 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
> goto errout;
> }
>
> -out:
> map->m_flags |= EXT4_MAP_NEW;
> map_out:
> map->m_flags |= EXT4_MAP_MAPPED;
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index 67fe7d0f47e3..2e79b09fe2f0 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -588,7 +588,6 @@ static int ext4_map_query_blocks(handle_t *handle, struct inode *inode,
> static int ext4_map_create_blocks(handle_t *handle, struct inode *inode,
> struct ext4_map_blocks *map, int flags)
> {
> - struct extent_status es;
> unsigned int status;
> int err, retval = 0;
>
> @@ -649,16 +648,6 @@ static int ext4_map_create_blocks(handle_t *handle, struct inode *inode,
> return err;
> }
>
> - /*
> - * If the extent has been zeroed out, we don't need to update
> - * extent status tree.
> - */
> - if (flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE &&
> - ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, &map->m_seq)) {
> - if (ext4_es_is_written(&es))
> - return retval;
> - }
> -
> status = map->m_flags & EXT4_MAP_UNWRITTEN ?
> EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
> ext4_es_insert_extent(inode, map->m_lblk, map->m_len, map->m_pblk,
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin()
2025-12-23 1:18 ` [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin() Zhang Yi
2025-12-31 7:46 ` Baokun Li
2026-01-03 14:17 ` Ojaswin Mujoo
@ 2026-01-04 13:00 ` Markus Elfring
2026-01-05 1:18 ` Zhang Yi
2 siblings, 1 reply; 25+ messages in thread
From: Markus Elfring @ 2026-01-04 13:00 UTC (permalink / raw)
To: Zhang Yi, linux-ext4, linux-fsdevel, Baokun Li, Jan Kara,
Ojaswin Mujoo, Theodore Ts'o
Cc: LKML, Andreas Dilger, Ritesh Harjani, Yang Erkun, Yu Kuai, zhangyi
> In the write path mapping check of ext4_iomap_begin(), the return value
> 'ret' should never greater than orig_mlen. If 'ret' equals 'orig_mlen',
> it can be returned directly without checking IOMAP_ATOMIC.
See also once more:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/submitting-patches.rst?h=v6.19-rc3#n94
How do you think about to use the word “simplify” in the summary phrase?
Regards,
Markus
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 1/7] ext4: use reserved metadata blocks when splitting extent on endio
2026-01-03 13:42 ` Ojaswin Mujoo
@ 2026-01-05 1:17 ` Zhang Yi
0 siblings, 0 replies; 25+ messages in thread
From: Zhang Yi @ 2026-01-05 1:17 UTC (permalink / raw)
To: Ojaswin Mujoo
Cc: linux-ext4, linux-fsdevel, linux-kernel, tytso, adilger.kernel,
jack, ritesh.list, yi.zhang, yizhang089, libaokun1, yangerkun,
yukuai
On 1/3/2026 9:42 PM, Ojaswin Mujoo wrote:
> On Tue, Dec 23, 2025 at 09:17:56AM +0800, Zhang Yi wrote:
>> From: Zhang Yi <yi.zhang@huawei.com>
>>
>> When performing buffered writes, we may need to split and convert an
>> unwritten extent into a written one during the end I/O process. However,
>> we do not reserve space specifically for these metadata changes, we only
>> reserve 2% of space or 4096 blocks. To address this, we use
>> EXT4_GET_BLOCKS_PRE_IO to potentially split extents in advance and
>> EXT4_GET_BLOCKS_METADATA_NOFAIL to utilize reserved space if necessary.
>>
>> These two approaches can reduce the likelihood of running out of space
>> and losing data. However, these methods are merely best efforts, we
>> could still run out of space, and there is not much difference between
>> converting an extent during the writeback process and the end I/O
>> process, it won't increase the rick of losing data if we postpone the
> ^^^^ risk
>
> Other than the minor typo above, feel free to add:
> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Thank you for reviewing this series, I will revise it in v3.
Cheers,
Yi.
>
>> conversion.
>>
>> Therefore, also use EXT4_GET_BLOCKS_METADATA_NOFAIL in
>> ext4_convert_unwritten_extents_endio() to prepare for the buffered I/O
>> iomap conversion, which may perform extent conversion during the end I/O
>> process.
>>
>> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
>> Reviewed-by: Jan Kara <jack@suse.cz>
>
>> ---
>> fs/ext4/extents.c | 4 +++-
>> 1 file changed, 3 insertions(+), 1 deletion(-)
>>
>> diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
>> index 27eb2c1df012..e53959120b04 100644
>> --- a/fs/ext4/extents.c
>> +++ b/fs/ext4/extents.c
>> @@ -3794,6 +3794,8 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
>> * illegal.
>> */
>> if (ee_block != map->m_lblk || ee_len > map->m_len) {
>> + int flags = EXT4_GET_BLOCKS_CONVERT |
>> + EXT4_GET_BLOCKS_METADATA_NOFAIL;
>> #ifdef CONFIG_EXT4_DEBUG
>> ext4_warning(inode->i_sb, "Inode (%ld) finished: extent logical block %llu,"
>> " len %u; IO logical block %llu, len %u",
>> @@ -3801,7 +3803,7 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
>> (unsigned long long)map->m_lblk, map->m_len);
>> #endif
>> path = ext4_split_convert_extents(handle, inode, map, path,
>> - EXT4_GET_BLOCKS_CONVERT, NULL);
>> + flags, NULL);
>> if (IS_ERR(path))
>> return path;
>>
>> --
>> 2.52.0
>>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin()
2026-01-04 13:00 ` Markus Elfring
@ 2026-01-05 1:18 ` Zhang Yi
0 siblings, 0 replies; 25+ messages in thread
From: Zhang Yi @ 2026-01-05 1:18 UTC (permalink / raw)
To: Markus Elfring, linux-ext4, linux-fsdevel, Baokun Li, Jan Kara,
Ojaswin Mujoo, Theodore Ts'o
Cc: LKML, Andreas Dilger, Ritesh Harjani, Yang Erkun, Yu Kuai, zhangyi
On 1/4/2026 9:00 PM, Markus Elfring wrote:
>> In the write path mapping check of ext4_iomap_begin(), the return value
>> 'ret' should never greater than orig_mlen. If 'ret' equals 'orig_mlen',
>> it can be returned directly without checking IOMAP_ATOMIC.
>
> See also once more:
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/submitting-patches.rst?h=v6.19-rc3#n94
>
>
> How do you think about to use the word “simplify” in the summary phrase?
>
> Regards,
> Markus
Yeah, it makes sense to me, I will revise it in v3.
Thanks,
Yi.
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-01-05 1:18 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-12-23 1:17 [PATCH -next v2 0/7] ext4: defer unwritten splitting until I/O completion Zhang Yi
2025-12-23 1:17 ` [PATCH -next v2 1/7] ext4: use reserved metadata blocks when splitting extent on endio Zhang Yi
2025-12-31 7:34 ` Baokun Li
2026-01-03 13:42 ` Ojaswin Mujoo
2026-01-05 1:17 ` Zhang Yi
2025-12-23 1:17 ` [PATCH -next v2 2/7] ext4: don't split extent before submitting I/O Zhang Yi
2025-12-31 7:35 ` Baokun Li
2026-01-03 13:47 ` Ojaswin Mujoo
2025-12-23 1:17 ` [PATCH -next v2 3/7] ext4: avoid starting handle when dio writing an unwritten extent Zhang Yi
2025-12-31 7:36 ` Baokun Li
2026-01-03 14:06 ` Ojaswin Mujoo
2025-12-23 1:17 ` [PATCH -next v2 4/7] ext4: remove useless ext4_iomap_overwrite_ops Zhang Yi
2025-12-31 7:40 ` Baokun Li
2026-01-03 14:14 ` Ojaswin Mujoo
2025-12-23 1:18 ` [PATCH -next v2 5/7] ext4: remove unused unwritten parameter in ext4_dio_write_iter() Zhang Yi
2025-12-31 7:42 ` Baokun Li
2026-01-03 14:16 ` Ojaswin Mujoo
2025-12-23 1:18 ` [PATCH -next v2 6/7] ext4: simply the mapping query logic in ext4_iomap_begin() Zhang Yi
2025-12-31 7:46 ` Baokun Li
2026-01-03 14:17 ` Ojaswin Mujoo
2026-01-04 13:00 ` Markus Elfring
2026-01-05 1:18 ` Zhang Yi
2025-12-23 1:18 ` [PATCH -next v2 7/7] ext4: remove EXT4_GET_BLOCKS_IO_CREATE_EXT Zhang Yi
2025-12-31 7:55 ` Baokun Li
2026-01-03 14:20 ` Ojaswin Mujoo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®