[PATCH 5/5] btrfs: zoned: avoid ordered_operations_mutex when finishing a zone
Johannes Thumshirn <[email protected]>
| Newsgroups | org.kernel.vger.linux-btrfs |
|---|---|
| Message-ID | <[email protected]> |
On a zoned filesystem with no free active zones, a data allocation finishes a
block group to make room:
cow_file_range -> btrfs_reserve_extent -> find_free_extent ->
btrfs_zone_finish_one_bg -> do_zone_finish ->
btrfs_wait_ordered_roots -> mutex_lock(&fs_info->ordered_operations_mutex)
This can be reached from an ordered extent flush worker
(btrfs_run_ordered_extent_work) which is run and waited for by
btrfs_wait_ordered_roots() while it holds ordered_operations_mutex:
btrfs_async_reclaim_metadata_space -> flush_space ->
btrfs_wait_ordered_roots -> mutex_lock(&fs_info->ordered_operations_mutex)
-> wait for the flush work to complete
The flush worker then blocks on the same, non-recursive mutex the reclaim
task holds while waiting for that very worker, so the filesystem deadlocks.
It was observed as a hang in generic/048:
INFO: task kworker/u36:8 blocked for more than 120 seconds.
btrfs_wait_ordered_roots -> flush_space -> btrfs_async_reclaim_metadata_space
INFO: task kworker/u34:6 is blocked on a mutex likely owned by kworker/u36:8
btrfs_wait_ordered_roots -> do_zone_finish -> btrfs_zone_finish_one_bg ->
find_free_extent -> cow_file_range
The cycle has existed since data block group allocation started finishing
zones (commit 393f646e34c1 ("btrfs: zoned: finish least available block
group on data bg allocation")).
Add btrfs_wait_ordered_roots_nolock(), which waits for a block group's
ordered extents using the multi-waiter safe ordered->wait bit and never
takes ordered_operations_mutex or ordered_extent_mutex, and use it from
do_zone_finish(). The block group is already read-only there, so no new
ordered extents can appear in its range while we wait. This relies on the
preceding two patches keeping the ordered extent and root lists visible
(they no longer splice the lists away) so the walk cannot miss an in-flight
ordered extent.
Assisted-by: LLM (debugging, commit message)
Signed-off-by: Johannes Thumshirn <[email protected]>
---
fs/btrfs/ordered-data.c | 74 +++++++++++++++++++++++++++++++++++++++++
fs/btrfs/ordered-data.h | 2 ++
fs/btrfs/zoned.c | 8 +++--
3 files changed, 82 insertions(+), 2 deletions(-)
diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index ddd21aa5c640..12102fc76696 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -866,6 +866,80 @@ void btrfs_wait_ordered_roots(struct btrfs_fs_info *fs_info, u64 nr,
mutex_unlock(&fs_info->ordered_operations_mutex);
}
+/*
+ * Wait for all ordered extents intersecting @bg to finish without taking
+ * ordered_operations_mutex or ordered_extent_mutex, so it is safe to call from
+ * the zone-finish path (which can run inside the ordered extent flush worker
+ * that a btrfs_wait_ordered_roots() caller is waiting for). @bg must be
+ * read-only, so its set of ordered extents only shrinks while we wait.
+ */
+void btrfs_wait_ordered_roots_nolock(struct btrfs_fs_info *fs_info,
+ const struct btrfs_block_group *bg)
+{
+ const u64 range_start = bg->start;
+ const u64 range_end = bg->start + bg->length;
+ struct btrfs_root **roots;
+ struct btrfs_root *root;
+ int nr_roots = 0;
+ int cap = 0;
+ int i;
+
+ spin_lock(&fs_info->ordered_root_lock);
+ cap = list_count_nodes(&fs_info->ordered_roots);
+ spin_unlock(&fs_info->ordered_root_lock);
+ if (!cap)
+ return;
+
+ roots = kvmalloc_array(cap, sizeof(*roots), GFP_NOFS);
+ if (!roots) {
+ btrfs_wait_ordered_roots(fs_info, U64_MAX, bg);
+ return;
+ }
+
+ spin_lock(&fs_info->ordered_root_lock);
+ list_for_each_entry(root, &fs_info->ordered_roots, ordered_root) {
+ if (nr_roots >= cap)
+ break;
+ if (!btrfs_grab_root(root))
+ continue;
+ roots[nr_roots++] = root;
+ }
+ spin_unlock(&fs_info->ordered_root_lock);
+
+ for (i = 0; i < nr_roots; i++) {
+ root = roots[i];
+
+ while (true) {
+ struct btrfs_ordered_extent *found = NULL;
+ struct btrfs_ordered_extent *ordered;
+
+ spin_lock(&root->ordered_extent_lock);
+ list_for_each_entry(ordered, &root->ordered_extents,
+ root_extent_list) {
+ if (test_bit(BTRFS_ORDERED_COMPLETE,
+ &ordered->flags))
+ continue;
+ if (!ordered_in_range(ordered, range_start,
+ range_end))
+ continue;
+ found = ordered;
+ refcount_inc(&found->refs);
+ break;
+ }
+ spin_unlock(&root->ordered_extent_lock);
+
+ if (!found)
+ break;
+
+ btrfs_start_ordered_extent(found);
+ btrfs_put_ordered_extent(found);
+ cond_resched();
+ }
+ btrfs_put_root(root);
+ }
+ kvfree(roots);
+}
+
/*
* Start IO and wait for a given ordered extent to finish.
*
diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
index f4d0675bb9c3..d8280a7bccb1 100644
--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -220,6 +220,8 @@ u64 btrfs_wait_ordered_extents(struct btrfs_root *root, u64 nr,
const struct btrfs_block_group *bg);
void btrfs_wait_ordered_roots(struct btrfs_fs_info *fs_info, u64 nr,
const struct btrfs_block_group *bg);
+void btrfs_wait_ordered_roots_nolock(struct btrfs_fs_info *fs_info,
+ const struct btrfs_block_group *bg);
void btrfs_lock_and_flush_ordered_range(struct btrfs_inode *inode, u64 start,
u64 end,
struct extent_state **cached_state);
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index a016cb471beb..3a3ef96dcc8b 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -2585,8 +2585,12 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ
/* Ensure all writes in this block group finish */
btrfs_wait_block_group_reservations(block_group);
- /* No need to wait for NOCOW writers. Zoned mode does not allow that */
- btrfs_wait_ordered_roots(fs_info, U64_MAX, block_group);
+ /*
+ * No need to wait for NOCOW writers. Zoned mode does not allow
+ * that. Use the lock-free variant as we can be called from the
+ * allocator inside an ordered extent flush worker.
+ */
+ btrfs_wait_ordered_roots_nolock(fs_info, block_group);
/* Wait for extent buffers to be written. */
if (is_metadata)
wait_eb_writebacks(block_group);
--
2.54.0