ext4: ext4_read_bh_lock() should submit IO if the buffer isn't uptodate
authorZhang Yi <yi.zhang@huawei.com>
Wed, 31 Aug 2022 07:46:29 +0000 (15:46 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 26 Oct 2022 10:34:29 +0000 (12:34 +0200)
commit 0b73284c564d3ae4feef4bc920292f004acf4980 upstream.

Recently we notice that ext4 filesystem would occasionally fail to read
metadata from disk and report error message, but the disk and block
layer looks fine. After analyse, we lockon commit 88dbcbb3a484
("blkdev: avoid migration stalls for blkdev pages"). It provide a
migration method for the bdev, we could move page that has buffers
without extra users now, but it lock the buffers on the page, which
breaks the fragile metadata read operation on ext4 filesystem,
ext4_read_bh_lock() was copied from ll_rw_block(), it depends on the
assumption of that locked buffer means it is under IO. So it just
trylock the buffer and skip submit IO if it lock failed, after
wait_on_buffer() we conclude IO error because the buffer is not
uptodate.

This issue could be easily reproduced by add some delay just after
buffer_migrate_lock_buffers() in __buffer_migrate_folio() and do
fsstress on ext4 filesystem.

  EXT4-fs error (device pmem1): __ext4_find_entry:1658: inode #73193:
  comm fsstress: reading directory lblock 0
  EXT4-fs error (device pmem1): __ext4_find_entry:1658: inode #75334:
  comm fsstress: reading directory lblock 0

Fix it by removing the trylock logic in ext4_read_bh_lock(), just lock
the buffer and submit IO if it's not uptodate, and also leave over
readahead helper.

Cc: stable@kernel.org
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Link: https://lore.kernel.org/r/20220831074629.3755110-1-yi.zhang@huawei.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/ext4/super.c

index 823f35e..eec4199 100644 (file)
@@ -186,19 +186,12 @@ int ext4_read_bh(struct buffer_head *bh, int op_flags, bh_end_io_t *end_io)
 
 int ext4_read_bh_lock(struct buffer_head *bh, int op_flags, bool wait)
 {
-       if (trylock_buffer(bh)) {
-               if (wait)
-                       return ext4_read_bh(bh, op_flags, NULL);
+       lock_buffer(bh);
+       if (!wait) {
                ext4_read_bh_nowait(bh, op_flags, NULL);
                return 0;
        }
-       if (wait) {
-               wait_on_buffer(bh);
-               if (buffer_uptodate(bh))
-                       return 0;
-               return -EIO;
-       }
-       return 0;
+       return ext4_read_bh(bh, op_flags, NULL);
 }
 
 /*
@@ -245,7 +238,8 @@ void ext4_sb_breadahead_unmovable(struct super_block *sb, sector_t block)
        struct buffer_head *bh = sb_getblk_gfp(sb, block, 0);
 
        if (likely(bh)) {
-               ext4_read_bh_lock(bh, REQ_RAHEAD, false);
+               if (trylock_buffer(bh))
+                       ext4_read_bh_nowait(bh, REQ_RAHEAD, NULL);
                brelse(bh);
        }
 }