2019-12-14 08:22:14 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
|
|
|
|
#include <linux/jiffies.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/ktime.h>
|
|
|
|
#include <linux/list.h>
|
2020-01-03 05:26:36 +08:00
|
|
|
#include <linux/math64.h>
|
2019-12-14 08:22:14 +08:00
|
|
|
#include <linux/sizes.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include "ctree.h"
|
|
|
|
#include "block-group.h"
|
|
|
|
#include "discard.h"
|
|
|
|
#include "free-space-cache.h"
|
|
|
|
|
2020-01-03 05:26:42 +08:00
|
|
|
/*
|
|
|
|
* This contains the logic to handle async discard.
|
|
|
|
*
|
|
|
|
* Async discard manages trimming of free space outside of transaction commit.
|
|
|
|
* Discarding is done by managing the block_groups on a LRU list based on free
|
|
|
|
* space recency. Two passes are used to first prioritize discarding extents
|
|
|
|
* and then allow for trimming in the bitmap the best opportunity to coalesce.
|
|
|
|
* The block_groups are maintained on multiple lists to allow for multiple
|
|
|
|
* passes with different discard filter requirements. A delayed work item is
|
|
|
|
* used to manage discarding with timeout determined by a max of the delay
|
|
|
|
* incurred by the iops rate limit, the byte rate limit, and the max delay of
|
|
|
|
* BTRFS_DISCARD_MAX_DELAY.
|
|
|
|
*
|
|
|
|
* Note, this only keeps track of block_groups that are explicitly for data.
|
|
|
|
* Mixed block_groups are not supported.
|
|
|
|
*
|
|
|
|
* The first list is special to manage discarding of fully free block groups.
|
|
|
|
* This is necessary because we issue a final trim for a full free block group
|
|
|
|
* after forgetting it. When a block group becomes unused, instead of directly
|
|
|
|
* being added to the unused_bgs list, we add it to this first list. Then
|
|
|
|
* from there, if it becomes fully discarded, we place it onto the unused_bgs
|
|
|
|
* list.
|
|
|
|
*
|
|
|
|
* The in-memory free space cache serves as the backing state for discard.
|
|
|
|
* Consequently this means there is no persistence. We opt to load all the
|
|
|
|
* block groups in as not discarded, so the mount case degenerates to the
|
|
|
|
* crashing case.
|
|
|
|
*
|
|
|
|
* As the free space cache uses bitmaps, there exists a tradeoff between
|
|
|
|
* ease/efficiency for find_free_extent() and the accuracy of discard state.
|
|
|
|
* Here we opt to let untrimmed regions merge with everything while only letting
|
|
|
|
* trimmed regions merge with other trimmed regions. This can cause
|
|
|
|
* overtrimming, but the coalescing benefit seems to be worth it. Additionally,
|
|
|
|
* bitmap state is tracked as a whole. If we're able to fully trim a bitmap,
|
|
|
|
* the trimmed flag is set on the bitmap. Otherwise, if an allocation comes in,
|
|
|
|
* this resets the state and we will retry trimming the whole bitmap. This is a
|
|
|
|
* tradeoff between discard state accuracy and the cost of accounting.
|
|
|
|
*/
|
|
|
|
|
2019-12-14 08:22:14 +08:00
|
|
|
/* This is an initial delay to give some chance for block reuse */
|
|
|
|
#define BTRFS_DISCARD_DELAY (120ULL * NSEC_PER_SEC)
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
#define BTRFS_DISCARD_UNUSED_DELAY (10ULL * NSEC_PER_SEC)
|
2019-12-14 08:22:14 +08:00
|
|
|
|
2020-01-03 05:26:35 +08:00
|
|
|
/* Target completion latency of discarding all discardable extents */
|
|
|
|
#define BTRFS_DISCARD_TARGET_MSEC (6 * 60 * 60UL * MSEC_PER_SEC)
|
|
|
|
#define BTRFS_DISCARD_MIN_DELAY_MSEC (1UL)
|
|
|
|
#define BTRFS_DISCARD_MAX_DELAY_MSEC (1000UL)
|
|
|
|
#define BTRFS_DISCARD_MAX_IOPS (10U)
|
|
|
|
|
2020-01-03 05:26:39 +08:00
|
|
|
/* Montonically decreasing minimum length filters after index 0 */
|
|
|
|
static int discard_minlen[BTRFS_NR_DISCARD_LISTS] = {
|
|
|
|
0,
|
|
|
|
BTRFS_ASYNC_DISCARD_MAX_FILTER,
|
|
|
|
BTRFS_ASYNC_DISCARD_MIN_FILTER
|
|
|
|
};
|
|
|
|
|
2019-12-14 08:22:14 +08:00
|
|
|
static struct list_head *get_discard_list(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
struct btrfs_block_group *block_group)
|
|
|
|
{
|
|
|
|
return &discard_ctl->discard_list[block_group->discard_index];
|
|
|
|
}
|
|
|
|
|
2019-12-14 08:22:16 +08:00
|
|
|
static void __add_to_discard_list(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
struct btrfs_block_group *block_group)
|
2019-12-14 08:22:14 +08:00
|
|
|
{
|
2019-12-14 08:22:16 +08:00
|
|
|
if (!btrfs_run_discard_work(discard_ctl))
|
2019-12-14 08:22:14 +08:00
|
|
|
return;
|
|
|
|
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
if (list_empty(&block_group->discard_list) ||
|
|
|
|
block_group->discard_index == BTRFS_DISCARD_INDEX_UNUSED) {
|
|
|
|
if (block_group->discard_index == BTRFS_DISCARD_INDEX_UNUSED)
|
|
|
|
block_group->discard_index = BTRFS_DISCARD_INDEX_START;
|
2019-12-14 08:22:14 +08:00
|
|
|
block_group->discard_eligible_time = (ktime_get_ns() +
|
|
|
|
BTRFS_DISCARD_DELAY);
|
2019-12-14 08:22:16 +08:00
|
|
|
block_group->discard_state = BTRFS_DISCARD_RESET_CURSOR;
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
}
|
2019-12-14 08:22:14 +08:00
|
|
|
|
|
|
|
list_move_tail(&block_group->discard_list,
|
|
|
|
get_discard_list(discard_ctl, block_group));
|
2019-12-14 08:22:16 +08:00
|
|
|
}
|
2019-12-14 08:22:14 +08:00
|
|
|
|
2019-12-14 08:22:16 +08:00
|
|
|
static void add_to_discard_list(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
struct btrfs_block_group *block_group)
|
|
|
|
{
|
2020-01-03 05:26:40 +08:00
|
|
|
if (!btrfs_is_block_group_data_only(block_group))
|
|
|
|
return;
|
|
|
|
|
2019-12-14 08:22:16 +08:00
|
|
|
spin_lock(&discard_ctl->lock);
|
|
|
|
__add_to_discard_list(discard_ctl, block_group);
|
2019-12-14 08:22:14 +08:00
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
}
|
|
|
|
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
static void add_to_discard_unused_list(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
struct btrfs_block_group *block_group)
|
|
|
|
{
|
|
|
|
spin_lock(&discard_ctl->lock);
|
|
|
|
|
|
|
|
if (!btrfs_run_discard_work(discard_ctl)) {
|
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_del_init(&block_group->discard_list);
|
|
|
|
|
|
|
|
block_group->discard_index = BTRFS_DISCARD_INDEX_UNUSED;
|
|
|
|
block_group->discard_eligible_time = (ktime_get_ns() +
|
|
|
|
BTRFS_DISCARD_UNUSED_DELAY);
|
2019-12-14 08:22:16 +08:00
|
|
|
block_group->discard_state = BTRFS_DISCARD_RESET_CURSOR;
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
list_add_tail(&block_group->discard_list,
|
|
|
|
&discard_ctl->discard_list[BTRFS_DISCARD_INDEX_UNUSED]);
|
|
|
|
|
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
}
|
|
|
|
|
2019-12-14 08:22:14 +08:00
|
|
|
static bool remove_from_discard_list(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
struct btrfs_block_group *block_group)
|
|
|
|
{
|
|
|
|
bool running = false;
|
|
|
|
|
|
|
|
spin_lock(&discard_ctl->lock);
|
|
|
|
|
|
|
|
if (block_group == discard_ctl->block_group) {
|
|
|
|
running = true;
|
|
|
|
discard_ctl->block_group = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
block_group->discard_eligible_time = 0;
|
|
|
|
list_del_init(&block_group->discard_list);
|
|
|
|
|
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
|
|
|
|
return running;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* find_next_block_group - find block_group that's up next for discarding
|
|
|
|
* @discard_ctl: discard control
|
|
|
|
* @now: current time
|
|
|
|
*
|
|
|
|
* Iterate over the discard lists to find the next block_group up for
|
|
|
|
* discarding checking the discard_eligible_time of block_group.
|
|
|
|
*/
|
|
|
|
static struct btrfs_block_group *find_next_block_group(
|
|
|
|
struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
u64 now)
|
|
|
|
{
|
|
|
|
struct btrfs_block_group *ret_block_group = NULL, *block_group;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < BTRFS_NR_DISCARD_LISTS; i++) {
|
|
|
|
struct list_head *discard_list = &discard_ctl->discard_list[i];
|
|
|
|
|
|
|
|
if (!list_empty(discard_list)) {
|
|
|
|
block_group = list_first_entry(discard_list,
|
|
|
|
struct btrfs_block_group,
|
|
|
|
discard_list);
|
|
|
|
|
|
|
|
if (!ret_block_group)
|
|
|
|
ret_block_group = block_group;
|
|
|
|
|
|
|
|
if (ret_block_group->discard_eligible_time < now)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (ret_block_group->discard_eligible_time >
|
|
|
|
block_group->discard_eligible_time)
|
|
|
|
ret_block_group = block_group;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret_block_group;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-01-22 17:57:57 +08:00
|
|
|
* Wrap find_next_block_group()
|
|
|
|
*
|
|
|
|
* @discard_ctl: discard control
|
2019-12-14 08:22:16 +08:00
|
|
|
* @discard_state: the discard_state of the block_group after state management
|
2020-01-03 05:26:39 +08:00
|
|
|
* @discard_index: the discard_index of the block_group after state management
|
2021-01-22 17:57:57 +08:00
|
|
|
* @now: time when discard was invoked, in ns
|
2019-12-14 08:22:14 +08:00
|
|
|
*
|
|
|
|
* This wraps find_next_block_group() and sets the block_group to be in use.
|
2019-12-14 08:22:16 +08:00
|
|
|
* discard_state's control flow is managed here. Variables related to
|
2020-01-03 05:26:39 +08:00
|
|
|
* discard_state are reset here as needed (eg discard_cursor). @discard_state
|
|
|
|
* and @discard_index are remembered as it may change while we're discarding,
|
|
|
|
* but we want the discard to execute in the context determined here.
|
2019-12-14 08:22:14 +08:00
|
|
|
*/
|
|
|
|
static struct btrfs_block_group *peek_discard_list(
|
2019-12-14 08:22:16 +08:00
|
|
|
struct btrfs_discard_ctl *discard_ctl,
|
2020-01-03 05:26:39 +08:00
|
|
|
enum btrfs_discard_state *discard_state,
|
2020-12-06 23:56:20 +08:00
|
|
|
int *discard_index, u64 now)
|
2019-12-14 08:22:14 +08:00
|
|
|
{
|
|
|
|
struct btrfs_block_group *block_group;
|
|
|
|
|
|
|
|
spin_lock(&discard_ctl->lock);
|
2019-12-14 08:22:16 +08:00
|
|
|
again:
|
2019-12-14 08:22:14 +08:00
|
|
|
block_group = find_next_block_group(discard_ctl, now);
|
|
|
|
|
2020-12-06 23:56:20 +08:00
|
|
|
if (block_group && now >= block_group->discard_eligible_time) {
|
2019-12-14 08:22:16 +08:00
|
|
|
if (block_group->discard_index == BTRFS_DISCARD_INDEX_UNUSED &&
|
|
|
|
block_group->used != 0) {
|
2020-01-03 05:26:40 +08:00
|
|
|
if (btrfs_is_block_group_data_only(block_group))
|
|
|
|
__add_to_discard_list(discard_ctl, block_group);
|
|
|
|
else
|
|
|
|
list_del_init(&block_group->discard_list);
|
2019-12-14 08:22:16 +08:00
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
if (block_group->discard_state == BTRFS_DISCARD_RESET_CURSOR) {
|
|
|
|
block_group->discard_cursor = block_group->start;
|
|
|
|
block_group->discard_state = BTRFS_DISCARD_EXTENTS;
|
|
|
|
}
|
|
|
|
discard_ctl->block_group = block_group;
|
2020-12-06 23:56:20 +08:00
|
|
|
}
|
|
|
|
if (block_group) {
|
2019-12-14 08:22:16 +08:00
|
|
|
*discard_state = block_group->discard_state;
|
2020-01-03 05:26:39 +08:00
|
|
|
*discard_index = block_group->discard_index;
|
2019-12-14 08:22:16 +08:00
|
|
|
}
|
2019-12-14 08:22:14 +08:00
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
|
|
|
|
return block_group;
|
|
|
|
}
|
|
|
|
|
2020-01-03 05:26:39 +08:00
|
|
|
/**
|
|
|
|
* btrfs_discard_check_filter - updates a block groups filters
|
|
|
|
* @block_group: block group of interest
|
|
|
|
* @bytes: recently freed region size after coalescing
|
|
|
|
*
|
|
|
|
* Async discard maintains multiple lists with progressively smaller filters
|
|
|
|
* to prioritize discarding based on size. Should a free space that matches
|
|
|
|
* a larger filter be returned to the free_space_cache, prioritize that discard
|
|
|
|
* by moving @block_group to the proper filter.
|
|
|
|
*/
|
|
|
|
void btrfs_discard_check_filter(struct btrfs_block_group *block_group,
|
|
|
|
u64 bytes)
|
|
|
|
{
|
|
|
|
struct btrfs_discard_ctl *discard_ctl;
|
|
|
|
|
|
|
|
if (!block_group ||
|
|
|
|
!btrfs_test_opt(block_group->fs_info, DISCARD_ASYNC))
|
|
|
|
return;
|
|
|
|
|
|
|
|
discard_ctl = &block_group->fs_info->discard_ctl;
|
|
|
|
|
|
|
|
if (block_group->discard_index > BTRFS_DISCARD_INDEX_START &&
|
|
|
|
bytes >= discard_minlen[block_group->discard_index - 1]) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
remove_from_discard_list(discard_ctl, block_group);
|
|
|
|
|
|
|
|
for (i = BTRFS_DISCARD_INDEX_START; i < BTRFS_NR_DISCARD_LISTS;
|
|
|
|
i++) {
|
|
|
|
if (bytes >= discard_minlen[i]) {
|
|
|
|
block_group->discard_index = i;
|
|
|
|
add_to_discard_list(discard_ctl, block_group);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* btrfs_update_discard_index - moves a block group along the discard lists
|
|
|
|
* @discard_ctl: discard control
|
|
|
|
* @block_group: block_group of interest
|
|
|
|
*
|
|
|
|
* Increment @block_group's discard_index. If it falls of the list, let it be.
|
|
|
|
* Otherwise add it back to the appropriate list.
|
|
|
|
*/
|
|
|
|
static void btrfs_update_discard_index(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
struct btrfs_block_group *block_group)
|
|
|
|
{
|
|
|
|
block_group->discard_index++;
|
|
|
|
if (block_group->discard_index == BTRFS_NR_DISCARD_LISTS) {
|
|
|
|
block_group->discard_index = 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
add_to_discard_list(discard_ctl, block_group);
|
|
|
|
}
|
|
|
|
|
2019-12-14 08:22:14 +08:00
|
|
|
/**
|
|
|
|
* btrfs_discard_cancel_work - remove a block_group from the discard lists
|
|
|
|
* @discard_ctl: discard control
|
|
|
|
* @block_group: block_group of interest
|
|
|
|
*
|
|
|
|
* This removes @block_group from the discard lists. If necessary, it waits on
|
|
|
|
* the current work and then reschedules the delayed work.
|
|
|
|
*/
|
|
|
|
void btrfs_discard_cancel_work(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
struct btrfs_block_group *block_group)
|
|
|
|
{
|
|
|
|
if (remove_from_discard_list(discard_ctl, block_group)) {
|
|
|
|
cancel_delayed_work_sync(&discard_ctl->work);
|
|
|
|
btrfs_discard_schedule_work(discard_ctl, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* btrfs_discard_queue_work - handles queuing the block_groups
|
|
|
|
* @discard_ctl: discard control
|
|
|
|
* @block_group: block_group of interest
|
|
|
|
*
|
|
|
|
* This maintains the LRU order of the discard lists.
|
|
|
|
*/
|
|
|
|
void btrfs_discard_queue_work(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
struct btrfs_block_group *block_group)
|
|
|
|
{
|
|
|
|
if (!block_group || !btrfs_test_opt(block_group->fs_info, DISCARD_ASYNC))
|
|
|
|
return;
|
|
|
|
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
if (block_group->used == 0)
|
|
|
|
add_to_discard_unused_list(discard_ctl, block_group);
|
|
|
|
else
|
|
|
|
add_to_discard_list(discard_ctl, block_group);
|
2019-12-14 08:22:14 +08:00
|
|
|
|
|
|
|
if (!delayed_work_pending(&discard_ctl->work))
|
|
|
|
btrfs_discard_schedule_work(discard_ctl, false);
|
|
|
|
}
|
|
|
|
|
2020-12-06 23:56:22 +08:00
|
|
|
static void __btrfs_discard_schedule_work(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
u64 now, bool override)
|
2019-12-14 08:22:14 +08:00
|
|
|
{
|
|
|
|
struct btrfs_block_group *block_group;
|
|
|
|
|
|
|
|
if (!btrfs_run_discard_work(discard_ctl))
|
2020-12-06 23:56:22 +08:00
|
|
|
return;
|
2019-12-14 08:22:14 +08:00
|
|
|
if (!override && delayed_work_pending(&discard_ctl->work))
|
2020-12-06 23:56:22 +08:00
|
|
|
return;
|
2019-12-14 08:22:14 +08:00
|
|
|
|
|
|
|
block_group = find_next_block_group(discard_ctl, now);
|
|
|
|
if (block_group) {
|
2020-11-04 17:45:52 +08:00
|
|
|
u64 delay = discard_ctl->delay_ms * NSEC_PER_MSEC;
|
2020-01-03 05:26:36 +08:00
|
|
|
u32 kbps_limit = READ_ONCE(discard_ctl->kbps_limit);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A single delayed workqueue item is responsible for
|
|
|
|
* discarding, so we can manage the bytes rate limit by keeping
|
|
|
|
* track of the previous discard.
|
|
|
|
*/
|
|
|
|
if (kbps_limit && discard_ctl->prev_discard) {
|
|
|
|
u64 bps_limit = ((u64)kbps_limit) * SZ_1K;
|
|
|
|
u64 bps_delay = div64_u64(discard_ctl->prev_discard *
|
2020-11-04 17:45:52 +08:00
|
|
|
NSEC_PER_SEC, bps_limit);
|
2020-01-03 05:26:36 +08:00
|
|
|
|
2020-11-04 17:45:52 +08:00
|
|
|
delay = max(delay, bps_delay);
|
2020-01-03 05:26:36 +08:00
|
|
|
}
|
2020-01-03 05:26:35 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* This timeout is to hopefully prevent immediate discarding
|
|
|
|
* in a recently allocated block group.
|
|
|
|
*/
|
|
|
|
if (now < block_group->discard_eligible_time) {
|
|
|
|
u64 bg_timeout = block_group->discard_eligible_time - now;
|
2019-12-14 08:22:14 +08:00
|
|
|
|
2020-11-04 17:45:52 +08:00
|
|
|
delay = max(delay, bg_timeout);
|
2020-01-03 05:26:35 +08:00
|
|
|
}
|
2019-12-14 08:22:14 +08:00
|
|
|
|
2020-11-04 17:45:53 +08:00
|
|
|
if (override && discard_ctl->prev_discard) {
|
|
|
|
u64 elapsed = now - discard_ctl->prev_discard_time;
|
|
|
|
|
|
|
|
if (delay > elapsed)
|
|
|
|
delay -= elapsed;
|
|
|
|
else
|
|
|
|
delay = 0;
|
|
|
|
}
|
|
|
|
|
2019-12-14 08:22:14 +08:00
|
|
|
mod_delayed_work(discard_ctl->discard_workers,
|
2020-11-04 17:45:52 +08:00
|
|
|
&discard_ctl->work, nsecs_to_jiffies(delay));
|
2019-12-14 08:22:14 +08:00
|
|
|
}
|
2020-12-06 23:56:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* btrfs_discard_schedule_work - responsible for scheduling the discard work
|
|
|
|
* @discard_ctl: discard control
|
|
|
|
* @override: override the current timer
|
|
|
|
*
|
|
|
|
* Discards are issued by a delayed workqueue item. @override is used to
|
|
|
|
* update the current delay as the baseline delay interval is reevaluated on
|
|
|
|
* transaction commit. This is also maxed with any other rate limit.
|
|
|
|
*/
|
|
|
|
void btrfs_discard_schedule_work(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
bool override)
|
|
|
|
{
|
|
|
|
const u64 now = ktime_get_ns();
|
|
|
|
|
|
|
|
spin_lock(&discard_ctl->lock);
|
|
|
|
__btrfs_discard_schedule_work(discard_ctl, now, override);
|
2019-12-14 08:22:14 +08:00
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
}
|
|
|
|
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
/**
|
|
|
|
* btrfs_finish_discard_pass - determine next step of a block_group
|
|
|
|
* @discard_ctl: discard control
|
|
|
|
* @block_group: block_group of interest
|
|
|
|
*
|
|
|
|
* This determines the next step for a block group after it's finished going
|
|
|
|
* through a pass on a discard list. If it is unused and fully trimmed, we can
|
|
|
|
* mark it unused and send it to the unused_bgs path. Otherwise, pass it onto
|
|
|
|
* the appropriate filter list or let it fall off.
|
|
|
|
*/
|
|
|
|
static void btrfs_finish_discard_pass(struct btrfs_discard_ctl *discard_ctl,
|
|
|
|
struct btrfs_block_group *block_group)
|
|
|
|
{
|
|
|
|
remove_from_discard_list(discard_ctl, block_group);
|
|
|
|
|
|
|
|
if (block_group->used == 0) {
|
|
|
|
if (btrfs_is_free_space_trimmed(block_group))
|
|
|
|
btrfs_mark_bg_unused(block_group);
|
|
|
|
else
|
|
|
|
add_to_discard_unused_list(discard_ctl, block_group);
|
2020-01-03 05:26:39 +08:00
|
|
|
} else {
|
|
|
|
btrfs_update_discard_index(discard_ctl, block_group);
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-14 08:22:14 +08:00
|
|
|
/**
|
|
|
|
* btrfs_discard_workfn - discard work function
|
|
|
|
* @work: work
|
|
|
|
*
|
2019-12-14 08:22:16 +08:00
|
|
|
* This finds the next block_group to start discarding and then discards a
|
|
|
|
* single region. It does this in a two-pass fashion: first extents and second
|
|
|
|
* bitmaps. Completely discarded block groups are sent to the unused_bgs path.
|
2019-12-14 08:22:14 +08:00
|
|
|
*/
|
|
|
|
static void btrfs_discard_workfn(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct btrfs_discard_ctl *discard_ctl;
|
|
|
|
struct btrfs_block_group *block_group;
|
2019-12-14 08:22:16 +08:00
|
|
|
enum btrfs_discard_state discard_state;
|
2020-01-03 05:26:39 +08:00
|
|
|
int discard_index = 0;
|
2019-12-14 08:22:14 +08:00
|
|
|
u64 trimmed = 0;
|
2020-01-03 05:26:39 +08:00
|
|
|
u64 minlen = 0;
|
2020-12-06 23:56:20 +08:00
|
|
|
u64 now = ktime_get_ns();
|
2019-12-14 08:22:14 +08:00
|
|
|
|
|
|
|
discard_ctl = container_of(work, struct btrfs_discard_ctl, work.work);
|
|
|
|
|
2020-01-03 05:26:39 +08:00
|
|
|
block_group = peek_discard_list(discard_ctl, &discard_state,
|
2020-12-06 23:56:20 +08:00
|
|
|
&discard_index, now);
|
2019-12-14 08:22:14 +08:00
|
|
|
if (!block_group || !btrfs_run_discard_work(discard_ctl))
|
|
|
|
return;
|
2020-12-06 23:56:20 +08:00
|
|
|
if (now < block_group->discard_eligible_time) {
|
|
|
|
btrfs_discard_schedule_work(discard_ctl, false);
|
|
|
|
return;
|
|
|
|
}
|
2019-12-14 08:22:14 +08:00
|
|
|
|
2019-12-14 08:22:16 +08:00
|
|
|
/* Perform discarding */
|
2020-01-03 05:26:39 +08:00
|
|
|
minlen = discard_minlen[discard_index];
|
|
|
|
|
|
|
|
if (discard_state == BTRFS_DISCARD_BITMAPS) {
|
|
|
|
u64 maxlen = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use the previous levels minimum discard length as the max
|
|
|
|
* length filter. In the case something is added to make a
|
|
|
|
* region go beyond the max filter, the entire bitmap is set
|
|
|
|
* back to BTRFS_TRIM_STATE_UNTRIMMED.
|
|
|
|
*/
|
|
|
|
if (discard_index != BTRFS_DISCARD_INDEX_UNUSED)
|
|
|
|
maxlen = discard_minlen[discard_index - 1];
|
|
|
|
|
2019-12-14 08:22:16 +08:00
|
|
|
btrfs_trim_block_group_bitmaps(block_group, &trimmed,
|
|
|
|
block_group->discard_cursor,
|
|
|
|
btrfs_block_group_end(block_group),
|
2020-01-03 05:26:39 +08:00
|
|
|
minlen, maxlen, true);
|
2020-01-03 05:26:41 +08:00
|
|
|
discard_ctl->discard_bitmap_bytes += trimmed;
|
2020-01-03 05:26:39 +08:00
|
|
|
} else {
|
2019-12-14 08:22:16 +08:00
|
|
|
btrfs_trim_block_group_extents(block_group, &trimmed,
|
|
|
|
block_group->discard_cursor,
|
|
|
|
btrfs_block_group_end(block_group),
|
2020-01-03 05:26:39 +08:00
|
|
|
minlen, true);
|
2020-01-03 05:26:41 +08:00
|
|
|
discard_ctl->discard_extent_bytes += trimmed;
|
2020-01-03 05:26:39 +08:00
|
|
|
}
|
2019-12-14 08:22:16 +08:00
|
|
|
|
|
|
|
/* Determine next steps for a block_group */
|
|
|
|
if (block_group->discard_cursor >= btrfs_block_group_end(block_group)) {
|
|
|
|
if (discard_state == BTRFS_DISCARD_BITMAPS) {
|
|
|
|
btrfs_finish_discard_pass(discard_ctl, block_group);
|
|
|
|
} else {
|
|
|
|
block_group->discard_cursor = block_group->start;
|
|
|
|
spin_lock(&discard_ctl->lock);
|
|
|
|
if (block_group->discard_state !=
|
|
|
|
BTRFS_DISCARD_RESET_CURSOR)
|
|
|
|
block_group->discard_state =
|
|
|
|
BTRFS_DISCARD_BITMAPS;
|
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-06 23:56:21 +08:00
|
|
|
now = ktime_get_ns();
|
2019-12-14 08:22:16 +08:00
|
|
|
spin_lock(&discard_ctl->lock);
|
2020-12-06 23:56:21 +08:00
|
|
|
discard_ctl->prev_discard = trimmed;
|
|
|
|
discard_ctl->prev_discard_time = now;
|
2019-12-14 08:22:16 +08:00
|
|
|
discard_ctl->block_group = NULL;
|
2020-12-06 23:56:22 +08:00
|
|
|
__btrfs_discard_schedule_work(discard_ctl, now, false);
|
2019-12-14 08:22:16 +08:00
|
|
|
spin_unlock(&discard_ctl->lock);
|
2019-12-14 08:22:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* btrfs_run_discard_work - determines if async discard should be running
|
|
|
|
* @discard_ctl: discard control
|
|
|
|
*
|
|
|
|
* Checks if the file system is writeable and BTRFS_FS_DISCARD_RUNNING is set.
|
|
|
|
*/
|
|
|
|
bool btrfs_run_discard_work(struct btrfs_discard_ctl *discard_ctl)
|
|
|
|
{
|
|
|
|
struct btrfs_fs_info *fs_info = container_of(discard_ctl,
|
|
|
|
struct btrfs_fs_info,
|
|
|
|
discard_ctl);
|
|
|
|
|
|
|
|
return (!(fs_info->sb->s_flags & SB_RDONLY) &&
|
|
|
|
test_bit(BTRFS_FS_DISCARD_RUNNING, &fs_info->flags));
|
|
|
|
}
|
|
|
|
|
2020-01-03 05:26:35 +08:00
|
|
|
/**
|
|
|
|
* btrfs_discard_calc_delay - recalculate the base delay
|
|
|
|
* @discard_ctl: discard control
|
|
|
|
*
|
|
|
|
* Recalculate the base delay which is based off the total number of
|
|
|
|
* discardable_extents. Clamp this between the lower_limit (iops_limit or 1ms)
|
|
|
|
* and the upper_limit (BTRFS_DISCARD_MAX_DELAY_MSEC).
|
|
|
|
*/
|
|
|
|
void btrfs_discard_calc_delay(struct btrfs_discard_ctl *discard_ctl)
|
|
|
|
{
|
|
|
|
s32 discardable_extents;
|
2020-01-03 05:26:46 +08:00
|
|
|
s64 discardable_bytes;
|
2020-01-03 05:26:35 +08:00
|
|
|
u32 iops_limit;
|
|
|
|
unsigned long delay;
|
|
|
|
|
|
|
|
discardable_extents = atomic_read(&discard_ctl->discardable_extents);
|
|
|
|
if (!discardable_extents)
|
|
|
|
return;
|
|
|
|
|
|
|
|
spin_lock(&discard_ctl->lock);
|
|
|
|
|
2020-01-03 05:26:46 +08:00
|
|
|
/*
|
|
|
|
* The following is to fix a potential -1 discrepenancy that we're not
|
|
|
|
* sure how to reproduce. But given that this is the only place that
|
|
|
|
* utilizes these numbers and this is only called by from
|
|
|
|
* btrfs_finish_extent_commit() which is synchronized, we can correct
|
|
|
|
* here.
|
|
|
|
*/
|
|
|
|
if (discardable_extents < 0)
|
|
|
|
atomic_add(-discardable_extents,
|
|
|
|
&discard_ctl->discardable_extents);
|
|
|
|
|
|
|
|
discardable_bytes = atomic64_read(&discard_ctl->discardable_bytes);
|
|
|
|
if (discardable_bytes < 0)
|
|
|
|
atomic64_add(-discardable_bytes,
|
|
|
|
&discard_ctl->discardable_bytes);
|
|
|
|
|
|
|
|
if (discardable_extents <= 0) {
|
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-01-03 05:26:35 +08:00
|
|
|
iops_limit = READ_ONCE(discard_ctl->iops_limit);
|
|
|
|
if (iops_limit)
|
2020-11-04 17:45:51 +08:00
|
|
|
delay = MSEC_PER_SEC / iops_limit;
|
|
|
|
else
|
|
|
|
delay = BTRFS_DISCARD_TARGET_MSEC / discardable_extents;
|
2020-01-03 05:26:35 +08:00
|
|
|
|
2020-11-04 17:45:51 +08:00
|
|
|
delay = clamp(delay, BTRFS_DISCARD_MIN_DELAY_MSEC,
|
|
|
|
BTRFS_DISCARD_MAX_DELAY_MSEC);
|
2020-11-04 17:45:52 +08:00
|
|
|
discard_ctl->delay_ms = delay;
|
2020-01-03 05:26:35 +08:00
|
|
|
|
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
}
|
|
|
|
|
2019-12-14 08:22:20 +08:00
|
|
|
/**
|
|
|
|
* btrfs_discard_update_discardable - propagate discard counters
|
|
|
|
* @block_group: block_group of interest
|
|
|
|
*
|
|
|
|
* This propagates deltas of counters up to the discard_ctl. It maintains a
|
|
|
|
* current counter and a previous counter passing the delta up to the global
|
|
|
|
* stat. Then the current counter value becomes the previous counter value.
|
|
|
|
*/
|
2020-10-23 21:58:07 +08:00
|
|
|
void btrfs_discard_update_discardable(struct btrfs_block_group *block_group)
|
2019-12-14 08:22:20 +08:00
|
|
|
{
|
2020-10-23 21:58:07 +08:00
|
|
|
struct btrfs_free_space_ctl *ctl;
|
2019-12-14 08:22:20 +08:00
|
|
|
struct btrfs_discard_ctl *discard_ctl;
|
|
|
|
s32 extents_delta;
|
2019-12-14 08:22:21 +08:00
|
|
|
s64 bytes_delta;
|
2019-12-14 08:22:20 +08:00
|
|
|
|
2020-01-03 05:26:40 +08:00
|
|
|
if (!block_group ||
|
|
|
|
!btrfs_test_opt(block_group->fs_info, DISCARD_ASYNC) ||
|
|
|
|
!btrfs_is_block_group_data_only(block_group))
|
2019-12-14 08:22:20 +08:00
|
|
|
return;
|
|
|
|
|
2020-10-23 21:58:07 +08:00
|
|
|
ctl = block_group->free_space_ctl;
|
2019-12-14 08:22:20 +08:00
|
|
|
discard_ctl = &block_group->fs_info->discard_ctl;
|
|
|
|
|
2020-10-23 21:58:07 +08:00
|
|
|
lockdep_assert_held(&ctl->tree_lock);
|
2019-12-14 08:22:20 +08:00
|
|
|
extents_delta = ctl->discardable_extents[BTRFS_STAT_CURR] -
|
|
|
|
ctl->discardable_extents[BTRFS_STAT_PREV];
|
|
|
|
if (extents_delta) {
|
|
|
|
atomic_add(extents_delta, &discard_ctl->discardable_extents);
|
|
|
|
ctl->discardable_extents[BTRFS_STAT_PREV] =
|
|
|
|
ctl->discardable_extents[BTRFS_STAT_CURR];
|
|
|
|
}
|
2019-12-14 08:22:21 +08:00
|
|
|
|
|
|
|
bytes_delta = ctl->discardable_bytes[BTRFS_STAT_CURR] -
|
|
|
|
ctl->discardable_bytes[BTRFS_STAT_PREV];
|
|
|
|
if (bytes_delta) {
|
|
|
|
atomic64_add(bytes_delta, &discard_ctl->discardable_bytes);
|
|
|
|
ctl->discardable_bytes[BTRFS_STAT_PREV] =
|
|
|
|
ctl->discardable_bytes[BTRFS_STAT_CURR];
|
|
|
|
}
|
2019-12-14 08:22:20 +08:00
|
|
|
}
|
|
|
|
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
/**
|
|
|
|
* btrfs_discard_punt_unused_bgs_list - punt unused_bgs list to discard lists
|
|
|
|
* @fs_info: fs_info of interest
|
|
|
|
*
|
|
|
|
* The unused_bgs list needs to be punted to the discard lists because the
|
2021-05-21 23:42:23 +08:00
|
|
|
* order of operations is changed. In the normal synchronous discard path, the
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
* block groups are trimmed via a single large trim in transaction commit. This
|
|
|
|
* is ultimately what we are trying to avoid with asynchronous discard. Thus,
|
|
|
|
* it must be done before going down the unused_bgs path.
|
|
|
|
*/
|
|
|
|
void btrfs_discard_punt_unused_bgs_list(struct btrfs_fs_info *fs_info)
|
|
|
|
{
|
|
|
|
struct btrfs_block_group *block_group, *next;
|
|
|
|
|
|
|
|
spin_lock(&fs_info->unused_bgs_lock);
|
|
|
|
/* We enabled async discard, so punt all to the queue */
|
|
|
|
list_for_each_entry_safe(block_group, next, &fs_info->unused_bgs,
|
|
|
|
bg_list) {
|
|
|
|
list_del_init(&block_group->bg_list);
|
btrfs: discard: add missing put when grabbing block group from unused list
[BUG]
The following small test script can trigger ASSERT() at unmount time:
mkfs.btrfs -f $dev
mount $dev $mnt
mount -o remount,discard=async $mnt
umount $mnt
The call trace:
assertion failed: atomic_read(&block_group->count) == 1, in fs/btrfs/block-group.c:3431
------------[ cut here ]------------
kernel BUG at fs/btrfs/ctree.h:3204!
invalid opcode: 0000 [#1] PREEMPT SMP NOPTI
CPU: 4 PID: 10389 Comm: umount Tainted: G O 5.8.0-rc3-custom+ #68
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015
Call Trace:
btrfs_free_block_groups.cold+0x22/0x55 [btrfs]
close_ctree+0x2cb/0x323 [btrfs]
btrfs_put_super+0x15/0x17 [btrfs]
generic_shutdown_super+0x72/0x110
kill_anon_super+0x18/0x30
btrfs_kill_super+0x17/0x30 [btrfs]
deactivate_locked_super+0x3b/0xa0
deactivate_super+0x40/0x50
cleanup_mnt+0x135/0x190
__cleanup_mnt+0x12/0x20
task_work_run+0x64/0xb0
__prepare_exit_to_usermode+0x1bc/0x1c0
__syscall_return_slowpath+0x47/0x230
do_syscall_64+0x64/0xb0
entry_SYSCALL_64_after_hwframe+0x44/0xa9
The code:
ASSERT(atomic_read(&block_group->count) == 1);
btrfs_put_block_group(block_group);
[CAUSE]
Obviously it's some btrfs_get_block_group() call doesn't get its put
call.
The offending btrfs_get_block_group() happens here:
void btrfs_mark_bg_unused(struct btrfs_block_group *bg)
{
if (list_empty(&bg->bg_list)) {
btrfs_get_block_group(bg);
list_add_tail(&bg->bg_list, &fs_info->unused_bgs);
}
}
So every call sites removing the block group from unused_bgs list should
reduce the ref count of that block group.
However for async discard, it didn't follow the call convention:
void btrfs_discard_punt_unused_bgs_list(struct btrfs_fs_info *fs_info)
{
list_for_each_entry_safe(block_group, next, &fs_info->unused_bgs,
bg_list) {
list_del_init(&block_group->bg_list);
btrfs_discard_queue_work(&fs_info->discard_ctl, block_group);
}
}
And in btrfs_discard_queue_work(), it doesn't call
btrfs_put_block_group() either.
[FIX]
Fix the problem by reducing the reference count when we grab the block
group from unused_bgs list.
Reported-by: Marcos Paulo de Souza <mpdesouza@suse.com>
Fixes: 6e80d4f8c422 ("btrfs: handle empty block_group removal for async discard")
CC: stable@vger.kernel.org # 5.6+
Tested-by: Marcos Paulo de Souza <mpdesouza@suse.com>
Reviewed-by: Anand Jain <anand.jain@oracle.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2020-07-03 15:05:50 +08:00
|
|
|
btrfs_put_block_group(block_group);
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
btrfs_discard_queue_work(&fs_info->discard_ctl, block_group);
|
|
|
|
}
|
|
|
|
spin_unlock(&fs_info->unused_bgs_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* btrfs_discard_purge_list - purge discard lists
|
|
|
|
* @discard_ctl: discard control
|
|
|
|
*
|
|
|
|
* If we are disabling async discard, we may have intercepted block groups that
|
|
|
|
* are completely free and ready for the unused_bgs path. As discarding will
|
|
|
|
* now happen in transaction commit or not at all, we can safely mark the
|
|
|
|
* corresponding block groups as unused and they will be sent on their merry
|
|
|
|
* way to the unused_bgs list.
|
|
|
|
*/
|
|
|
|
static void btrfs_discard_purge_list(struct btrfs_discard_ctl *discard_ctl)
|
|
|
|
{
|
|
|
|
struct btrfs_block_group *block_group, *next;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
spin_lock(&discard_ctl->lock);
|
|
|
|
for (i = 0; i < BTRFS_NR_DISCARD_LISTS; i++) {
|
|
|
|
list_for_each_entry_safe(block_group, next,
|
|
|
|
&discard_ctl->discard_list[i],
|
|
|
|
discard_list) {
|
|
|
|
list_del_init(&block_group->discard_list);
|
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
if (block_group->used == 0)
|
|
|
|
btrfs_mark_bg_unused(block_group);
|
|
|
|
spin_lock(&discard_ctl->lock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spin_unlock(&discard_ctl->lock);
|
|
|
|
}
|
|
|
|
|
2019-12-14 08:22:14 +08:00
|
|
|
void btrfs_discard_resume(struct btrfs_fs_info *fs_info)
|
|
|
|
{
|
|
|
|
if (!btrfs_test_opt(fs_info, DISCARD_ASYNC)) {
|
|
|
|
btrfs_discard_cleanup(fs_info);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
btrfs_discard_punt_unused_bgs_list(fs_info);
|
|
|
|
|
2019-12-14 08:22:14 +08:00
|
|
|
set_bit(BTRFS_FS_DISCARD_RUNNING, &fs_info->flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
void btrfs_discard_stop(struct btrfs_fs_info *fs_info)
|
|
|
|
{
|
|
|
|
clear_bit(BTRFS_FS_DISCARD_RUNNING, &fs_info->flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
void btrfs_discard_init(struct btrfs_fs_info *fs_info)
|
|
|
|
{
|
|
|
|
struct btrfs_discard_ctl *discard_ctl = &fs_info->discard_ctl;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
spin_lock_init(&discard_ctl->lock);
|
|
|
|
INIT_DELAYED_WORK(&discard_ctl->work, btrfs_discard_workfn);
|
|
|
|
|
|
|
|
for (i = 0; i < BTRFS_NR_DISCARD_LISTS; i++)
|
|
|
|
INIT_LIST_HEAD(&discard_ctl->discard_list[i]);
|
2019-12-14 08:22:20 +08:00
|
|
|
|
2020-01-03 05:26:36 +08:00
|
|
|
discard_ctl->prev_discard = 0;
|
2020-11-04 17:45:53 +08:00
|
|
|
discard_ctl->prev_discard_time = 0;
|
2019-12-14 08:22:20 +08:00
|
|
|
atomic_set(&discard_ctl->discardable_extents, 0);
|
2019-12-14 08:22:21 +08:00
|
|
|
atomic64_set(&discard_ctl->discardable_bytes, 0);
|
2020-01-03 05:26:38 +08:00
|
|
|
discard_ctl->max_discard_size = BTRFS_ASYNC_DISCARD_DEFAULT_MAX_SIZE;
|
2020-11-04 17:45:52 +08:00
|
|
|
discard_ctl->delay_ms = BTRFS_DISCARD_MAX_DELAY_MSEC;
|
2020-01-03 05:26:35 +08:00
|
|
|
discard_ctl->iops_limit = BTRFS_DISCARD_MAX_IOPS;
|
2020-01-03 05:26:36 +08:00
|
|
|
discard_ctl->kbps_limit = 0;
|
2020-01-03 05:26:41 +08:00
|
|
|
discard_ctl->discard_extent_bytes = 0;
|
|
|
|
discard_ctl->discard_bitmap_bytes = 0;
|
|
|
|
atomic64_set(&discard_ctl->discard_bytes_saved, 0);
|
2019-12-14 08:22:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void btrfs_discard_cleanup(struct btrfs_fs_info *fs_info)
|
|
|
|
{
|
|
|
|
btrfs_discard_stop(fs_info);
|
|
|
|
cancel_delayed_work_sync(&fs_info->discard_ctl.work);
|
btrfs: handle empty block_group removal for async discard
block_group removal is a little tricky. It can race with the extent
allocator, the cleaner thread, and balancing. The current path is for a
block_group to be added to the unused_bgs list. Then, when the cleaner
thread comes around, it starts a transaction and then proceeds with
removing the block_group. Extents that are pinned are subsequently
removed from the pinned trees and then eventually a discard is issued
for the entire block_group.
Async discard introduces another player into the game, the discard
workqueue. While it has none of the racing issues, the new problem is
ensuring we don't leave free space untrimmed prior to forgetting the
block_group. This is handled by placing fully free block_groups on a
separate discard queue. This is necessary to maintain discarding order
as in the future we will slowly trim even fully free block_groups. The
ordering helps us make progress on the same block_group rather than say
the last fully freed block_group or needing to search through the fully
freed block groups at the beginning of a list and insert after.
The new order of events is a fully freed block group gets placed on the
unused discard queue first. Once it's processed, it will be placed on
the unusued_bgs list and then the original sequence of events will
happen, just without the final whole block_group discard.
The mount flags can change when processing unused_bgs, so when flipping
from DISCARD to DISCARD_ASYNC, the unused_bgs must be punted to the
discard_list to be trimmed. If we flip off DISCARD_ASYNC, we punt
free block groups on the discard_list to the unused_bg queue which will
do the final discard for us.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Dennis Zhou <dennis@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2019-12-14 08:22:15 +08:00
|
|
|
btrfs_discard_purge_list(&fs_info->discard_ctl);
|
2019-12-14 08:22:14 +08:00
|
|
|
}
|