All of lore.kernel.org
 help / color / mirror / Atom feed
From: Dave Chinner <dgc@kernel.org>
To: linux-xfs@vger.kernel.org
Cc: cem@kernel.org
Subject: [PATCH 07/38] xfs: add async quota-targeted blockgc flush
Date: Wed, 19 Aug 2026 10:12:10 +1000	[thread overview]
Message-ID: <20260819001442.1451892-8-dgc@kernel.org> (raw)
In-Reply-To: <20260819001442.1451892-1-dgc@kernel.org>

Convert xfs_blockgc_free_space() to queue the icwalk scan to the
blockgc workqueue rather than running it inline. The existing
synchronous callers wait for completion via flush_work(). Add a
static xfs_blockgc_free_space_nowait() for contexts that need to
kick a scan without waiting.

Factor xfs_blockgc_dquot_filter() out of xfs_blockgc_free_dquots()
to build the icwalk quota filter separately from running the scan.
This allows both the existing synchronous xfs_blockgc_free_dquots()
and the new async xfs_blockgc_free_quota_nowait() to share the
filter construction.

xfs_blockgc_free_quota_nowait() is the async variant for contexts
that hold the ILOCK and cannot wait for the scan synchronously. The
blockgc workers need the ILOCK to scan inodes for reclaimable
speculative preallocations, and the locked inode pins journal space
that the workers need for transactions.

Signed-off-by: Dave Chinner <dgc@kernel.org>
---
 fs/xfs/xfs_icache.c | 145 +++++++++++++++++++++++++++++++++++---------
 fs/xfs/xfs_icache.h |   1 +
 2 files changed, 116 insertions(+), 30 deletions(-)

diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c
index bad09cb090cc..256253bfeac1 100644
--- a/fs/xfs/xfs_icache.c
+++ b/fs/xfs/xfs_icache.c
@@ -1575,24 +1575,75 @@ xfs_blockgc_worker(
 	xfs_blockgc_queue(pag);
 }
 
+struct xfs_blockgc_async_work {
+	struct work_struct	work;
+	struct xfs_mount	*mp;
+	struct xfs_icwalk	icw;
+	int			error;
+	bool			async;
+};
+
+static void
+xfs_blockgc_free_space_worker(
+	struct work_struct	*work)
+{
+	struct xfs_blockgc_async_work *baw = container_of(work,
+					struct xfs_blockgc_async_work, work);
+
+	baw->error = xfs_icwalk(baw->mp, XFS_ICWALK_BLOCKGC, &baw->icw);
+	if (!baw->error)
+		baw->error = xfs_inodegc_flush(baw->mp);
+
+	if (baw->async)
+		kfree(baw);
+}
+
 /*
  * Try to free space in the filesystem by purging inactive inodes, eofblocks
- * and cowblocks.
+ * and cowblocks. The scan is queued to the blockgc workqueue and the caller
+ * waits for it to complete.
  */
 int
 xfs_blockgc_free_space(
 	struct xfs_mount	*mp,
 	struct xfs_icwalk	*icw)
 {
-	int			error;
+	struct xfs_blockgc_async_work baw = {
+		.mp		= mp,
+		.icw		= *icw,
+		.async		= false,
+	};
 
 	trace_xfs_blockgc_free_space(mp, icw, _RET_IP_);
 
-	error = xfs_icwalk(mp, XFS_ICWALK_BLOCKGC, icw);
-	if (error)
-		return error;
+	INIT_WORK_ONSTACK(&baw.work, xfs_blockgc_free_space_worker);
+	queue_work(mp->m_blockgc_wq, &baw.work);
+	flush_work(&baw.work);
+	destroy_work_on_stack(&baw.work);
+	return baw.error;
+}
 
