Skip to content

Commit

Permalink
Btrfs: Fix locking around adding new space_info
Browse files Browse the repository at this point in the history
Storage allocated to different raid levels in btrfs is tracked by
a btrfs_space_info structure, and all of the current space_infos are
collected into a list_head.

Most filesystems have 3 or 4 of these structs total, and the list is
only changed when new raid levels are added or at unmount time.

This commit adds rcu locking on the list head, and properly frees
things at unmount time.  It also clears the space_info->full flag
whenever new space is added to the FS.

The locking for the space info list goes like this:

reads: protected by rcu_read_lock()
writes: protected by the chunk_mutex

At unmount time we don't need special locking because all the readers
are gone.

Signed-off-by: Chris Mason <chris.mason@oracle.com>
  • Loading branch information
Chris Mason committed Mar 10, 2009
1 parent b9447ef commit 4184ea7
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 3 deletions.
9 changes: 9 additions & 0 deletions fs/btrfs/ctree.h
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,14 @@ struct btrfs_fs_info {
struct list_head dirty_cowonly_roots;

struct btrfs_fs_devices *fs_devices;

/*
* the space_info list is almost entirely read only. It only changes
* when we add a new raid type to the FS, and that happens
* very rarely. RCU is used to protect it.
*/
struct list_head space_info;

spinlock_t delalloc_lock;
spinlock_t new_trans_lock;
u64 delalloc_bytes;
Expand Down Expand Up @@ -1797,6 +1804,8 @@ int btrfs_cleanup_reloc_trees(struct btrfs_root *root);
int btrfs_reloc_clone_csums(struct inode *inode, u64 file_pos, u64 len);
u64 btrfs_reduce_alloc_profile(struct btrfs_root *root, u64 flags);
void btrfs_set_inode_space_info(struct btrfs_root *root, struct inode *ionde);
void btrfs_clear_space_info_full(struct btrfs_fs_info *info);

int btrfs_check_metadata_free_space(struct btrfs_root *root);
int btrfs_check_data_free_space(struct btrfs_root *root, struct inode *inode,
u64 bytes);
Expand Down
45 changes: 42 additions & 3 deletions fs/btrfs/extent-tree.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <linux/writeback.h>
#include <linux/blkdev.h>
#include <linux/sort.h>
#include <linux/rcupdate.h>
#include "compat.h"
#include "hash.h"
#include "crc32c.h"
Expand Down Expand Up @@ -330,13 +331,33 @@ static struct btrfs_space_info *__find_space_info(struct btrfs_fs_info *info,
{
struct list_head *head = &info->space_info;
struct btrfs_space_info *found;
list_for_each_entry(found, head, list) {
if (found->flags == flags)

rcu_read_lock();
list_for_each_entry_rcu(found, head, list) {
if (found->flags == flags) {
rcu_read_unlock();
return found;
}
}
rcu_read_unlock();
return NULL;
}

/*
* after adding space to the filesystem, we need to clear the full flags
* on all the space infos.
*/
void btrfs_clear_space_info_full(struct btrfs_fs_info *info)
{
struct list_head *head = &info->space_info;
struct btrfs_space_info *found;

rcu_read_lock();
list_for_each_entry_rcu(found, head, list)
found->full = 0;
rcu_read_unlock();
}

static u64 div_factor(u64 num, int factor)
{
if (factor == 10)
Expand Down Expand Up @@ -1903,7 +1924,6 @@ static int update_space_info(struct btrfs_fs_info *info, u64 flags,
if (!found)
return -ENOMEM;

list_add(&found->list, &info->space_info);
INIT_LIST_HEAD(&found->block_groups);
init_rwsem(&found->groups_sem);
spin_lock_init(&found->lock);
Expand All @@ -1917,6 +1937,7 @@ static int update_space_info(struct btrfs_fs_info *info, u64 flags,
found->full = 0;
found->force_alloc = 0;
*space_info = found;
list_add_rcu(&found->list, &info->space_info);
return 0;
}

Expand Down Expand Up @@ -6320,6 +6341,7 @@ static int find_first_block_group(struct btrfs_root *root,
int btrfs_free_block_groups(struct btrfs_fs_info *info)
{
struct btrfs_block_group_cache *block_group;
struct btrfs_space_info *space_info;
struct rb_node *n;

spin_lock(&info->block_group_cache_lock);
Expand All @@ -6341,6 +6363,23 @@ int btrfs_free_block_groups(struct btrfs_fs_info *info)
spin_lock(&info->block_group_cache_lock);
}
spin_unlock(&info->block_group_cache_lock);

/* now that all the block groups are freed, go through and
* free all the space_info structs. This is only called during
* the final stages of unmount, and so we know nobody is
* using them. We call synchronize_rcu() once before we start,
* just to be on the safe side.
*/
synchronize_rcu();

while(!list_empty(&info->space_info)) {
space_info = list_entry(info->space_info.next,
struct btrfs_space_info,
list);

list_del(&space_info->list);
kfree(space_info);
}
return 0;
}

Expand Down
2 changes: 2 additions & 0 deletions fs/btrfs/volumes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,8 @@ static int __btrfs_grow_device(struct btrfs_trans_handle *trans,
device->fs_devices->total_rw_bytes += diff;

device->total_bytes = new_size;
btrfs_clear_space_info_full(device->dev_root->fs_info);

return btrfs_update_device(trans, device);
}

Expand Down

0 comments on commit 4184ea7

Please sign in to comment.