sb_end_write(fs_info->sb);
}
+/*
+ * Periodic re-evaluation of stripe_alloc / stripe_meta groups from the
+ * cleaner. The commit-time rescan only sees groups whose free space changed
+ * in that transaction, and only when there is a transaction: a mark that
+ * did not land (the group was on another list) or a group the worker dropped
+ * would otherwise wait for the next commit, which an idle filesystem at the
+ * fill edge never produces. Same predicate and five-minute hysteresis as
+ * the rescan; the worker re-checks everything it takes.
+ */
+static void btrfs_stripe_reclaim_tick(struct btrfs_fs_info *fs_info)
+{
+ struct btrfs_space_info *sinfo;
+
+ if (!btrfs_test_opt(fs_info, STRIPE_ALLOC))
+ return;
+ list_for_each_entry(sinfo, &fs_info->space_info, list) {
+ int raid;
+
+ if (!(sinfo->flags & (BTRFS_BLOCK_GROUP_DATA |
+ BTRFS_BLOCK_GROUP_METADATA)))
+ continue;
+ if (READ_ONCE(sinfo->bg_reclaim_threshold) <= 0)
+ continue;
+ down_read(&sinfo->groups_sem);
+ for (raid = 0; raid < BTRFS_NR_RAID_TYPES; raid++) {
+ struct btrfs_block_group *bg;
+
+ if (!(btrfs_raid_array[raid].bg_flag &
+ BTRFS_BLOCK_GROUP_RAID56_MASK))
+ continue;
+ list_for_each_entry(bg, &sinfo->block_groups[raid],
+ list) {
+ if (bg->stripe_reclaim_jiffies &&
+ !time_after(jiffies,
+ bg->stripe_reclaim_jiffies +
+ 5 * 60 * HZ))
+ continue;
+ if (!btrfs_stripe_bg_wants_reclaim(bg))
+ continue;
+ spin_lock(&bg->lock);
+ if (btrfs_mark_bg_to_reclaim(bg))
+ bg->stripe_reclaim_jiffies = jiffies ?: 1;
+ spin_unlock(&bg->lock);
+ }
+ }
+ up_read(&sinfo->groups_sem);
+ }
+}
+
void btrfs_reclaim_bgs(struct btrfs_fs_info *fs_info)
{
btrfs_reclaim_sweep(fs_info);
+ btrfs_stripe_reclaim_tick(fs_info);
spin_lock(&fs_info->unused_bgs_lock);
if (!list_empty(&fs_info->reclaim_bgs))
queue_work(system_dfl_wq, &fs_info->reclaim_bgs_work);
spin_unlock(&fs_info->unused_bgs_lock);
}
-void btrfs_mark_bg_to_reclaim(struct btrfs_block_group *bg)
+/* Returns true when the group was linked to the reclaim list. */
+bool btrfs_mark_bg_to_reclaim(struct btrfs_block_group *bg)
{
struct btrfs_fs_info *fs_info = bg->fs_info;
- if (btrfs_link_bg_list(bg, &fs_info->reclaim_bgs))
- trace_btrfs_add_reclaim_block_group(bg);
+ if (!btrfs_link_bg_list(bg, &fs_info->reclaim_bgs))
+ return false;
+ trace_btrfs_add_reclaim_block_group(bg);
+ return true;
}
static int read_bg_from_eb(struct btrfs_fs_info *fs_info, const struct btrfs_key *key,
}
+/*
+ * Does this stripe_alloc / stripe_meta group want the reclaim worker? Like
+ * zoned's zone_unusable reclaim: once enough of the group's FREE space is
+ * trapped in partially used stripes (zoned compares with the capacity, but a
+ * stripe group also holds claimable whole stripes), relocation packs the live
+ * extents into whole stripes elsewhere and frees the group. Only groups
+ * whose live data is below the same threshold are cheap enough to move, and
+ * only when that data fits in the claimable supply of the OTHER groups:
+ * relocation needs whole stripes too, and a group queued while there is
+ * nowhere to move it starves writers with read-only transitions and fails
+ * each time at the fill edge. The worker re-checks with
+ * should_reclaim_block_group(). Reads the counters without locks; the
+ * commit-time rescan calls it with fresh values, the cleaner's periodic
+ * re-evaluation with slightly stale ones, and either error is caught by
+ * the worker.
+ */
+bool btrfs_stripe_bg_wants_reclaim(struct btrfs_block_group *bg)
+{
+ struct btrfs_space_info *sinfo = bg->space_info;
+ const int thresh = READ_ONCE(sinfo->bg_reclaim_threshold);
+ const u64 used = READ_ONCE(bg->used);
+ const u64 trapped = READ_ONCE(bg->stripe_unusable);
+ u64 elsewhere;
+ u64 promised;
+
+ if (thresh <= 0 || bg->ro || !READ_ONCE(bg->stripe_unusable_ready))
+ return false;
+ if (trapped < mult_perc(bg->length - min(used, bg->length), thresh))
+ return false;
+ if (used >= mult_perc(bg->length, thresh))
+ return false;
+ elsewhere = READ_ONCE(sinfo->bytes_stripe_claimable);
+ promised = READ_ONCE(sinfo->bytes_may_use) +
+ READ_ONCE(sinfo->bytes_stripe_margin);
+ elsewhere -= min(elsewhere, READ_ONCE(bg->stripe_claimable));
+ elsewhere -= min(elsewhere, promised);
+ return elsewhere >= used;
+}
+
/*
* Recompute a stripe_alloc block group's trapped free space from a fresh scan
* and fold the change into the space_info total. Called at commit for groups
void btrfs_block_group_rescan_stripe_unusable(struct btrfs_block_group *bg)
{
struct btrfs_free_space_ctl *ctl = bg->free_space_ctl;
- int thresh = READ_ONCE(bg->space_info->bg_reclaim_threshold);
bool reclaim = false;
u64 nstripes;
u32 *freep;
bg->start, bg->stripe_claimable, scanned);
bg->stripe_claimable = scanned;
/*
- * Like zoned's zone_unusable reclaim: once enough of the
- * group's FREE space is trapped in partially used stripes
- * (zoned compares with the capacity, but a stripe group also
- * holds claimable whole stripes), hand it to the reclaim worker
- * so relocation packs the live extents into whole stripes
- * elsewhere and frees the group. Only groups whose live data
- * is below the same threshold are cheap enough to move; the
- * worker re-checks with should_reclaim_block_group().
+ * Hand a group full of trapped free space to the reclaim
+ * worker (btrfs_stripe_bg_wants_reclaim()), but not again
+ * within five minutes of the last mark: a relocation that
+ * failed at the fill edge is not going to succeed a commit
+ * later, and re-queueing it every commit starves writers
+ * with read-only transitions.
*/
- if (thresh > 0 && !bg->ro &&
- (!bg->stripe_reclaim_jiffies ||
+ if ((!bg->stripe_reclaim_jiffies ||
time_after(jiffies, bg->stripe_reclaim_jiffies + 5 * 60 * HZ)) &&
- bg->stripe_unusable >= mult_perc(bg->length - min(bg->used, bg->length), thresh) &&
- bg->used < mult_perc(bg->length, thresh)) {
- /*
- * Only when the live data fits in the claimable supply
- * of the OTHER groups: relocation needs whole stripes
- * too, and a group queued every commit while there is
- * nowhere to move it starves writers with read-only
- * transitions and fails each time at the fill edge.
- */
- struct btrfs_space_info *sinfo = bg->space_info;
- u64 elsewhere = READ_ONCE(sinfo->bytes_stripe_claimable);
- u64 promised = READ_ONCE(sinfo->bytes_may_use) +
- READ_ONCE(sinfo->bytes_stripe_margin);
-
- elsewhere -= min(elsewhere, bg->stripe_claimable);
- elsewhere -= min(elsewhere, promised);
- if (elsewhere >= bg->used)
- reclaim = true;
- }
+ btrfs_stripe_bg_wants_reclaim(bg))
+ reclaim = true;
}
spin_unlock(&ctl->tree_lock);
kvfree(freep);
if (reclaim) {
- bg->stripe_reclaim_jiffies = jiffies ?: 1;
+ /*
+ * Stamp the hysteresis only when the mark lands. A group
+ * that is already on another list (a chunk created this
+ * transaction is still on trans->new_bgs) cannot be linked,
+ * and stamping it anyway lost the mark for five minutes and,
+ * on an idle filesystem with no commit to rescan it, for good.
+ */
spin_lock(&bg->lock);
- btrfs_mark_bg_to_reclaim(bg);
+ if (btrfs_mark_bg_to_reclaim(bg))
+ bg->stripe_reclaim_jiffies = jiffies ?: 1;
spin_unlock(&bg->lock);
}
/*