Skip to content

Commit 18dfa71

Browse files
fdmananakdave
authored andcommitted
Btrfs: fix unwritten extent buffers and hangs on future writeback attempts
The lock_extent_buffer_io() returns 1 to the caller to tell it everything went fine and the callers needs to start writeback for the extent buffer (submit a bio, etc), 0 to tell the caller everything went fine but it does not need to start writeback for the extent buffer, and a negative value if some error happened. When it's about to return 1 it tries to lock all pages, and if a try lock on a page fails, and we didn't flush any existing bio in our "epd", it calls flush_write_bio(epd) and overwrites the return value of 1 to 0 or an error. The page might have been locked elsewhere, not with the goal of starting writeback of the extent buffer, and even by some code other than btrfs, like page migration for example, so it does not mean the writeback of the extent buffer was already started by some other task, so returning a 0 tells the caller (btree_write_cache_pages()) to not start writeback for the extent buffer. Note that epd might currently have either no bio, so flush_write_bio() returns 0 (success) or it might have a bio for another extent buffer with a lower index (logical address). Since we return 0 with the EXTENT_BUFFER_WRITEBACK bit set on the extent buffer and writeback is never started for the extent buffer, future attempts to writeback the extent buffer will hang forever waiting on that bit to be cleared, since it can only be cleared after writeback completes. Such hang is reported with a trace like the following: [49887.347053] INFO: task btrfs-transacti:1752 blocked for more than 122 seconds. [49887.347059] Not tainted 5.2.13-gentoo #2 [49887.347060] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. [49887.347062] btrfs-transacti D 0 1752 2 0x80004000 [49887.347064] Call Trace: [49887.347069] ? __schedule+0x265/0x830 [49887.347071] ? bit_wait+0x50/0x50 [49887.347072] ? bit_wait+0x50/0x50 [49887.347074] schedule+0x24/0x90 [49887.347075] io_schedule+0x3c/0x60 [49887.347077] bit_wait_io+0x8/0x50 [49887.347079] __wait_on_bit+0x6c/0x80 [49887.347081] ? __lock_release.isra.29+0x155/0x2d0 [49887.347083] out_of_line_wait_on_bit+0x7b/0x80 [49887.347084] ? var_wake_function+0x20/0x20 [49887.347087] lock_extent_buffer_for_io+0x28c/0x390 [49887.347089] btree_write_cache_pages+0x18e/0x340 [49887.347091] do_writepages+0x29/0xb0 [49887.347093] ? kmem_cache_free+0x132/0x160 [49887.347095] ? convert_extent_bit+0x544/0x680 [49887.347097] filemap_fdatawrite_range+0x70/0x90 [49887.347099] btrfs_write_marked_extents+0x53/0x120 [49887.347100] btrfs_write_and_wait_transaction.isra.4+0x38/0xa0 [49887.347102] btrfs_commit_transaction+0x6bb/0x990 [49887.347103] ? start_transaction+0x33e/0x500 [49887.347105] transaction_kthread+0x139/0x15c So fix this by not overwriting the return value (ret) with the result from flush_write_bio(). We also need to clear the EXTENT_BUFFER_WRITEBACK bit in case flush_write_bio() returns an error, otherwise it will hang any future attempts to writeback the extent buffer, and undo all work done before (set back EXTENT_BUFFER_DIRTY, etc). This is a regression introduced in the 5.2 kernel. Fixes: 2e3c251 ("btrfs: extent_io: add proper error handling to lock_extent_buffer_for_io()") Fixes: f434062 ("btrfs: extent_io: Move the BUG_ON() in flush_write_bio() one level up") Reported-by: Zdenek Sojka <[email protected]> Link: https://lore.kernel.org/linux-btrfs/GpO.2yos.3WGDOLpx6t%[email protected]/T/#u Reported-by: Stefan Priebe - Profihost AG <[email protected]> Link: https://lore.kernel.org/linux-btrfs/[email protected]/T/#t Reported-by: Drazen Kacar <[email protected]> Link: https://lore.kernel.org/linux-btrfs/DB8PR03MB562876ECE2319B3E579590F799C80@DB8PR03MB5628.eurprd03.prod.outlook.com/ Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=204377 Signed-off-by: Filipe Manana <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 410f954 commit 18dfa71

File tree

1 file changed

+26
-9
lines changed

1 file changed

+26
-9
lines changed

fs/btrfs/extent_io.c

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3628,6 +3628,13 @@ void wait_on_extent_buffer_writeback(struct extent_buffer *eb)
36283628
TASK_UNINTERRUPTIBLE);
36293629
}
36303630

3631+
static void end_extent_buffer_writeback(struct extent_buffer *eb)
3632+
{
3633+
clear_bit(EXTENT_BUFFER_WRITEBACK, &eb->bflags);
3634+
smp_mb__after_atomic();
3635+
wake_up_bit(&eb->bflags, EXTENT_BUFFER_WRITEBACK);
3636+
}
3637+
36313638
/*
36323639
* Lock eb pages and flush the bio if we can't the locks
36333640
*
@@ -3699,8 +3706,11 @@ static noinline_for_stack int lock_extent_buffer_for_io(struct extent_buffer *eb
36993706

37003707
if (!trylock_page(p)) {
37013708
if (!flush) {
3702-
ret = flush_write_bio(epd);
3703-
if (ret < 0) {
3709+
int err;
3710+
3711+
err = flush_write_bio(epd);
3712+
if (err < 0) {
3713+
ret = err;
37043714
failed_page_nr = i;
37053715
goto err_unlock;
37063716
}
@@ -3715,16 +3725,23 @@ static noinline_for_stack int lock_extent_buffer_for_io(struct extent_buffer *eb
37153725
/* Unlock already locked pages */
37163726
for (i = 0; i < failed_page_nr; i++)
37173727
unlock_page(eb->pages[i]);
3728+
/*
3729+
* Clear EXTENT_BUFFER_WRITEBACK and wake up anyone waiting on it.
3730+
* Also set back EXTENT_BUFFER_DIRTY so future attempts to this eb can
3731+
* be made and undo everything done before.
3732+
*/
3733+
btrfs_tree_lock(eb);
3734+
spin_lock(&eb->refs_lock);
3735+
set_bit(EXTENT_BUFFER_DIRTY, &eb->bflags);
3736+
end_extent_buffer_writeback(eb);
3737+
spin_unlock(&eb->refs_lock);
3738+
percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, eb->len,
3739+
fs_info->dirty_metadata_batch);
3740+
btrfs_clear_header_flag(eb, BTRFS_HEADER_FLAG_WRITTEN);
3741+
btrfs_tree_unlock(eb);
37183742
return ret;
37193743
}
37203744

3721-
static void end_extent_buffer_writeback(struct extent_buffer *eb)
3722-
{
3723-
clear_bit(EXTENT_BUFFER_WRITEBACK, &eb->bflags);
3724-
smp_mb__after_atomic();
3725-
wake_up_bit(&eb->bflags, EXTENT_BUFFER_WRITEBACK);
3726-
}
3727-
37283745
static void set_btree_ioerr(struct page *page)
37293746
{
37303747
struct extent_buffer *eb = (struct extent_buffer *)page->private;

0 commit comments

Comments
 (0)