Commit 9f3a7427 authored by Chris Mason's avatar Chris Mason Committed by David Woodhouse

Btrfs: Do snapshot deletion in smaller chunks.

Before, snapshot deletion was a single atomic unit.  This caused considerable
lock contention and required an unbounded amount of space.  Now,
the drop_progress field in the root item is used to indicate how far along
snapshot deletion is, and to resume where it left off.
Signed-off-by: default avatarChris Mason <chris.mason@oracle.com>
parent 8578f0f1
...@@ -659,9 +659,16 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -659,9 +659,16 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root
struct buffer_head *b; struct buffer_head *b;
struct buffer_head *cow_buf; struct buffer_head *cow_buf;
struct btrfs_node *c; struct btrfs_node *c;
struct btrfs_root_item *root_item = &root->root_item;
int slot; int slot;
int ret; int ret;
int level; int level;
u8 lowest_level = 0;
if (btrfs_root_refs(root_item) == 0 && root->ref_cows) {
lowest_level = root_item->drop_level;
WARN_ON(ins_len || cow);
}
WARN_ON(p->nodes[0] != NULL); WARN_ON(p->nodes[0] != NULL);
WARN_ON(!mutex_is_locked(&root->fs_info->fs_mutex)); WARN_ON(!mutex_is_locked(&root->fs_info->fs_mutex));
...@@ -718,6 +725,9 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -718,6 +725,9 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root
slot = p->slots[level]; slot = p->slots[level];
BUG_ON(btrfs_header_nritems(&c->header) == 1); BUG_ON(btrfs_header_nritems(&c->header) == 1);
} }
/* this is only true while dropping a snapshot */
if (level == lowest_level)
break;
b = read_tree_block(root, btrfs_node_blockptr(c, slot)); b = read_tree_block(root, btrfs_node_blockptr(c, slot));
} else { } else {
struct btrfs_leaf *l = (struct btrfs_leaf *)c; struct btrfs_leaf *l = (struct btrfs_leaf *)c;
......
...@@ -333,10 +333,10 @@ struct btrfs_root { ...@@ -333,10 +333,10 @@ struct btrfs_root {
u64 objectid; u64 objectid;
u64 last_trans; u64 last_trans;
u32 blocksize; u32 blocksize;
int ref_cows;
u32 type; u32 type;
u64 highest_inode; u64 highest_inode;
u64 last_inode_alloc; u64 last_inode_alloc;
int ref_cows;
}; };
/* the lower bits in the key flags defines the item type */ /* the lower bits in the key flags defines the item type */
...@@ -1073,7 +1073,7 @@ int btrfs_insert_empty_item(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -1073,7 +1073,7 @@ int btrfs_insert_empty_item(struct btrfs_trans_handle *trans, struct btrfs_root
int btrfs_next_leaf(struct btrfs_root *root, struct btrfs_path *path); int btrfs_next_leaf(struct btrfs_root *root, struct btrfs_path *path);
int btrfs_leaf_free_space(struct btrfs_root *root, struct btrfs_leaf *leaf); int btrfs_leaf_free_space(struct btrfs_root *root, struct btrfs_leaf *leaf);
int btrfs_drop_snapshot(struct btrfs_trans_handle *trans, struct btrfs_root int btrfs_drop_snapshot(struct btrfs_trans_handle *trans, struct btrfs_root
*root, struct buffer_head *snap); *root);
/* root-item.c */ /* root-item.c */
int btrfs_del_root(struct btrfs_trans_handle *trans, struct btrfs_root *root, int btrfs_del_root(struct btrfs_trans_handle *trans, struct btrfs_root *root,
struct btrfs_key *key); struct btrfs_key *key);
......
...@@ -1561,12 +1561,21 @@ static int walk_up_tree(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -1561,12 +1561,21 @@ static int walk_up_tree(struct btrfs_trans_handle *trans, struct btrfs_root
int i; int i;
int slot; int slot;
int ret; int ret;
struct btrfs_root_item *root_item = &root->root_item;
for(i = *level; i < BTRFS_MAX_LEVEL - 1 && path->nodes[i]; i++) { for(i = *level; i < BTRFS_MAX_LEVEL - 1 && path->nodes[i]; i++) {
slot = path->slots[i]; slot = path->slots[i];
if (slot < btrfs_header_nritems( if (slot < btrfs_header_nritems(
btrfs_buffer_header(path->nodes[i])) - 1) { btrfs_buffer_header(path->nodes[i])) - 1) {
struct btrfs_node *node;
node = btrfs_buffer_node(path->nodes[i]);
path->slots[i]++; path->slots[i]++;
*level = i; *level = i;
WARN_ON(*level == 0);
memcpy(&root_item->drop_progress,
&node->ptrs[path->slots[i]].key,
sizeof(root_item->drop_progress));
root_item->drop_level = i;
return 0; return 0;
} else { } else {
ret = btrfs_free_extent(trans, root, ret = btrfs_free_extent(trans, root,
...@@ -1587,7 +1596,7 @@ static int walk_up_tree(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -1587,7 +1596,7 @@ static int walk_up_tree(struct btrfs_trans_handle *trans, struct btrfs_root
* decremented. * decremented.
*/ */
int btrfs_drop_snapshot(struct btrfs_trans_handle *trans, struct btrfs_root int btrfs_drop_snapshot(struct btrfs_trans_handle *trans, struct btrfs_root
*root, struct buffer_head *snap) *root)
{ {
int ret = 0; int ret = 0;
int wret; int wret;
...@@ -1595,14 +1604,33 @@ int btrfs_drop_snapshot(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -1595,14 +1604,33 @@ int btrfs_drop_snapshot(struct btrfs_trans_handle *trans, struct btrfs_root
struct btrfs_path *path; struct btrfs_path *path;
int i; int i;
int orig_level; int orig_level;
int num_walks = 0;
struct btrfs_root_item *root_item = &root->root_item;
path = btrfs_alloc_path(); path = btrfs_alloc_path();
BUG_ON(!path); BUG_ON(!path);
level = btrfs_header_level(btrfs_buffer_header(snap)); level = btrfs_header_level(btrfs_buffer_header(root->node));
orig_level = level; orig_level = level;
path->nodes[level] = snap; if (btrfs_disk_key_objectid(&root_item->drop_progress) == 0) {
path->nodes[level] = root->node;
path->slots[level] = 0; path->slots[level] = 0;
} else {
struct btrfs_key key;
struct btrfs_disk_key *found_key;
struct btrfs_node *node;
btrfs_disk_key_to_cpu(&key, &root_item->drop_progress);
wret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
if (ret < 0) {
ret = wret;
goto out;
}
level = root_item->drop_level;
node = btrfs_buffer_node(path->nodes[level]);
found_key = &node->ptrs[path->slots[level]].key;
WARN_ON(memcmp(found_key, &root_item->drop_progress,
sizeof(*found_key)));
}
while(1) { while(1) {
wret = walk_down_tree(trans, root, path, &level); wret = walk_down_tree(trans, root, path, &level);
if (wret > 0) if (wret > 0)
...@@ -1615,12 +1643,21 @@ int btrfs_drop_snapshot(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -1615,12 +1643,21 @@ int btrfs_drop_snapshot(struct btrfs_trans_handle *trans, struct btrfs_root
break; break;
if (wret < 0) if (wret < 0)
ret = wret; ret = wret;
num_walks++;
if (num_walks > 10) {
struct btrfs_key key;
btrfs_disk_key_to_cpu(&key, &root_item->drop_progress);
ret = -EAGAIN;
get_bh(root->node);
break;
}
} }
for (i = 0; i <= orig_level; i++) { for (i = 0; i <= orig_level; i++) {
if (path->nodes[i]) { if (path->nodes[i]) {
btrfs_block_release(root, path->nodes[i]); btrfs_block_release(root, path->nodes[i]);
} }
} }
out:
btrfs_free_path(path); btrfs_free_path(path);
return ret; return ret;
} }
......
...@@ -213,10 +213,7 @@ static int wait_for_commit(struct btrfs_root *root, ...@@ -213,10 +213,7 @@ static int wait_for_commit(struct btrfs_root *root,
struct dirty_root { struct dirty_root {
struct list_head list; struct list_head list;
struct btrfs_key snap_key;
struct buffer_head *commit_root;
struct btrfs_root *root; struct btrfs_root *root;
int free_on_drop;
}; };
int btrfs_add_dead_root(struct btrfs_root *root, struct list_head *dead_list) int btrfs_add_dead_root(struct btrfs_root *root, struct list_head *dead_list)
...@@ -226,10 +223,7 @@ int btrfs_add_dead_root(struct btrfs_root *root, struct list_head *dead_list) ...@@ -226,10 +223,7 @@ int btrfs_add_dead_root(struct btrfs_root *root, struct list_head *dead_list)
dirty = kmalloc(sizeof(*dirty), GFP_NOFS); dirty = kmalloc(sizeof(*dirty), GFP_NOFS);
if (!dirty) if (!dirty)
return -ENOMEM; return -ENOMEM;
memcpy(&dirty->snap_key, &root->root_key, sizeof(root->root_key));
dirty->commit_root = root->node;
dirty->root = root; dirty->root = root;
dirty->free_on_drop = 1;
list_add(&dirty->list, dead_list); list_add(&dirty->list, dead_list);
return 0; return 0;
} }
...@@ -241,7 +235,6 @@ static int add_dirty_roots(struct btrfs_trans_handle *trans, ...@@ -241,7 +235,6 @@ static int add_dirty_roots(struct btrfs_trans_handle *trans,
struct dirty_root *dirty; struct dirty_root *dirty;
struct btrfs_root *gang[8]; struct btrfs_root *gang[8];
struct btrfs_root *root; struct btrfs_root *root;
struct btrfs_root_item tmp_item;
int i; int i;
int ret; int ret;
int err = 0; int err = 0;
...@@ -267,13 +260,16 @@ static int add_dirty_roots(struct btrfs_trans_handle *trans, ...@@ -267,13 +260,16 @@ static int add_dirty_roots(struct btrfs_trans_handle *trans,
} }
dirty = kmalloc(sizeof(*dirty), GFP_NOFS); dirty = kmalloc(sizeof(*dirty), GFP_NOFS);
BUG_ON(!dirty); BUG_ON(!dirty);
memcpy(&dirty->snap_key, &root->root_key, dirty->root = kmalloc(sizeof(*dirty->root), GFP_NOFS);
sizeof(root->root_key)); BUG_ON(!dirty->root);
dirty->commit_root = root->commit_root;
memset(&root->root_item.drop_progress, 0,
sizeof(struct btrfs_disk_key));
root->root_item.drop_level = 0;
memcpy(dirty->root, root, sizeof(*root));
dirty->root->node = root->commit_root;
root->commit_root = NULL; root->commit_root = NULL;
dirty->root = root;
dirty->free_on_drop = 0;
memcpy(&tmp_item, &root->root_item, sizeof(tmp_item));
root->root_key.offset = root->fs_info->generation; root->root_key.offset = root->fs_info->generation;
btrfs_set_root_blocknr(&root->root_item, btrfs_set_root_blocknr(&root->root_item,
...@@ -283,19 +279,23 @@ static int add_dirty_roots(struct btrfs_trans_handle *trans, ...@@ -283,19 +279,23 @@ static int add_dirty_roots(struct btrfs_trans_handle *trans,
&root->root_item); &root->root_item);
if (err) if (err)
break; break;
refs = btrfs_root_refs(&tmp_item);
btrfs_set_root_refs(&tmp_item, refs - 1); refs = btrfs_root_refs(&dirty->root->root_item);
btrfs_set_root_refs(&dirty->root->root_item, refs - 1);
err = btrfs_update_root(trans, root->fs_info->tree_root, err = btrfs_update_root(trans, root->fs_info->tree_root,
&dirty->snap_key, &dirty->root->root_key,
&tmp_item); &dirty->root->root_item);
BUG_ON(err); BUG_ON(err);
if (refs == 1) if (refs == 1) {
list_add(&dirty->list, list); list_add(&dirty->list, list);
else } else {
WARN_ON(1);
kfree(dirty->root);
kfree(dirty); kfree(dirty);
} }
} }
}
return err; return err;
} }
...@@ -305,22 +305,35 @@ static int drop_dirty_roots(struct btrfs_root *tree_root, ...@@ -305,22 +305,35 @@ static int drop_dirty_roots(struct btrfs_root *tree_root,
struct dirty_root *dirty; struct dirty_root *dirty;
struct btrfs_trans_handle *trans; struct btrfs_trans_handle *trans;
int ret = 0; int ret = 0;
int err;
while(!list_empty(list)) { while(!list_empty(list)) {
mutex_lock(&tree_root->fs_info->fs_mutex); mutex_lock(&tree_root->fs_info->fs_mutex);
dirty = list_entry(list->next, struct dirty_root, list); dirty = list_entry(list->next, struct dirty_root, list);
list_del_init(&dirty->list); list_del_init(&dirty->list);
while(1) {
trans = btrfs_start_transaction(tree_root, 1); trans = btrfs_start_transaction(tree_root, 1);
ret = btrfs_drop_snapshot(trans, dirty->root, ret = btrfs_drop_snapshot(trans, dirty->root);
dirty->commit_root); if (ret != -EAGAIN) {
break;
}
err = btrfs_update_root(trans,
tree_root,
&dirty->root->root_key,
&dirty->root->root_item);
if (err)
ret = err;
ret = btrfs_end_transaction(trans, tree_root);
BUG_ON(ret);
}
BUG_ON(ret); BUG_ON(ret);
ret = btrfs_del_root(trans, tree_root, &dirty->snap_key); ret = btrfs_del_root(trans, tree_root, &dirty->root->root_key);
if (ret) if (ret)
break; break;
ret = btrfs_end_transaction(trans, tree_root); ret = btrfs_end_transaction(trans, tree_root);
BUG_ON(ret); BUG_ON(ret);
if (dirty->free_on_drop)
kfree(dirty->root); kfree(dirty->root);
kfree(dirty); kfree(dirty);
mutex_unlock(&tree_root->fs_info->fs_mutex); mutex_unlock(&tree_root->fs_info->fs_mutex);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment