[PATCH 07/38] xfs: add async quota-targeted blockgc flush

Dave Chinner <[email protected]>
Newsgroups org.kernel.vger.linux-xfs
Message-ID <[email protected]>
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 <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.