-	return xfs_inodegc_flush(mp);
+/*
+ * Queue a background space reclaim scan without waiting for it to complete.
+ * This is for callers that hold the ILOCK and cannot wait for the scan
+ * synchronously because the blockgc workers need the ILOCK to scan inodes
+ * and the locked inode pins journal space that the workers need for
+ * transactions.
+ */
+static void
+xfs_blockgc_free_space_nowait(
+	struct xfs_mount	*mp,
+	struct xfs_icwalk	*icw)
+{
+	struct xfs_blockgc_async_work *baw;
+
+	baw = kzalloc(sizeof(*baw), GFP_KERNEL | __GFP_NOFAIL);
+	baw->mp = mp;
+	baw->icw = *icw;
+	baw->async = true;
+
+	INIT_WORK(&baw->work, xfs_blockgc_free_space_worker);
+	queue_work(mp->m_blockgc_wq, &baw->work);
 }
 
 /*
@@ -1632,56 +1683,72 @@ xfs_blockgc_flush_all(
 }
 
 /*
- * Run cow/eofblocks scans on the supplied dquots.  We don't know exactly which
- * quota caused an allocation failure, so we make a best effort by including
- * each quota under low free space conditions (less than 1% free space) in the
- * scan.
+ * Build an icwalk filter to free speculative preallocations on inodes in
+ * quota groups that are under low free space conditions. We don't know
+ * exactly which quota caused an allocation failure, so we make a best effort
+ * by including each quota under low free space conditions (less than 1% free
+ * space) in the scan.
  *
- * Callers must not hold any inode's ILOCK.  If requesting a synchronous scan
- * (XFS_ICWALK_FLAG_SYNC), the caller also must not hold any inode's IOLOCK or
- * MMAPLOCK.
+ * Returns true if there are quota groups that need scanning, false otherwise.
  */
-int
-xfs_blockgc_free_dquots(
+static bool
+xfs_blockgc_dquot_filter(
 	struct xfs_mount	*mp,
 	struct xfs_dquot	*udqp,
 	struct xfs_dquot	*gdqp,
 	struct xfs_dquot	*pdqp,
-	unsigned int		iwalk_flags)
+	struct xfs_icwalk	*icw)
 {
-	struct xfs_icwalk	icw = {0};
 	bool			do_work = false;
 
 	if (!udqp && !gdqp && !pdqp)
-		return 0;
+		return false;
 
-	/*
-	 * Run a scan to free blocks using the union filter to cover all
-	 * applicable quotas in a single scan.
-	 */
-	icw.icw_flags = XFS_ICWALK_FLAG_UNION | iwalk_flags;
+	memset(icw, 0, sizeof(*icw));
+	icw->icw_flags = XFS_ICWALK_FLAG_UNION;
 
 	if (XFS_IS_UQUOTA_ENFORCED(mp) && udqp && xfs_dquot_lowsp(udqp)) {
-		icw.icw_uid = make_kuid(mp->m_super->s_user_ns, udqp->q_id);
-		icw.icw_flags |= XFS_ICWALK_FLAG_UID;
+		icw->icw_uid = make_kuid(mp->m_super->s_user_ns, udqp->q_id);
+		icw->icw_flags |= XFS_ICWALK_FLAG_UID;
 		do_work = true;
 	}
 
 	if (XFS_IS_UQUOTA_ENFORCED(mp) && gdqp && xfs_dquot_lowsp(gdqp)) {
-		icw.icw_gid = make_kgid(mp->m_super->s_user_ns, gdqp->q_id);
-		icw.icw_flags |= XFS_ICWALK_FLAG_GID;
+		icw->icw_gid = make_kgid(mp->m_super->s_user_ns, gdqp->q_id);
+		icw->icw_flags |= XFS_ICWALK_FLAG_GID;
 		do_work = true;
 	}
 
 	if (XFS_IS_PQUOTA_ENFORCED(mp) && pdqp && xfs_dquot_lowsp(pdqp)) {
-		icw.icw_prid = pdqp->q_id;
-		icw.icw_flags |= XFS_ICWALK_FLAG_PRID;
+		icw->icw_prid = pdqp->q_id;
+		icw->icw_flags |= XFS_ICWALK_FLAG_PRID;
 		do_work = true;
 	}
 
-	if (!do_work)
+	return do_work;
+}
+
+/*
+ * Run cow/eofblocks scans on the supplied dquots.
+ *
+ * Callers must not hold any inode's ILOCK.  If requesting a synchronous scan
+ * (XFS_ICWALK_FLAG_SYNC), the caller also must not hold any inode's IOLOCK or
+ * MMAPLOCK.
+ */
+int
+xfs_blockgc_free_dquots(
+	struct xfs_mount	*mp,
+	struct xfs_dquot	*udqp,
+	struct xfs_dquot	*gdqp,
+	struct xfs_dquot	*pdqp,
+	unsigned int		iwalk_flags)
+{
+	struct xfs_icwalk	icw;
+
+	if (!xfs_blockgc_dquot_filter(mp, udqp, gdqp, pdqp, &icw))
 		return 0;
 
+	icw.icw_flags |= iwalk_flags;
 	return xfs_blockgc_free_space(mp, &icw);
 }
 
