[PATCH 3/3] xfs_scrub: use multiple threads to run in-kernel metadata scrubs that scan inodes

Darrick J. Wong
2023-12-31T22:11:10Z

Instead of running the inode link count and quotacheck scanners in
serial, run them in parallel, with a slight delay to stagger the work to
reduce inode resource contention.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
---
scrub/phase5.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++------
scrub/scrub.c | 18 +++----
scrub/scrub.h | 1
3 files changed, 145 insertions(+), 24 deletions(-)

diff --git a/scrub/phase5.c b/scrub/phase5.c
index 0a91e4f0640..b4c635d3452 100644
--- a/scrub/phase5.c
+++ b/scrub/phase5.c
@@ -384,26 +384,146 @@ check_fs_label(
return error;
}

-/* Check directory connectivity. */
-int
-phase5_func(
- struct scrub_ctx *ctx)
-{
+typedef int (*fs_scan_item_fn)(struct scrub_ctx *, struct action_list *);
+
+struct fs_scan_item {
struct action_list alist;
- bool aborted = false;
+ bool *abortedp;
+ fs_scan_item_fn scrub_fn;
+};
+
+/* Run one full-fs scan scrubber in this thread. */
+static void
+fs_scan_worker(
+ struct workqueue *wq,
+ xfs_agnumber_t nr,
+ void *arg)
+{
+ struct timespec tv;
+ struct fs_scan_item *item = arg;
+ struct scrub_ctx *ctx = wq->wq_ctx;
int ret;

/*
- * Check and fix anything that requires a full inode scan. We do this
- * after we've checked all inodes and repaired anything that could get
- * in the way of a scan.
+ * Delay each successive fs scan by a second so that the threads are
+ * less likely to contend on the inobt and inode buffers.
*/
- action_list_init(&alist);
- ret = scrub_iscan_metadata(ctx, &alist);
- if (ret)
- return ret;
- ret = action_list_process(ctx, ctx->mnt.fd, &alist,
+ if (nr) {
+ tv.tv_sec = nr;
+ tv.tv_nsec = 0;
+ nanosleep(&tv, NULL);
+ }
+
+ ret = item->scrub_fn(ctx, &item->alist);
+ if (ret) {
+ str_liberror(ctx, ret, _("checking fs scan metadata"));
+ *item->abortedp = true;
+ goto out;
+ }
+
+ ret = action_list_process(ctx, ctx->mnt.fd, &item->alist,
ALP_COMPLAIN_IF_UNFIXED | ALP_NOPROGRESS);
+ if (ret) {
+ str_liberror(ctx, ret, _("repairing fs scan metadata"));
+ *item->abortedp = true;
+ goto out;
+ }
+
+out:
+ free(item);
+ return;
+}
+
+/* Queue one full-fs scan scrubber. */
+static int
+queue_fs_scan(
+ struct workqueue *wq,
+ bool *abortedp,
+ xfs_agnumber_t nr,
+ fs_scan_item_fn scrub_fn)
+{
+ struct fs_scan_item *item;
+ struct scrub_ctx *ctx = wq->wq_ctx;
+ int ret;
+
+ item = malloc(sizeof(struct fs_scan_item));
+ if (!item) {
+ ret = ENOMEM;
+ str_liberror(ctx, ret, _("setting up fs scan"));
+ return ret;
+ }
+ action_list_init(&item->alist);
+ item->scrub_fn = scrub_fn;
+ item->abortedp = abortedp;
+
+ ret = -workqueue_add(wq, fs_scan_worker, nr, item);
+ if (ret)
+ str_liberror(ctx, ret, _("queuing fs scan work"));
+
+ return ret;
+}
+
+/* Run multiple full-fs scan scrubbers at the same time. */
+static int
+run_kernel_fs_scan_scrubbers(
+ struct scrub_ctx *ctx)
+{
+ struct workqueue wq_fs_scan;
+ unsigned int nr_threads = scrub_nproc_workqueue(ctx);
+ xfs_agnumber_t nr = 0;
+ bool aborted = false;
+ int ret, ret2;
+
+ ret = -workqueue_create(&wq_fs_scan, (struct xfs_mount *)ctx,
+ nr_threads);
+ if (ret) {
+ str_liberror(ctx, ret, _("setting up fs scan workqueue"));
+ return ret;
+ }
+
+ /*
+ * The nlinks scanner is much faster than quotacheck because it only
+ * walks directories, so we start it first.
+ */
+ ret = queue_fs_scan(&wq_fs_scan, &aborted, nr, scrub_nlinks);
+ if (ret)
+ goto wait;
+
+ if (nr_threads > 1)
+ nr++;
+
+ ret = queue_fs_scan(&wq_fs_scan, &aborted, nr, scrub_quotacheck);
+ if (ret)
+ goto wait;
+
+wait:
+ ret2 = -workqueue_terminate(&wq_fs_scan);
+ if (ret2) {
+ str_liberror(ctx, ret2, _("joining fs scan workqueue"));
+ if (!ret)
+ ret = ret2;
+ }
+ if (aborted && !ret)
+ ret = ECANCELED;
+
+ workqueue_destroy(&wq_fs_scan);
+ return ret;
+}
+
+/* Check directory connectivity. */
+int
+phase5_func(
+ struct scrub_ctx *ctx)
+{
+ bool aborted = false;
+ int ret;
+
+ /*
+ * Check and fix anything that requires a full filesystem scan. We do
+ * this after we've checked all inodes and repaired anything that could
+ * get in the way of a scan.
+ */
+ ret = run_kernel_fs_scan_scrubbers(ctx);
if (ret)
return ret;

@@ -436,7 +556,7 @@ phase5_estimate(
int *rshift)
{
*items = scrub_estimate_iscan_work(ctx);
- *nr_threads = scrub_nproc(ctx);
+ *nr_threads = scrub_nproc(ctx) * 2;
*rshift = 0;
return 0;
}
diff --git a/scrub/scrub.c b/scrub/scrub.c
index a22633a8115..b7ec54c16a4 100644
--- a/scrub/scrub.c
+++ b/scrub/scrub.c
@@ -422,15 +422,6 @@ scrub_summary_metadata(
return scrub_group(ctx, XFROG_SCRUB_GROUP_SUMMARY, 0, alist);
}

-/* Scrub all metadata requiring a full inode scan. */
-int
-scrub_iscan_metadata(
- struct scrub_ctx *ctx,
- struct action_list *alist)
-{
- return scrub_group(ctx, XFROG_SCRUB_GROUP_ISCAN, 0, alist);
-}
-
/* Scrub /only/ the superblock summary counters. */
int
scrub_fs_counters(
@@ -449,6 +440,15 @@ scrub_quotacheck(
return scrub_meta_type(ctx, XFS_SCRUB_TYPE_QUOTACHECK, 0, alist);
}

+/* Scrub /only/ the file link counters. */
+int
+scrub_nlinks(
+ struct scrub_ctx *ctx,
+ struct action_list *alist)
+{
+ return scrub_meta_type(ctx, XFS_SCRUB_TYPE_NLINKS, 0, alist);
+}
+
/* How many items do we have to check? */
unsigned int
scrub_estimate_ag_work(
diff --git a/scrub/scrub.h b/scrub/scrub.h
index 927f86de9ec..5e3f40bf1f4 100644
--- a/scrub/scrub.h
+++ b/scrub/scrub.h
@@ -28,6 +28,7 @@ int scrub_iscan_metadata(struct scrub_ctx *ctx, struct action_list *alist);
int scrub_summary_metadata(struct scrub_ctx *ctx, struct action_list *alist);
int scrub_fs_counters(struct scrub_ctx *ctx, struct action_list *alist);
int scrub_quotacheck(struct scrub_ctx *ctx, struct action_list *alist);
+int scrub_nlinks(struct scrub_ctx *ctx, struct action_list *alist);

bool can_scrub_fs_metadata(struct scrub_ctx *ctx);
bool can_scrub_inode(struct scrub_ctx *ctx);