Skip to contentSkip to conversation

Btrfs: proper metadata -ENOSPC handling

PATCH New Josef Bacik 1 participant 1 message Aug 24, 2009 Btrfs file system

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Btrfs: proper metadata -ENOSPC handling

This patch finally gives us full proper -ENOSPC handling for btrfs. Now whenever you do a btrfs_start_transaction, you must specify the number of items you are planning to add/delete/modify. The worst case number of blocks that could be modified by changing that number of items will be calculated and checked against the amount of free space in the space_info where the root you are modifying would allocate out of. If there is not enough free space, you will get an PTR_ERR(-ENOSPC) returned. Now the data ENOSPC checker does not check metadata space, it only checks data space, and this seems to speed up my fs_mark tests quite a bit.

Everytime we start a new transaction, we calculate how much space we have for metadata and store it in the transaction, so we do not go above that number of bytes. Also, we calculate how many bytes would be needed to commit the transaction, and subtract that from the maximum number of bytes allowed to be used in the transaction. This makes sure that if we start a transaction we can _always_ commit it.

There are some gotcha's related with this patch. Relocation seems to work for the most part, but you may hit problems with btrfs_drop_snapshots. The problem is not easy to hit, but its not terribly hard either. This will be fixed later by Yan, since he is already planning on re-working that code.

This patch also turns off the metadata_ratio stuff in order to make sure users can get the most disk space as possible. We only force metadata chunk allocations when we pass 80% used in the metadata space.

Also, instead of doing a

trans = btrfs_start_transaction(root, 1);
btrfs_commit_transaction(trans, root);

You will want to do

btrfs_force_transacation_commit(root, 0);

This does not do any of the free space checks that btrfs_start_transaction or btrfs_join_transaction does, since we expect to _only_ be committing the transaction. This makes sure that we use the reserved space for committing the transaction and don't end up with ENOSPC being returned to the transaction kthread.

This has been hammered on pretty well with various tests and seems to work well. Thanks,

---
 fs/btrfs/ctree.h       |    4 +-
 fs/btrfs/disk-io.c     |   13 +--
 fs/btrfs/extent-tree.c |  139 +++++++++++---------
 fs/btrfs/file.c        |   17 ++-
 fs/btrfs/inode.c       |  186 +++++++++++++++++++--------
 fs/btrfs/ioctl.c       |   14 +--
 fs/btrfs/relocation.c  |   63 +++++++--
 fs/btrfs/super.c       |    3 +
 fs/btrfs/transaction.c |  336 ++++++++++++++++++++++++++++++++++++++++++++----
 fs/btrfs/transaction.h |   27 ++++-
 fs/btrfs/volumes.c     |   17 ++-
 11 files changed, 632 insertions(+), 187 deletions(-)
