[PATCH v6 26/31] ext4: clear DISKSIZE_GROW_PENDING on truncate or error
From: Zhang Yi
Date: Thu Sep 03 2026 - 08:53:57 EST
From: Zhang Yi <yi.zhang@xxxxxxxxxx>
The disksize-grow-pending state is set when a zeroed EOF block is queued
for writeback and cleared by the ioend completion path once writeback
finishes. However, the zeroed block may be discarded before writeback
completes — through folio discard, truncate, or unlink and inode
eviction.
In any of these cases, leaving the bit set would block subsequent
writeback indefinitely. Therefore, we must clear it on all paths that
invalidate the pending block before writeback completes:
- ext4_iomap_discard_folio() on folio discard.
- ext4_evict_inode() when an unlinked inode is destroyed.
In ext4_truncate_down(), truncating past the pending zeroed EOF block
also invalidates the pending disksize update, so the bit must be cleared
there as well.
Finally, add a WARN_ON in ext4_destroy_inode() to catch any inode
destroyed with the bit still set. The check is skipped when the
filesystem is in an error or emergency state, as the pending block may
not have been written back in those cases.
Note that EXT4_STATE_DISKSIZE_GROW_PENDING is not set for now, and it
will be set after everthing is done.
Signed-off-by: Zhang Yi <yi.zhang@xxxxxxxxxx>
---
fs/ext4/inode.c | 24 +++++++++++++++++++++++-
fs/ext4/super.c | 8 ++++++++
2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index c81a28b67913..8e8859c8f5b0 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -273,6 +273,8 @@ void ext4_evict_inode(struct inode *inode)
if (ext4_should_order_data(inode))
ext4_begin_ordered_truncate(inode, 0);
+ if (ext4_inode_buffered_iomap(inode))
+ ext4_iomap_clear_disksize_pending(inode);
truncate_inode_pages_final(&inode->i_data);
/*
@@ -4344,8 +4346,17 @@ static void ext4_iomap_discard_folio(struct folio *folio, loff_t pos)
{
struct inode *inode = folio->mapping->host;
loff_t length = folio_pos(folio) + folio_size(folio) - pos;
+ loff_t pstart, plen;
ext4_iomap_punch_delalloc(inode, pos, length, NULL);
+
+ /*
+ * Clear the disksize-grow-pending state if the zeroed EOF block
+ * fails to write back and is discarded.
+ */
+ plen = ext4_iomap_get_disksize_pending_range(inode, &pstart);
+ if (plen && pos <= pstart && folio_next_pos(folio) >= pstart + plen)
+ ext4_iomap_clear_disksize_pending(inode);
}
static ssize_t ext4_iomap_writeback_range(struct iomap_writepage_ctx *wpc,
@@ -6765,7 +6776,18 @@ static int ext4_truncate_down(struct inode *inode, loff_t oldsize,
start_lblk = newsize > 0 ? (newsize - 1) >> inode->i_blkbits : 0;
ext4_fc_track_range(handle, inode, start_lblk, EXT_MAX_BLOCKS - 1);
- ext4_set_inode_size(inode, newsize);
+ down_write(&EXT4_I(inode)->i_data_sem);
+ /*
+ * Truncate the zeroed EOF block invalidates the pending disksize
+ * update, so clear the disksize-grow-pending state.
+ */
+ if (ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING) &&
+ (newsize <= EXT4_I(inode)->i_disksize))
+ ext4_iomap_clear_disksize_pending(inode);
+
+ i_size_write(inode, newsize);
+ __ext4_set_i_disksize(inode, newsize);
+ up_write(&EXT4_I(inode)->i_data_sem);
ret = ext4_mark_inode_dirty(handle, inode);
ext4_journal_stop(handle);
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 1c2395aa1d53..73735fd336b9 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1497,6 +1497,14 @@ static void ext4_destroy_inode(struct inode *inode)
"Inode %llu (%p): i_reserved_data_blocks (%u) not cleared!",
inode->i_ino, EXT4_I(inode),
EXT4_I(inode)->i_reserved_data_blocks);
+
+ if (!(EXT4_SB(inode->i_sb)->s_mount_state & EXT4_ERROR_FS) &&
+ !ext4_emergency_state(inode->i_sb) &&
+ WARN_ON_ONCE(ext4_test_inode_state(inode,
+ EXT4_STATE_DISKSIZE_GROW_PENDING)))
+ ext4_msg(inode->i_sb, KERN_ERR,
+ "Inode %llu (%p): EXT4_STATE_DISKSIZE_GROW_PENDING not cleared!",
+ inode->i_ino, EXT4_I(inode));
}
static void ext4_shutdown(struct super_block *sb)
--
2.52.0