[PATCH 08/38] xfs: add async blockgc retry to xfs_trans_reserve_more_inode()

Dave Chinner <[email protected]>
Newsgroups org.kernel.vger.linux-xfs
Message-ID <[email protected]>
xfs_trans_reserve_more_inode() can fail with ENOSPC or EDQUOT when
called from contexts that hold the ILOCK. Unlike xfs_trans_alloc_inode()
which can drop the ILOCK, run synchronous blockgc, and retry, this
function cannot use synchronous reclaim because the ILOCK is held.

Synchronous blockgc would deadlock in two ways: first, the blockgc
workers need to take the ILOCK on each inode they scan, so they
would block on the inode we hold. Second, the locked inode pins
journal space that blockgc transactions need for their own commits.

Add a retry loop that on ENOSPC kicks xfs_blockgc_start_flush() to
reclaim global speculative preallocations, and on EDQUOT/ENOSPC
from quota kicks xfs_blockgc_free_quota_nowait() to reclaim quota-
targeted preallocations. After kicking, the loop sleeps for 100ms
via io_schedule_timeout() to allow the background workers to make
progress, then retries once. io_schedule_timeout() is used because
this can be called from the IO submission path where block plugs
may be held.

Signed-off-by: Dave Chinner <[email protected]>
---
 fs/xfs/xfs_trans.c | 63 +++++++++++++++++++++++++++++++++++-----------
 1 file changed, 48 insertions(+), 15 deletions(-)

diff --git a/fs/xfs/xfs_trans.c b/fs/xfs/xfs_trans.c
index 609b0fe3d4d3..8e32da47501e 100644
--- a/fs/xfs/xfs_trans.c
+++ b/fs/xfs/xfs_trans.c
@@ -1130,8 +1130,24 @@ xfs_trans_alloc_inode(
 }
 
 /*
- * Try to reserve more blocks and file quota for a transaction.  Same
- * conditions of usage as xfs_trans_reserve_blocks.
+ * Reserve blocks and quota for an inode that is already locked and joined to
+ * the transaction. If the reservation fails due to ENOSPC or EDQUOT, kick
+ * background blockgc reclaim and retry once after a short delay.
+ *
+ * We cannot use synchronous blockgc flushing here because we hold the ILOCK.
+ * Synchronous blockgc needs to take the ILOCK on each inode it scans, so it
+ * would deadlock on the inode we hold. Furthermore, the locked inode pins
+ * journal space that blockgc transactions need for their own commits, creating
+ * a second deadlock vector. Instead, we kick the background workers and sleep
+ * briefly so that if blockgc is stuck waiting on resources we hold, we still
+ * retry and return ENOSPC rather than deadlocking.
+ *
+ * We use io_schedule_timeout() for the delay because this can be called from
+ * the IO submission path where block plugs may be held. The 100ms delay is
+ * long enough for blockgc workers to lock an inode, run a transaction to free
+ * speculative preallocations and commit it, but short enough that we don't
+ * cause excessive IO latency if blockgc cannot make progress because it is
+ * blocked on resources we hold.
  */
 int
 xfs_trans_reserve_more_inode(
@@ -1143,27 +1159,44 @@ xfs_trans_reserve_more_inode(
 {
 	struct xfs_mount	*mp = ip->i_mount;
 	unsigned int		rtx = xfs_extlen_to_rtxlen(mp, rblocks);
+	int			retry = 0;
 	int			error;
 
 	xfs_assert_ilocked(ip, XFS_ILOCK_EXCL);
 
-	error = xfs_trans_reserve_blocks(tp, dblocks, rtx);
-	if (error)
-		return error;
-
-	if (!XFS_IS_QUOTA_ON(mp) || xfs_is_quota_inode(&mp->m_sb, I_INO(ip)))
-		return 0;
-
 	if (tp->t_flags & XFS_TRANS_RESERVE)
 		force_quota = true;
 
-	error = xfs_trans_reserve_quota_nblks(tp, ip, dblocks, rblocks,
-			force_quota);
-	if (!error)
-		return 0;
+	do {
+		if (retry)
+			io_schedule_timeout(msecs_to_jiffies(100));
+
+		error = xfs_trans_reserve_blocks(tp, dblocks, rtx);
+		if (error == -ENOSPC) {
+			xfs_blockgc_start_flush(mp);
+			continue;
+		}
+		if (error)
+			return error;
+
+		if (!XFS_IS_QUOTA_ON(mp) ||
+		    xfs_is_quota_inode(&mp->m_sb, I_INO(ip)))
+			return 0;
+
+		error = xfs_trans_reserve_quota_nblks(tp, ip, dblocks,
+				rblocks, force_quota);
+		if (!error)
+			return 0;
+
+		xfs_trans_unreserve_blocks(tp, dblocks, rtx);
+
+		if (error == -EDQUOT || error == -ENOSPC) {
+			xfs_blockgc_free_quota_nowait(ip);
+			continue;
+		}
+		return error;
+	} while (retry++ == 0);
 
-	/* Quota failed, give back the new reservation. */
-	xfs_trans_unreserve_blocks(tp, dblocks, rtx);
 	return error;
 }
 
-- 
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.