From: Zhang Yi 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 --- 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