Commit 7f9fe614 authored by Josef Bacik's avatar Josef Bacik Committed by David Sterba

btrfs: improve global reserve stealing logic

For unlink transactions and block group removal
btrfs_start_transaction_fallback_global_rsv will first try to start an
ordinary transaction and if it fails it will fall back to reserving the
required amount by stealing from the global reserve. This is problematic
because of all the same reasons we had with previous iterations of the
ENOSPC handling, thundering herd.  We get a bunch of failures all at
once, everybody tries to allocate from the global reserve, some win and
some lose, we get an ENSOPC.

Fix this behavior by introducing BTRFS_RESERVE_FLUSH_ALL_STEAL. It's
used to mark unlink reservation. To fix this we need to integrate this
logic into the normal ENOSPC infrastructure.  We still go through all of
the normal flushing work, and at the moment we begin to fail all the
tickets we try to satisfy any tickets that are allowed to steal by
stealing from the global reserve.  If this works we start the flushing
system over again just like we would with a normal ticket satisfaction.
This serializes our global reserve stealing, so we don't have the
thundering herd problem.
Reviewed-by: default avatarNikolay Borisov <nborisov@suse.com>
Tested-by: default avatarNikolay Borisov <nborisov@suse.com>
Signed-off-by: default avatarJosef Bacik <josef@toxicpanda.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent 876de781
...@@ -1175,7 +1175,7 @@ struct btrfs_trans_handle *btrfs_start_trans_remove_block_group( ...@@ -1175,7 +1175,7 @@ struct btrfs_trans_handle *btrfs_start_trans_remove_block_group(
free_extent_map(em); free_extent_map(em);
return btrfs_start_transaction_fallback_global_rsv(fs_info->extent_root, return btrfs_start_transaction_fallback_global_rsv(fs_info->extent_root,
num_items, 1); num_items);
} }
/* /*
......
...@@ -2512,6 +2512,7 @@ enum btrfs_reserve_flush_enum { ...@@ -2512,6 +2512,7 @@ enum btrfs_reserve_flush_enum {
BTRFS_RESERVE_FLUSH_LIMIT, BTRFS_RESERVE_FLUSH_LIMIT,
BTRFS_RESERVE_FLUSH_EVICT, BTRFS_RESERVE_FLUSH_EVICT,
BTRFS_RESERVE_FLUSH_ALL, BTRFS_RESERVE_FLUSH_ALL,
BTRFS_RESERVE_FLUSH_ALL_STEAL,
}; };
enum btrfs_flush_state { enum btrfs_flush_state {
......
...@@ -3618,7 +3618,7 @@ static struct btrfs_trans_handle *__unlink_start_trans(struct inode *dir) ...@@ -3618,7 +3618,7 @@ static struct btrfs_trans_handle *__unlink_start_trans(struct inode *dir)
* 1 for the inode ref * 1 for the inode ref
* 1 for the inode * 1 for the inode
*/ */
return btrfs_start_transaction_fallback_global_rsv(root, 5, 5); return btrfs_start_transaction_fallback_global_rsv(root, 5);
} }
static int btrfs_unlink(struct inode *dir, struct dentry *dentry) static int btrfs_unlink(struct inode *dir, struct dentry *dentry)
......
...@@ -856,6 +856,34 @@ static inline int need_do_async_reclaim(struct btrfs_fs_info *fs_info, ...@@ -856,6 +856,34 @@ static inline int need_do_async_reclaim(struct btrfs_fs_info *fs_info,
!test_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state)); !test_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state));
} }
static bool steal_from_global_rsv(struct btrfs_fs_info *fs_info,
struct btrfs_space_info *space_info,
struct reserve_ticket *ticket)
{
struct btrfs_block_rsv *global_rsv = &fs_info->global_block_rsv;
u64 min_bytes;
if (global_rsv->space_info != space_info)
return false;
spin_lock(&global_rsv->lock);
min_bytes = div_factor(global_rsv->size, 5);
if (global_rsv->reserved < min_bytes + ticket->bytes) {
spin_unlock(&global_rsv->lock);
return false;
}
global_rsv->reserved -= ticket->bytes;
ticket->bytes = 0;
list_del_init(&ticket->list);
wake_up(&ticket->wait);
space_info->tickets_id++;
if (global_rsv->reserved < global_rsv->size)
global_rsv->full = 0;
spin_unlock(&global_rsv->lock);
return true;
}
/* /*
* maybe_fail_all_tickets - we've exhausted our flushing, start failing tickets * maybe_fail_all_tickets - we've exhausted our flushing, start failing tickets
* @fs_info - fs_info for this fs * @fs_info - fs_info for this fs
...@@ -888,6 +916,10 @@ static bool maybe_fail_all_tickets(struct btrfs_fs_info *fs_info, ...@@ -888,6 +916,10 @@ static bool maybe_fail_all_tickets(struct btrfs_fs_info *fs_info,
ticket = list_first_entry(&space_info->tickets, ticket = list_first_entry(&space_info->tickets,
struct reserve_ticket, list); struct reserve_ticket, list);
if (ticket->steal &&
steal_from_global_rsv(fs_info, space_info, ticket))
return true;
/* /*
* may_commit_transaction will avoid committing the transaction * may_commit_transaction will avoid committing the transaction
* if it doesn't feel like the space reclaimed by the commit * if it doesn't feel like the space reclaimed by the commit
...@@ -1104,6 +1136,7 @@ static int handle_reserve_ticket(struct btrfs_fs_info *fs_info, ...@@ -1104,6 +1136,7 @@ static int handle_reserve_ticket(struct btrfs_fs_info *fs_info,
switch (flush) { switch (flush) {
case BTRFS_RESERVE_FLUSH_ALL: case BTRFS_RESERVE_FLUSH_ALL:
case BTRFS_RESERVE_FLUSH_ALL_STEAL:
wait_reserve_ticket(fs_info, space_info, ticket); wait_reserve_ticket(fs_info, space_info, ticket);
break; break;
case BTRFS_RESERVE_FLUSH_LIMIT: case BTRFS_RESERVE_FLUSH_LIMIT:
...@@ -1203,7 +1236,9 @@ static int __reserve_metadata_bytes(struct btrfs_fs_info *fs_info, ...@@ -1203,7 +1236,9 @@ static int __reserve_metadata_bytes(struct btrfs_fs_info *fs_info,
ticket.error = 0; ticket.error = 0;
space_info->reclaim_size += ticket.bytes; space_info->reclaim_size += ticket.bytes;
init_waitqueue_head(&ticket.wait); init_waitqueue_head(&ticket.wait);
if (flush == BTRFS_RESERVE_FLUSH_ALL) { ticket.steal = (flush == BTRFS_RESERVE_FLUSH_ALL_STEAL);
if (flush == BTRFS_RESERVE_FLUSH_ALL ||
flush == BTRFS_RESERVE_FLUSH_ALL_STEAL) {
list_add_tail(&ticket.list, &space_info->tickets); list_add_tail(&ticket.list, &space_info->tickets);
if (!space_info->flush) { if (!space_info->flush) {
space_info->flush = 1; space_info->flush = 1;
......
...@@ -78,6 +78,7 @@ struct btrfs_space_info { ...@@ -78,6 +78,7 @@ struct btrfs_space_info {
struct reserve_ticket { struct reserve_ticket {
u64 bytes; u64 bytes;
int error; int error;
bool steal;
struct list_head list; struct list_head list;
wait_queue_head_t wait; wait_queue_head_t wait;
}; };
......
...@@ -563,7 +563,8 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, ...@@ -563,7 +563,8 @@ start_transaction(struct btrfs_root *root, unsigned int num_items,
* refill that amount for whatever is missing in the reserve. * refill that amount for whatever is missing in the reserve.
*/ */
num_bytes = btrfs_calc_insert_metadata_size(fs_info, num_items); num_bytes = btrfs_calc_insert_metadata_size(fs_info, num_items);
if (delayed_refs_rsv->full == 0) { if (flush == BTRFS_RESERVE_FLUSH_ALL &&
delayed_refs_rsv->full == 0) {
delayed_refs_bytes = num_bytes; delayed_refs_bytes = num_bytes;
num_bytes <<= 1; num_bytes <<= 1;
} }
...@@ -699,43 +700,10 @@ struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root, ...@@ -699,43 +700,10 @@ struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root,
struct btrfs_trans_handle *btrfs_start_transaction_fallback_global_rsv( struct btrfs_trans_handle *btrfs_start_transaction_fallback_global_rsv(
struct btrfs_root *root, struct btrfs_root *root,
unsigned int num_items, unsigned int num_items)
int min_factor)
{ {
struct btrfs_fs_info *fs_info = root->fs_info; return start_transaction(root, num_items, TRANS_START,
struct btrfs_trans_handle *trans; BTRFS_RESERVE_FLUSH_ALL_STEAL, false);
u64 num_bytes;
int ret;
/*
* We have two callers: unlink and block group removal. The
* former should succeed even if we will temporarily exceed
* quota and the latter operates on the extent root so
* qgroup enforcement is ignored anyway.
*/
trans = start_transaction(root, num_items, TRANS_START,
BTRFS_RESERVE_FLUSH_ALL, false);
if (!IS_ERR(trans) || PTR_ERR(trans) != -ENOSPC)
return trans;
trans = btrfs_start_transaction(root, 0);
if (IS_ERR(trans))
return trans;
num_bytes = btrfs_calc_insert_metadata_size(fs_info, num_items);
ret = btrfs_cond_migrate_bytes(fs_info, &fs_info->trans_block_rsv,
num_bytes, min_factor);
if (ret) {
btrfs_end_transaction(trans);
return ERR_PTR(ret);
}
trans->block_rsv = &fs_info->trans_block_rsv;
trans->bytes_reserved = num_bytes;
trace_btrfs_space_reservation(fs_info, "transaction",
trans->transid, num_bytes, 1);
return trans;
} }
struct btrfs_trans_handle *btrfs_join_transaction(struct btrfs_root *root) struct btrfs_trans_handle *btrfs_join_transaction(struct btrfs_root *root)
......
...@@ -193,8 +193,7 @@ struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root, ...@@ -193,8 +193,7 @@ struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root,
unsigned int num_items); unsigned int num_items);
struct btrfs_trans_handle *btrfs_start_transaction_fallback_global_rsv( struct btrfs_trans_handle *btrfs_start_transaction_fallback_global_rsv(
struct btrfs_root *root, struct btrfs_root *root,
unsigned int num_items, unsigned int num_items);
int min_factor);
struct btrfs_trans_handle *btrfs_join_transaction(struct btrfs_root *root); struct btrfs_trans_handle *btrfs_join_transaction(struct btrfs_root *root);
struct btrfs_trans_handle *btrfs_join_transaction_spacecache(struct btrfs_root *root); struct btrfs_trans_handle *btrfs_join_transaction_spacecache(struct btrfs_root *root);
struct btrfs_trans_handle *btrfs_join_transaction_nostart(struct btrfs_root *root); struct btrfs_trans_handle *btrfs_join_transaction_nostart(struct btrfs_root *root);
......
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