Skip to content

Commit ec4cb1a

Browse files
jankaratytso
authored andcommitted
ext4: fix jbd2 warning under heavy xattr load
When heavily exercising xattr code the assertion that jbd2_journal_dirty_metadata() shouldn't return error was triggered: WARNING: at /srv/autobuild-ceph/gitbuilder.git/build/fs/jbd2/transaction.c:1237 jbd2_journal_dirty_metadata+0x1ba/0x260() CPU: 0 PID: 8877 Comm: ceph-osd Tainted: G W 3.10.0-ceph-00049-g68d04c9 #1 Hardware name: Dell Inc. PowerEdge R410/01V648, BIOS 1.6.3 02/07/2011 ffffffff81a1d3c8 ffff880214469928 ffffffff816311b0 ffff880214469968 ffffffff8103fae0 ffff880214469958 ffff880170a9dc30 ffff8802240fbe80 0000000000000000 ffff88020b366000 ffff8802256e7510 ffff880214469978 Call Trace: [<ffffffff816311b0>] dump_stack+0x19/0x1b [<ffffffff8103fae0>] warn_slowpath_common+0x70/0xa0 [<ffffffff8103fb2a>] warn_slowpath_null+0x1a/0x20 [<ffffffff81267c2a>] jbd2_journal_dirty_metadata+0x1ba/0x260 [<ffffffff81245093>] __ext4_handle_dirty_metadata+0xa3/0x140 [<ffffffff812561f3>] ext4_xattr_release_block+0x103/0x1f0 [<ffffffff81256680>] ext4_xattr_block_set+0x1e0/0x910 [<ffffffff8125795b>] ext4_xattr_set_handle+0x38b/0x4a0 [<ffffffff810a319d>] ? trace_hardirqs_on+0xd/0x10 [<ffffffff81257b32>] ext4_xattr_set+0xc2/0x140 [<ffffffff81258547>] ext4_xattr_user_set+0x47/0x50 [<ffffffff811935ce>] generic_setxattr+0x6e/0x90 [<ffffffff81193ecb>] __vfs_setxattr_noperm+0x7b/0x1c0 [<ffffffff811940d4>] vfs_setxattr+0xc4/0xd0 [<ffffffff8119421e>] setxattr+0x13e/0x1e0 [<ffffffff811719c7>] ? __sb_start_write+0xe7/0x1b0 [<ffffffff8118f2e8>] ? mnt_want_write_file+0x28/0x60 [<ffffffff8118c65c>] ? fget_light+0x3c/0x130 [<ffffffff8118f2e8>] ? mnt_want_write_file+0x28/0x60 [<ffffffff8118f1f8>] ? __mnt_want_write+0x58/0x70 [<ffffffff811946be>] SyS_fsetxattr+0xbe/0x100 [<ffffffff816407c2>] system_call_fastpath+0x16/0x1b The reason for the warning is that buffer_head passed into jbd2_journal_dirty_metadata() didn't have journal_head attached. This is caused by the following race of two ext4_xattr_release_block() calls: CPU1 CPU2 ext4_xattr_release_block() ext4_xattr_release_block() lock_buffer(bh); /* False */ if (BHDR(bh)->h_refcount == cpu_to_le32(1)) } else { le32_add_cpu(&BHDR(bh)->h_refcount, -1); unlock_buffer(bh); lock_buffer(bh); /* True */ if (BHDR(bh)->h_refcount == cpu_to_le32(1)) get_bh(bh); ext4_free_blocks() ... jbd2_journal_forget() jbd2_journal_unfile_buffer() -> JH is gone error = ext4_handle_dirty_xattr_block(handle, inode, bh); -> triggers the warning We fix the problem by moving ext4_handle_dirty_xattr_block() under the buffer lock. Sadly this cannot be done in nojournal mode as that function can call sync_dirty_buffer() which would deadlock. Luckily in nojournal mode the race is harmless (we only dirty already freed buffer) and thus for nojournal mode we leave the dirtying outside of the buffer lock. Reported-by: Sage Weil <[email protected]> Signed-off-by: Jan Kara <[email protected]> Signed-off-by: "Theodore Ts'o" <[email protected]> Cc: [email protected]
1 parent 9503c67 commit ec4cb1a

File tree

1 file changed

+19
-4
lines changed

1 file changed

+19
-4
lines changed

fs/ext4/xattr.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -520,8 +520,8 @@ static void ext4_xattr_update_super_block(handle_t *handle,
520520
}
521521

522522
/*
523-
* Release the xattr block BH: If the reference count is > 1, decrement
524-
* it; otherwise free the block.
523+
* Release the xattr block BH: If the reference count is > 1, decrement it;
524+
* otherwise free the block.
525525
*/
526526
static void
527527
ext4_xattr_release_block(handle_t *handle, struct inode *inode,
@@ -542,16 +542,31 @@ ext4_xattr_release_block(handle_t *handle, struct inode *inode,
542542
if (ce)
543543
mb_cache_entry_free(ce);
544544
get_bh(bh);
545+
unlock_buffer(bh);
545546
ext4_free_blocks(handle, inode, bh, 0, 1,
546547
EXT4_FREE_BLOCKS_METADATA |
547548
EXT4_FREE_BLOCKS_FORGET);
548-
unlock_buffer(bh);
549549
} else {
550550
le32_add_cpu(&BHDR(bh)->h_refcount, -1);
551551
if (ce)
552552
mb_cache_entry_release(ce);
553+
/*
554+
* Beware of this ugliness: Releasing of xattr block references
555+
* from different inodes can race and so we have to protect
556+
* from a race where someone else frees the block (and releases
557+
* its journal_head) before we are done dirtying the buffer. In
558+
* nojournal mode this race is harmless and we actually cannot
559+
* call ext4_handle_dirty_xattr_block() with locked buffer as
560+
* that function can call sync_dirty_buffer() so for that case
561+
* we handle the dirtying after unlocking the buffer.
562+
*/
563+
if (ext4_handle_valid(handle))
564+
error = ext4_handle_dirty_xattr_block(handle, inode,
565+
bh);
553566
unlock_buffer(bh);
554-
error = ext4_handle_dirty_xattr_block(handle, inode, bh);
567+
if (!ext4_handle_valid(handle))
568+
error = ext4_handle_dirty_xattr_block(handle, inode,
569+
bh);
555570
if (IS_SYNC(inode))
556571
ext4_handle_sync(handle);
557572
dquot_free_block(inode, EXT4_C2B(EXT4_SB(inode->i_sb), 1));

0 commit comments

Comments
 (0)