Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c79a1751 authored by Liu Bo's avatar Liu Bo Committed by David Sterba
Browse files

Btrfs: fix memory leak of block group cache



While processing delayed refs, we may update block group's statistics
and attach it to cur_trans->dirty_bgs, and later writing dirty block
groups will process the list, which happens during
btrfs_commit_transaction().

For whatever reason, the transaction is aborted and dirty_bgs
is not processed in cleanup_transaction(), we end up with memory leak
of these dirty block group cache.

Since btrfs_start_dirty_block_groups() doesn't make it go to the commit
critical section, this also adds the cleanup work inside it.

Signed-off-by: default avatarLiu Bo <bo.li.liu@oracle.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent 08895a8b
Loading
Loading
Loading
Loading
+71 −0
Original line number Diff line number Diff line
@@ -4475,9 +4475,80 @@ static int btrfs_destroy_pinned_extent(struct btrfs_root *root,
	return 0;
}

static void btrfs_cleanup_bg_io(struct btrfs_block_group_cache *cache)
{
	struct inode *inode;

	inode = cache->io_ctl.inode;
	if (inode) {
		invalidate_inode_pages2(inode->i_mapping);
		BTRFS_I(inode)->generation = 0;
		cache->io_ctl.inode = NULL;
		iput(inode);
	}
	btrfs_put_block_group(cache);
}

void btrfs_cleanup_dirty_bgs(struct btrfs_transaction *cur_trans,
			     struct btrfs_root *root)
{
	struct btrfs_block_group_cache *cache;

	spin_lock(&cur_trans->dirty_bgs_lock);
	while (!list_empty(&cur_trans->dirty_bgs)) {
		cache = list_first_entry(&cur_trans->dirty_bgs,
					 struct btrfs_block_group_cache,
					 dirty_list);
		if (!cache) {
			btrfs_err(root->fs_info,
				  "orphan block group dirty_bgs list");
			spin_unlock(&cur_trans->dirty_bgs_lock);
			return;
		}

		if (!list_empty(&cache->io_list)) {
			spin_unlock(&cur_trans->dirty_bgs_lock);
			list_del_init(&cache->io_list);
			btrfs_cleanup_bg_io(cache);
			spin_lock(&cur_trans->dirty_bgs_lock);
		}

		list_del_init(&cache->dirty_list);
		spin_lock(&cache->lock);
		cache->disk_cache_state = BTRFS_DC_ERROR;
		spin_unlock(&cache->lock);

		spin_unlock(&cur_trans->dirty_bgs_lock);
		btrfs_put_block_group(cache);
		spin_lock(&cur_trans->dirty_bgs_lock);
	}
	spin_unlock(&cur_trans->dirty_bgs_lock);

	while (!list_empty(&cur_trans->io_bgs)) {
		cache = list_first_entry(&cur_trans->io_bgs,
					 struct btrfs_block_group_cache,
					 io_list);
		if (!cache) {
			btrfs_err(root->fs_info,
				  "orphan block group on io_bgs list");
			return;
		}

		list_del_init(&cache->io_list);
		spin_lock(&cache->lock);
		cache->disk_cache_state = BTRFS_DC_ERROR;
		spin_unlock(&cache->lock);
		btrfs_cleanup_bg_io(cache);
	}
}

void btrfs_cleanup_one_transaction(struct btrfs_transaction *cur_trans,
				   struct btrfs_root *root)
{
	btrfs_cleanup_dirty_bgs(cur_trans, root);
	ASSERT(list_empty(&cur_trans->dirty_bgs));
	ASSERT(list_empty(&cur_trans->io_bgs));

	btrfs_destroy_delayed_refs(cur_trans, root);

	cur_trans->state = TRANS_STATE_COMMIT_START;
+2 −0
Original line number Diff line number Diff line
@@ -136,6 +136,8 @@ int btrfs_init_log_root_tree(struct btrfs_trans_handle *trans,
			     struct btrfs_fs_info *fs_info);
int btrfs_add_log_tree(struct btrfs_trans_handle *trans,
		       struct btrfs_root *root);
void btrfs_cleanup_dirty_bgs(struct btrfs_transaction *trans,
			     struct btrfs_root *root);
void btrfs_cleanup_one_transaction(struct btrfs_transaction *trans,
				  struct btrfs_root *root);
struct btrfs_root *btrfs_create_tree(struct btrfs_trans_handle *trans,
+2 −0
Original line number Diff line number Diff line
@@ -3694,6 +3694,8 @@ int btrfs_start_dirty_block_groups(struct btrfs_trans_handle *trans,
			goto again;
		}
		spin_unlock(&cur_trans->dirty_bgs_lock);
	} else if (ret < 0) {
		btrfs_cleanup_dirty_bgs(cur_trans, root);
	}

	btrfs_free_path(path);