[PATCH 7/7] xfs: force small EFIs for reaping btree extents

Darrick J. Wong
2023-11-24T23:48:44Z

From: Darrick J. Wong <djwong@kernel.org>

Introduce the concept of a defer ops barrier to separate consecutively
queued pending work items of the same type. With a barrier in place,
the two work items will be tracked separately, and receive separate log
intent items. The goal here is to prevent reaping of old metadata
blocks from creating unnecessarily huge EFIs that could then run the
risk of overflowing the scrub transaction.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
---
fs/xfs/libxfs/xfs_defer.c | 83 +++++++++++++++++++++++++++++++++++++++++++++
fs/xfs/libxfs/xfs_defer.h | 3 ++
fs/xfs/scrub/reap.c | 5 +++
3 files changed, 91 insertions(+)

diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c
index 6604eb50058ba..6b0d4c2e844b0 100644
--- a/fs/xfs/libxfs/xfs_defer.c
+++ b/fs/xfs/libxfs/xfs_defer.c
@@ -181,6 +181,58 @@ static struct kmem_cache *xfs_defer_pending_cache;
* Note that the continuation requested between t2 and t3 is likely to
* reoccur.
*/
+STATIC struct xfs_log_item *
+xfs_defer_barrier_create_intent(
+ struct xfs_trans *tp,
+ struct list_head *items,
+ unsigned int count,
+ bool sort)
+{
+ return NULL;
+}
+
+STATIC void
+xfs_defer_barrier_abort_intent(
+ struct xfs_log_item *intent)
+{
+ /* empty */
+}
+
+STATIC struct xfs_log_item *
+xfs_defer_barrier_create_done(
+ struct xfs_trans *tp,
+ struct xfs_log_item *intent,
+ unsigned int count)
+{
+ return NULL;
+}
+
+STATIC int
+xfs_defer_barrier_finish_item(
+ struct xfs_trans *tp,
+ struct xfs_log_item *done,
+ struct list_head *item,
+ struct xfs_btree_cur **state)
+{
+ ASSERT(0);
+ return -EFSCORRUPTED;
+}
+
+STATIC void
+xfs_defer_barrier_cancel_item(
+ struct list_head *item)
+{
+ ASSERT(0);
+}
+
+static const struct xfs_defer_op_type xfs_barrier_defer_type = {
+ .max_items = 1,
+ .create_intent = xfs_defer_barrier_create_intent,
+ .abort_intent = xfs_defer_barrier_abort_intent,
+ .create_done = xfs_defer_barrier_create_done,
+ .finish_item = xfs_defer_barrier_finish_item,
+ .cancel_item = xfs_defer_barrier_cancel_item,
+};

static const struct xfs_defer_op_type *defer_op_types[] = {
[XFS_DEFER_OPS_TYPE_BMAP] = &xfs_bmap_update_defer_type,
@@ -189,6 +241,7 @@ static const struct xfs_defer_op_type *defer_op_types[] = {
[XFS_DEFER_OPS_TYPE_FREE] = &xfs_extent_free_defer_type,
[XFS_DEFER_OPS_TYPE_AGFL_FREE] = &xfs_agfl_free_defer_type,
[XFS_DEFER_OPS_TYPE_ATTR] = &xfs_attr_defer_type,
+ [XFS_DEFER_OPS_TYPE_BARRIER] = &xfs_barrier_defer_type,
};

/*
@@ -1036,3 +1089,33 @@ xfs_defer_item_unpause(

trace_xfs_defer_item_unpause(tp->t_mountp, dfp);
}
+
+/*
+ * Add a defer ops barrier to force two otherwise adjacent deferred work items
+ * to be tracked separately and have separate log items.
+ */
+void
+xfs_defer_add_barrier(
+ struct xfs_trans *tp)
+{
+ struct xfs_defer_pending *dfp;
+
+ ASSERT(tp->t_flags & XFS_TRANS_PERM_LOG_RES);
+
+ /* If the last defer op added was a barrier, we're done. */
+ if (!list_empty(&tp->t_dfops)) {
+ dfp = list_last_entry(&tp->t_dfops,
+ struct xfs_defer_pending, dfp_list);
+ if (dfp->dfp_type == XFS_DEFER_OPS_TYPE_BARRIER)
+ return;
+ }
+
+ dfp = kmem_cache_zalloc(xfs_defer_pending_cache,
+ GFP_NOFS | __GFP_NOFAIL);
+ dfp->dfp_type = XFS_DEFER_OPS_TYPE_BARRIER;
+ INIT_LIST_HEAD(&dfp->dfp_work);
+ list_add_tail(&dfp->dfp_list, &tp->t_dfops);
+
+ trace_xfs_defer_add_item(tp->t_mountp, dfp, NULL);
+ dfp->dfp_count++;
+}
diff --git a/fs/xfs/libxfs/xfs_defer.h b/fs/xfs/libxfs/xfs_defer.h
index 094ff9062b251..0112678a8856b 100644
--- a/fs/xfs/libxfs/xfs_defer.h
+++ b/fs/xfs/libxfs/xfs_defer.h
@@ -20,6 +20,7 @@ enum xfs_defer_ops_type {
XFS_DEFER_OPS_TYPE_FREE,
XFS_DEFER_OPS_TYPE_AGFL_FREE,
XFS_DEFER_OPS_TYPE_ATTR,
+ XFS_DEFER_OPS_TYPE_BARRIER,
XFS_DEFER_OPS_TYPE_MAX,
};

@@ -141,4 +142,6 @@ void xfs_defer_resources_rele(struct xfs_defer_resources *dres);
int __init xfs_defer_init_item_caches(void);
void xfs_defer_destroy_item_caches(void);

+void xfs_defer_add_barrier(struct xfs_trans *tp);
+
#endif /* __XFS_DEFER_H__ */
diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c
index 78c9f2085db46..ee26fcb500b78 100644
--- a/fs/xfs/scrub/reap.c
+++ b/fs/xfs/scrub/reap.c
@@ -31,6 +31,7 @@
#include "xfs_da_btree.h"
#include "xfs_attr.h"
#include "xfs_attr_remote.h"
+#include "xfs_defer.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
@@ -409,6 +410,8 @@ xreap_agextent_iter(
/*
* Use deferred frees to get rid of the old btree blocks to try to
* minimize the window in which we could crash and lose the old blocks.
+ * Add a defer ops barrier every other extent to avoid stressing the
+ * system with large EFIs.
*/
error = xfs_free_extent_later(sc->tp, fsbno, *aglenp, rs->oinfo,
rs->resv, true);
@@ -416,6 +419,8 @@ xreap_agextent_iter(
return error;

rs->deferred++;
+ if (rs->deferred % 2 == 0)
+ xfs_defer_add_barrier(sc->tp);
return 0;
}