fs/btrfs/ctree.h +3 -1
index db02e26..003400a 100644
@@ -672,6 +672,7 @@ struct btrfs_space_info {
 	u64 bytes_reserved;	/* total bytes the allocator has reserved for
 				   current allocations */
 	u64 bytes_readonly;	/* total bytes that are read only */
+	u64 bytes_super;	/* total bytes reserved for the super blocks */
 
 	/* delalloc accounting */
 	u64 bytes_delalloc;	/* number of bytes reserved for allocation,
@@ -744,6 +745,7 @@ struct btrfs_block_group_cache {
 	spinlock_t lock;
 	u64 pinned;
 	u64 reserved;
+	u64 bytes_super;
 	u64 flags;
 	u64 sectorsize;
 	int extents_thresh;
@@ -2012,7 +2014,7 @@ 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);
+u64 btrfs_metadata_free_space(struct btrfs_root *root);
 int btrfs_check_data_free_space(struct btrfs_root *root, struct inode *inode,
 				u64 bytes);
 void btrfs_free_reserved_data_space(struct btrfs_root *root,
fs/btrfs/disk-io.c +4 -9
index fafd6f2..dbd2f13 100644
@@ -1456,7 +1456,6 @@ static int cleaner_kthread(void *arg)
 static int transaction_kthread(void *arg)
 {
 	struct btrfs_root *root = arg;
-	struct btrfs_trans_handle *trans;
 	struct btrfs_transaction *cur;
 	unsigned long now;
 	unsigned long delay;
@@ -1485,8 +1484,7 @@ static int transaction_kthread(void *arg)
 			goto sleep;
 		}
 		mutex_unlock(&root->fs_info->trans_mutex);
-		trans = btrfs_start_transaction(root, 1);
-		ret = btrfs_commit_transaction(trans, root);
+		ret = btrfs_force_transaction_commit(root, 0);
 
 sleep:
 		wake_up_process(root->fs_info->cleaner_kthread);
@@ -1574,7 +1572,7 @@ struct btrfs_root *open_ctree(struct super_block *sb,
 	fs_info->btree_inode = new_inode(sb);
 	fs_info->btree_inode->i_ino = 1;
 	fs_info->btree_inode->i_nlink = 1;
-	fs_info->metadata_ratio = 8;
+	fs_info->metadata_ratio = 0;
 
 	fs_info->thread_pool_size = min_t(unsigned long,
 					  num_online_cpus() + 2, 8);
@@ -2285,18 +2283,15 @@ int btrfs_cleanup_fs_roots(struct btrfs_fs_info *fs_info)
 
 int btrfs_commit_super(struct btrfs_root *root)
 {
-	struct btrfs_trans_handle *trans;
 	int ret;
 
 	mutex_lock(&root->fs_info->cleaner_mutex);
 	btrfs_clean_old_snapshots(root);
 	mutex_unlock(&root->fs_info->cleaner_mutex);
-	trans = btrfs_start_transaction(root, 1);
-	ret = btrfs_commit_transaction(trans, root);
+	ret = btrfs_force_transaction_commit(root, 0);
 	BUG_ON(ret);
 	/* run commit again to drop the original snapshot */
-	trans = btrfs_start_transaction(root, 1);
-	btrfs_commit_transaction(trans, root);
+	btrfs_force_transaction_commit(root, 0);
 	ret = btrfs_write_and_wait_transaction(NULL, root);
 	BUG_ON(ret);
 
fs/btrfs/extent-tree.c +75 -64

302 lines not on this page · raw message

fs/btrfs/file.c +13 -4
index 7c3cd24..cabd0b2 100644
@@ -129,8 +129,8 @@ static noinline int dirty_and_release_pages(struct btrfs_trans_handle *trans,
 
 	lock_extent(io_tree, start_pos, end_of_last_block, GFP_NOFS);
 	trans = btrfs_join_transaction(root, 1);
-	if (!trans) {
-		err = -ENOMEM;
+	if (IS_ERR(trans)) {
+		err = PTR_ERR(trans);
 		goto out_unlock;
 	}
 	btrfs_set_trans_block_group(trans, inode);
@@ -323,6 +323,10 @@ noinline int btrfs_drop_extents(struct btrfs_trans_handle *trans,
 	while (1) {
 		recow = 0;
 		btrfs_release_path(root, path);
+		ret = btrfs_extend_transaction(trans, root, 1);
+		if (ret)
+			goto out;
+
 		ret = btrfs_lookup_file_extent(trans, root, path, inode->i_ino,
 					       search_start, -1);
 		if (ret < 0)
@@ -1080,6 +1084,10 @@ out_nolock:
 
 		if ((file->f_flags & O_SYNC) || IS_SYNC(inode)) {
 			trans = btrfs_start_transaction(root, 1);
+			if (IS_ERR(trans)) {
+				err = PTR_ERR(trans);
+				goto fail;
+			}
 			ret = btrfs_log_dentry_safe(trans, root,
 						    file->f_dentry);
 			if (ret == 0) {
@@ -1092,6 +1100,7 @@ out_nolock:
 				btrfs_commit_transaction(trans, root);
 			}
 		}
+fail:
 		if (file->f_flags & O_DIRECT) {
 			invalidate_mapping_pages(inode->i_mapping,
 			      start_pos >> PAGE_CACHE_SHIFT,
@@ -1169,8 +1178,8 @@ int btrfs_sync_file(struct file *file, struct dentry *dentry, int datasync)
 		btrfs_ioctl_trans_end(file);
 
 	trans = btrfs_start_transaction(root, 1);
-	if (!trans) {
-		ret = -ENOMEM;
+	if (IS_ERR(trans)) {
+		ret = PTR_ERR(trans);
 		goto out;
 	}
 
fs/btrfs/inode.c +133 -53

385 lines not on this page · raw message

fs/btrfs/ioctl.c +3 -11
index 9f4db84..e7acdd5 100644
@@ -240,12 +240,8 @@ static noinline int create_subvol(struct btrfs_root *root,
 	u64 index = 0;
 	unsigned long nr = 1;
 
-	ret = btrfs_check_metadata_free_space(root);
-	if (ret)
-		goto fail_commit;
-
-	trans = btrfs_start_transaction(root, 1);
-	BUG_ON(!trans);
+	trans = btrfs_start_transaction(root, 4);
+	BUG_ON(IS_ERR(trans));
 
 	ret = btrfs_find_free_objectid(trans, root->fs_info->tree_root,
 				       0, &objectid);
@@ -376,10 +372,6 @@ static int create_snapshot(struct btrfs_root *root, struct dentry *dentry,
 	if (!root->ref_cows)
 		return -EINVAL;
 
-	ret = btrfs_check_metadata_free_space(root);
-	if (ret)
-		goto fail_unlock;
-
 	pending_snapshot = kzalloc(sizeof(*pending_snapshot), GFP_NOFS);
 	if (!pending_snapshot) {
 		ret = -ENOMEM;
@@ -395,7 +387,7 @@ static int create_snapshot(struct btrfs_root *root, struct dentry *dentry,
 	pending_snapshot->name[namelen] = '\0';
 	pending_snapshot->dentry = dentry;
 	trans = btrfs_start_transaction(root, 1);
-	BUG_ON(!trans);
+	BUG_ON(IS_ERR(trans));
 	pending_snapshot->root = root;
 	list_add(&pending_snapshot->list,
 		 &trans->transaction->pending_snapshots);
fs/btrfs/relocation.c +49 -14

118 lines not on this page · raw message

fs/btrfs/super.c +3 -0
index 708ac06..cf21c1b 100644
@@ -407,6 +407,9 @@ int btrfs_sync_fs(struct super_block *sb, int wait)
 	btrfs_wait_ordered_extents(root, 0);
 
 	trans = btrfs_start_transaction(root, 1);
+	if (IS_ERR(trans))
+		return PTR_ERR(trans);
+
 	ret = btrfs_commit_transaction(trans, root);
 	sb->s_dirt = 0;
 	return ret;
fs/btrfs/transaction.c +311 -25

422 lines not on this page · raw message

fs/btrfs/transaction.h +23 -4
index 663c674..06e9ba7 100644
@@ -30,6 +30,8 @@ struct btrfs_transaction {
 	unsigned long num_writers;
 
 	unsigned long num_joined;
+	u64 bytes_reserved;
+	u64 max_bytes;
 	int in_commit;
 	int use_count;
 	int commit_done;
@@ -45,13 +47,24 @@ struct btrfs_transaction {
 
 struct btrfs_trans_handle {
 	u64 transid;
-	unsigned long blocks_reserved;
+
+	/*
+	 * keep track of how many bytes are in use by this handle.  Ideally when
+	 * this all works perfectly bytes_reserved can take a hike, but until
+	 * then its usefull in making sure we are making the right guesses for
+	 * how many bytes we plan to use for different operations
+	 */
+	u64 bytes_reserved;
+	u64 bytes_used;
 	unsigned long blocks_used;
 	struct btrfs_transaction *transaction;
 	u64 block_group;
 	u64 alloc_exclude_start;
 	u64 alloc_exclude_nr;
 	unsigned long delayed_ref_updates;
+	int use_count;
+	bool is_relocate;	/* if true, this trans is being used to
+				 * relocate chunks */
 };
 
 struct btrfs_pending_snapshot {
@@ -84,11 +97,16 @@ static inline void btrfs_set_inode_last_trans(struct btrfs_trans_handle *trans,
 int btrfs_end_transaction(struct btrfs_trans_handle *trans,
 			  struct btrfs_root *root);
 struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root,
-						   int num_blocks);
+						   int num_items);
 struct btrfs_trans_handle *btrfs_join_transaction(struct btrfs_root *root,
-						   int num_blocks);
+						   int num_items);
 struct btrfs_trans_handle *btrfs_start_ioctl_transaction(struct btrfs_root *r,
-						   int num_blocks);
+						   int num_items);
+int btrfs_try_extend_transaction(struct btrfs_trans_handle *trans,
+				 struct btrfs_root *root, int num_items);
+int btrfs_extend_transaction(struct btrfs_trans_handle *trans,
+			     struct btrfs_root *root, int num_items);
+void btrfs_trans_used_bytes(struct btrfs_trans_handle *trans, u64 num_bytes);
 int btrfs_write_and_wait_transaction(struct btrfs_trans_handle *trans,
 				     struct btrfs_root *root);
 int btrfs_commit_tree_roots(struct btrfs_trans_handle *trans,
@@ -98,6 +116,7 @@ int btrfs_add_dead_root(struct btrfs_root *root);
 int btrfs_drop_dead_root(struct btrfs_root *root);
 int btrfs_defrag_root(struct btrfs_root *root, int cacheonly);
 int btrfs_clean_old_snapshots(struct btrfs_root *root);
+int btrfs_force_transaction_commit(struct btrfs_root *root, int num_items);
 int btrfs_commit_transaction(struct btrfs_trans_handle *trans,
 			     struct btrfs_root *root);
 int btrfs_end_transaction_throttle(struct btrfs_trans_handle *trans,
fs/btrfs/volumes.c +15 -2
index 8d9c42e..b3430cc 100644
@@ -1743,10 +1743,18 @@ static int btrfs_relocate_chunk(struct btrfs_root *root,
 	ret = btrfs_relocate_block_group(extent_root, chunk_offset);
 	BUG_ON(ret);
 
-	trans = btrfs_start_transaction(root, 1);
-	BUG_ON(!trans);
+	/*
+	 * 2 - for one dev extent plus updating the device item, we'll have to
+	 * extend the transaction if there are more than one stripe
+	 * 1 - the chunk item
+	 * 1 - the block group item
+	 */
+	trans = btrfs_start_transaction(root, 4);
+	if (IS_ERR(trans))
+		return PTR_ERR(trans);
 
 	lock_chunks(root);
+	trans->is_relocate = true;
 
 	/*
 	 * step two, delete the device extents and the
@@ -1761,6 +1769,11 @@ static int btrfs_relocate_chunk(struct btrfs_root *root,
 	map = (struct map_lookup *)em->bdev;
 
 	for (i = 0; i < map->num_stripes; i++) {
+		if (i > 0) {
+			/* 2 - 1 for the dev item plus the device item */
+			ret = btrfs_extend_transaction(trans, root, 2);
+			BUG_ON(ret);
+		}
 		ret = btrfs_free_dev_extent(trans, map->stripes[i].dev,
 					    map->stripes[i].physical);
 		BUG_ON(ret);
Signature · 1 line
1.5.4.3