@@ -1697,6 +1764,24 @@ xfs_blockgc_free_quota(
 			xfs_inode_dquot(ip, XFS_DQTYPE_PROJ), iwalk_flags);
 }
 
+/*
+ * Start an async background scan to free speculative preallocations on inodes
+ * in the quota groups attached to the given inode. This does not wait for the
+ * scan to complete and is safe to call with the ILOCK held.
+ */
+void
+xfs_blockgc_free_quota_nowait(
+	struct xfs_inode	*ip)
+{
+	struct xfs_icwalk	icw;
+
+	if (xfs_blockgc_dquot_filter(ip->i_mount,
+			xfs_inode_dquot(ip, XFS_DQTYPE_USER),
+			xfs_inode_dquot(ip, XFS_DQTYPE_GROUP),
+			xfs_inode_dquot(ip, XFS_DQTYPE_PROJ), &icw))
+		xfs_blockgc_free_space_nowait(ip->i_mount, &icw);
+}
+
 /* XFS Inode Cache Walking Code */
 
 /*
diff --git a/fs/xfs/xfs_icache.h b/fs/xfs/xfs_icache.h
index e68d13125f7b..f55cabb9fcf5 100644
--- a/fs/xfs/xfs_icache.h
+++ b/fs/xfs/xfs_icache.h
@@ -62,6 +62,7 @@ int xfs_blockgc_free_dquots(struct xfs_mount *mp, struct xfs_dquot *udqp,
 		unsigned int iwalk_flags);
 int xfs_blockgc_free_quota(struct xfs_inode *ip, unsigned int iwalk_flags);
 int xfs_blockgc_free_space(struct xfs_mount *mp, struct xfs_icwalk *icm);
+void xfs_blockgc_free_quota_nowait(struct xfs_inode *ip);
 void xfs_blockgc_start_flush(struct xfs_mount *mp);
 int xfs_blockgc_flush_all(struct xfs_mount *mp);
 
-- 
2.55.0


  parent reply	other threads:[~2026-08-19  0:14 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-19  0:12 [PATCH V2 00/38] XFS: Atomic multi-extent operations via rolling transactions Dave Chinner
2026-08-19  0:12 ` [PATCH 01/38] xfs: fix dirty transaction cancellation in xfs_bmapi_convert_one_delalloc Dave Chinner
2026-08-19  0:12 ` [PATCH 02/38] xfs: fix dirty transaction cancellation in xfs_attr_set Dave Chinner
2026-08-19  0:12 ` [PATCH 03/38] xfs: fix isize update in xfs_iomap_write_unwritten to track conversion progress Dave Chinner
2026-08-19  0:12 ` [PATCH 04/38] xfs: fix block reservation for zoned RT extent remapping Dave Chinner
2026-08-19  0:12 ` [PATCH 05/38] xfs: factor xfs_trans_reserve_blocks() from xfs_trans_reserve() Dave Chinner
2026-08-19  0:12 ` [PATCH 06/38] xfs: factor xfs_blockgc_start_flush() from xfs_blockgc_flush_all() Dave Chinner
2026-08-19  0:12 ` Dave Chinner [this message]
2026-08-19  0:12 ` [PATCH 08/38] xfs: add async blockgc retry to xfs_trans_reserve_more_inode() Dave Chinner
2026-08-19  0:12 ` [PATCH 09/38] xfs: factor out COW iomap handling from xfs_direct_write_iomap_begin() Dave Chinner
2026-08-19  0:12 ` [PATCH 10/38] xfs: plumb xfs_trans through xfs_reflink_allocate_cow and fill_cow_hole Dave Chinner
2026-08-19  0:12 ` [PATCH 11/38] xfs: teach xfs_reflink_fill_cow_hole() to use a caller-supplied transaction Dave Chinner
2026-08-19  0:12 ` [PATCH 12/38] xfs: add transaction retry infrastructure to xfs_direct_write_cow_iomap_begin Dave Chinner
2026-08-19  0:12 ` [PATCH 13/38] xfs: return -EAGAIN from xfs_reflink_allocate_cow for COW hole without transaction Dave Chinner
2026-08-19  0:12 ` [PATCH 14/38] xfs: remove internal transaction allocation from xfs_reflink_fill_cow_hole Dave Chinner
2026-08-19  0:12 ` [PATCH 15/38] xfs: use zero-block transaction with xfs_trans_reserve_more_inode for COW holes Dave Chinner
2026-08-19  0:12 ` [PATCH 16/38] xfs: change *tp to **tpp in COW allocation call chain Dave Chinner
2026-08-19  0:12 ` [PATCH 17/38] xfs: convert xfs_reflink_fill_delalloc to use rolling transactions Dave Chinner
2026-08-19  0:12 ` [PATCH 18/38] xfs: return -EAGAIN from xfs_reflink_allocate_cow for all allocation cases Dave Chinner
2026-08-19  0:12 ` [PATCH 19/38] xfs: remove dead internal transaction allocation from xfs_reflink_fill_delalloc Dave Chinner
2026-08-19  0:12 ` [PATCH 20/38] xfs: plumb struct xfs_trans *tp into xfs_bmapi_convert_one_delalloc Dave Chinner
2026-08-19  0:12 ` [PATCH 21/38] xfs: use rolling transaction in xfs_bmapi_convert_delalloc Dave Chinner
2026-08-19  0:12 ` [PATCH 22/38] xfs: remove dead internal transaction path from xfs_bmapi_convert_one_delalloc Dave Chinner
2026-08-19  0:12 ` [PATCH 23/38] xfs: add block reservation renewal to xfs_defer_finish Dave Chinner
2026-08-19  0:12 ` [PATCH 24/38] xfs: factor out xfs_iomap_write_unwritten_one helper Dave Chinner
2026-08-19  0:12 ` [PATCH 25/38] xfs: convert xfs_iomap_write_unwritten to rolling transactions Dave Chinner
2026-08-19  0:12 ` [PATCH 26/38] xfs: plumb struct xfs_trans *tp into xfs_reflink_end_cow_extent Dave Chinner
2026-08-19  0:12 ` [PATCH 27/38] xfs: convert xfs_reflink_end_cow to rolling transactions Dave Chinner
2026-08-19  0:12 ` [PATCH 28/38] xfs: remove xfs_reflink_end_cow_extent wrapper and rename locked variant Dave Chinner
2026-08-19  0:12 ` [PATCH 29/38] xfs: convert xfs_zoned_end_io to rolling transactions Dave Chinner
2026-08-19  0:12 ` [PATCH 30/38] xfs: plumb struct xfs_trans *tp into xfs_iomap_write_direct Dave Chinner
2026-08-19  0:12 ` [PATCH 31/38] xfs: make xfs_iomap_write_direct fill in the iomap directly Dave Chinner
2026-08-19  0:12 ` [PATCH 32/38] xfs: plumb struct xfs_trans **tpp into xfs_direct_write_cow_iomap_begin Dave Chinner
2026-08-19  0:12 ` [PATCH 33/38] xfs: introduce struct xfs_direct_write_args for direct write call chain Dave Chinner
2026-08-19  0:12 ` [PATCH 34/38] xfs: convert xfs_direct_write_iomap_begin to use dwa struct throughout Dave Chinner
2026-08-19  0:12 ` [PATCH 35/38] xfs: restructure xfs_direct_write_iomap_begin with unified retry loop Dave Chinner
2026-08-19  0:12 ` [PATCH 36/38] xfs: clean up xfs_direct_write_cow_iomap_begin after restructure Dave Chinner
2026-08-19  0:12 ` [PATCH 37/38] xfs: make pNFS block allocation atomic with inode update Dave Chinner
2026-08-19  0:12 ` [PATCH 38/38] xfs: remove dead internal transaction path from xfs_iomap_write_direct Dave Chinner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260819001442.1451892-8-dgc@kernel.org \
    --to=dgc@kernel.org \
    --cc=cem@kernel.org \
    --cc=linux-xfs@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.