| From af7f89f19dba38857e69a3132d5c21ed77e0e860 Mon Sep 17 00:00:00 2001 |
| From: Josef Bacik <josef@toxicpanda.com> |
| Date: Wed, 4 Mar 2020 11:18:30 -0500 |
| Subject: [PATCH] btrfs: remove a BUG_ON() from merge_reloc_roots() |
| |
| commit 7b7b74315b24dc064bc1c683659061c3d48f8668 upstream. |
| |
| This was pretty subtle, we default to reloc roots having 0 root refs, so |
| if we crash in the middle of the relocation they can just be deleted. |
| If we successfully complete the relocation operations we'll set our root |
| refs to 1 in prepare_to_merge() and then go on to merge_reloc_roots(). |
| |
| At prepare_to_merge() time if any of the reloc roots have a 0 reference |
| still, we will remove that reloc root from our reloc root rb tree, and |
| then clean it up later. |
| |
| However this only happens if we successfully start a transaction. If |
| we've aborted previously we will skip this step completely, and only |
| have reloc roots with a reference count of 0, but were never properly |
| removed from the reloc control's rb tree. |
| |
| This isn't a problem per-se, our references are held by the list the |
| reloc roots are on, and by the original root the reloc root belongs to. |
| If we end up in this situation all the reloc roots will be added to the |
| dirty_reloc_list, and then properly dropped at that point. The reloc |
| control will be free'd and the rb tree is no longer used. |
| |
| There were two options when fixing this, one was to remove the BUG_ON(), |
| the other was to make prepare_to_merge() handle the case where we |
| couldn't start a trans handle. |
| |
| IMO this is the cleaner solution. I started with handling the error in |
| prepare_to_merge(), but it turned out super ugly. And in the end this |
| BUG_ON() simply doesn't matter, the cleanup was happening properly, we |
| were just panicing because this BUG_ON() only matters in the success |
| case. So I've opted to just remove it and add a comment where it was. |
| |
| Reviewed-by: Qu Wenruo <wqu@suse.com> |
| Signed-off-by: Josef Bacik <josef@toxicpanda.com> |
| Signed-off-by: David Sterba <dsterba@suse.com> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c |
| index 2a2426e0e765..16a9ee6f49b4 100644 |
| --- a/fs/btrfs/relocation.c |
| +++ b/fs/btrfs/relocation.c |
| @@ -2519,7 +2519,21 @@ void merge_reloc_roots(struct reloc_control *rc) |
| free_reloc_roots(&reloc_roots); |
| } |
| |
| - BUG_ON(!RB_EMPTY_ROOT(&rc->reloc_root_tree.rb_root)); |
| + /* |
| + * We used to have |
| + * |
| + * BUG_ON(!RB_EMPTY_ROOT(&rc->reloc_root_tree.rb_root)); |
| + * |
| + * here, but it's wrong. If we fail to start the transaction in |
| + * prepare_to_merge() we will have only 0 ref reloc roots, none of which |
| + * have actually been removed from the reloc_root_tree rb tree. This is |
| + * fine because we're bailing here, and we hold a reference on the root |
| + * for the list that holds it, so these roots will be cleaned up when we |
| + * do the reloc_dirty_list afterwards. Meanwhile the root->reloc_root |
| + * will be cleaned up on unmount. |
| + * |
| + * The remaining nodes will be cleaned up by free_reloc_control. |
| + */ |
| } |
| |
| static void free_block_list(struct rb_root *blocks) |
| -- |
| 2.7.4 |
| |