diff mbox series

[2/5] xfs: fix sparse warning in xfs_extent_busy_clear

Message ID 20240402213541.1199959-3-david@fromorbit.com (mailing list archive)
State New
Headers show
Series xfs: sparse warning fixes | expand

Commit Message

Dave Chinner April 2, 2024, 9:28 p.m. UTC
From: Dave Chinner <dchinner@redhat.com>

Sparse reports:

fs/xfs/xfs_extent_busy.c:588:17: warning: context imbalance in 'xfs_extent_busy_clear' - unexpected unlock

But there is no locking bug here. Sparse simply doesn't understand
the logic and locking in the busy extent processing loop.
xfs_extent_busy_put_pag() has an annotation to suppresses an
unexpected unlock warning, but that isn't sufficient.

If we move the pag existence check into xfs_extent_busy_put_pag() and
annotate that with a __release() so that this function always
appears to release the pag->pagb_lock, sparse now thinks the loop
locking is balanced (one unlock, one lock per loop) but still throws
an unexpected unlock warning after loop cleanup.

i.e. it does not understand that we enter the loop without any locks
held and exit it with the last lock still held. Whilst the locking
within the loop is inow balanced, we need to add an __acquire() to
xfs_extent_busy_clear() to set the initial lock context needed to
avoid false warnings.

Signed-off-by: Dave Chinner <dchinner@redhat.com>
---
 fs/xfs/xfs_extent_busy.c | 27 +++++++++++++++++++++++----
 1 file changed, 23 insertions(+), 4 deletions(-)

Comments

Darrick J. Wong April 3, 2024, 4:04 a.m. UTC | #1
On Wed, Apr 03, 2024 at 08:28:29AM +1100, Dave Chinner wrote:
> From: Dave Chinner <dchinner@redhat.com>
> 
> Sparse reports:
> 
> fs/xfs/xfs_extent_busy.c:588:17: warning: context imbalance in 'xfs_extent_busy_clear' - unexpected unlock
> 
> But there is no locking bug here. Sparse simply doesn't understand
> the logic and locking in the busy extent processing loop.
> xfs_extent_busy_put_pag() has an annotation to suppresses an
> unexpected unlock warning, but that isn't sufficient.
> 
> If we move the pag existence check into xfs_extent_busy_put_pag() and
> annotate that with a __release() so that this function always
> appears to release the pag->pagb_lock, sparse now thinks the loop
> locking is balanced (one unlock, one lock per loop) but still throws
> an unexpected unlock warning after loop cleanup.
> 
> i.e. it does not understand that we enter the loop without any locks
> held and exit it with the last lock still held. Whilst the locking
> within the loop is inow balanced, we need to add an __acquire() to
> xfs_extent_busy_clear() to set the initial lock context needed to
> avoid false warnings.
> 
> Signed-off-by: Dave Chinner <dchinner@redhat.com>
> ---
>  fs/xfs/xfs_extent_busy.c | 27 +++++++++++++++++++++++----
>  1 file changed, 23 insertions(+), 4 deletions(-)
> 
> diff --git a/fs/xfs/xfs_extent_busy.c b/fs/xfs/xfs_extent_busy.c
> index 56cfa1498571..686b67372030 100644
> --- a/fs/xfs/xfs_extent_busy.c
> +++ b/fs/xfs/xfs_extent_busy.c
> @@ -534,12 +534,24 @@ xfs_extent_busy_clear_one(
>  	kfree(busyp);
>  }
>  
> +/*
> + * Sparse has real trouble with the structure of xfs_extent_busy_clear() and it
> + * is impossible to annotate it correctly if we leave the 'if (pag)' conditional
> + * in xfs_extent_busy_clear(). Hence we always "release" the lock in
> + * xfs_extent_busy_put_pag() so sparse only ever sees one possible path to
> + * drop the lock.
> + */
>  static void
>  xfs_extent_busy_put_pag(
>  	struct xfs_perag	*pag,
>  	bool			wakeup)
>  		__releases(pag->pagb_lock)
>  {
> +	if (!pag) {
> +		__release(pag->pagb_lock);
> +		return;
> +	}

Passing in a null pointer so we can fake out a compliance tool with a
nonsense annotation really feels like the height of software bureaucracy
compliance culture now...

I don't want to RVB this but I'm so tired of fighting pointless battles
with people over their clearly inadequate tooling, so GIGO:

Reviewed-by: Darrick J. Wong <djwong@kernel.org>

--D

> +
>  	if (wakeup) {
>  		pag->pagb_gen++;
>  		wake_up_all(&pag->pagb_wait);
> @@ -565,10 +577,18 @@ xfs_extent_busy_clear(
>  	xfs_agnumber_t		agno = NULLAGNUMBER;
>  	bool			wakeup = false;
>  
> +	/*
> +	 * Sparse thinks the locking in the loop below is balanced (one unlock,
> +	 * one lock per loop iteration) and doesn't understand that we enter
> +	 * with no lock held and exit with a lock held. Hence we need to
> +	 * "acquire" the lock to create the correct initial condition for the
> +	 * cleanup after loop termination to avoid an unexpected unlock warning.
> +	 */
> +	__acquire(pag->pagb_lock);
> +
>  	list_for_each_entry_safe(busyp, n, list, list) {
>  		if (busyp->agno != agno) {
> -			if (pag)
> -				xfs_extent_busy_put_pag(pag, wakeup);
> +			xfs_extent_busy_put_pag(pag, wakeup);
>  			agno = busyp->agno;
>  			pag = xfs_perag_get(mp, agno);
>  			spin_lock(&pag->pagb_lock);
> @@ -584,8 +604,7 @@ xfs_extent_busy_clear(
>  		}
>  	}
>  
> -	if (pag)
> -		xfs_extent_busy_put_pag(pag, wakeup);
> +	xfs_extent_busy_put_pag(pag, wakeup);
>  }
>  
>  /*
> -- 
> 2.43.0
> 
>
Christoph Hellwig April 3, 2024, 4:32 a.m. UTC | #2
So while this works it looks pretty ugly.  I'd rewrite the nested loop
as what it is: a nested loop.  Something like the patch below.  Only
compile tested so far, but I'll kick off a real test later.

diff --git a/fs/xfs/xfs_extent_busy.c b/fs/xfs/xfs_extent_busy.c
index 2ccde32c9a9e97..1334360128662a 100644
--- a/fs/xfs/xfs_extent_busy.c
+++ b/fs/xfs/xfs_extent_busy.c
@@ -533,21 +533,6 @@ xfs_extent_busy_clear_one(
 	kmem_free(busyp);
 }
 
-static void
-xfs_extent_busy_put_pag(
-	struct xfs_perag	*pag,
-	bool			wakeup)
-		__releases(pag->pagb_lock)
-{
-	if (wakeup) {
-		pag->pagb_gen++;
-		wake_up_all(&pag->pagb_wait);
-	}
-
-	spin_unlock(&pag->pagb_lock);
-	xfs_perag_put(pag);
-}
-
 /*
  * Remove all extents on the passed in list from the busy extents tree.
  * If do_discard is set skip extents that need to be discarded, and mark
@@ -559,32 +544,43 @@ xfs_extent_busy_clear(
 	struct list_head	*list,
 	bool			do_discard)
 {
-	struct xfs_extent_busy	*busyp, *n;
-	struct xfs_perag	*pag = NULL;
-	xfs_agnumber_t		agno = NULLAGNUMBER;
-	bool			wakeup = false;
-
-	list_for_each_entry_safe(busyp, n, list, list) {
-		if (busyp->agno != agno) {
-			if (pag)
-				xfs_extent_busy_put_pag(pag, wakeup);
-			agno = busyp->agno;
-			pag = xfs_perag_get(mp, agno);
-			spin_lock(&pag->pagb_lock);
-			wakeup = false;
-		}
+	struct xfs_extent_busy	*busyp;
 
-		if (do_discard && busyp->length &&
-		    !(busyp->flags & XFS_EXTENT_BUSY_SKIP_DISCARD)) {
-			busyp->flags = XFS_EXTENT_BUSY_DISCARDED;
-		} else {
-			xfs_extent_busy_clear_one(mp, pag, busyp);
-			wakeup = true;
-		}
-	}
+	busyp = list_first_entry_or_null(list, typeof(*busyp), list);
+	if (!busyp)
+		return;
+
+	do {
+		struct xfs_perag	*pag = xfs_perag_get(mp, busyp->agno);
+		bool			wakeup = false;
+		struct xfs_extent_busy	*next;
 
-	if (pag)
-		xfs_extent_busy_put_pag(pag, wakeup);
+		spin_lock(&pag->pagb_lock);
+		for (;;) {
+			next = list_next_entry(busyp, list);
+
+			if (do_discard && busyp->length &&
+			    !(busyp->flags & XFS_EXTENT_BUSY_SKIP_DISCARD)) {
+				busyp->flags = XFS_EXTENT_BUSY_DISCARDED;
+			} else {
+				xfs_extent_busy_clear_one(mp, pag, busyp);
+				wakeup = true;
+			}
+	
+			if (list_entry_is_head(next, list, list) ||
+			    next->agno != pag->pag_agno)
+				break;
+			busyp = next;
+		}
+		if (wakeup) {
+			pag->pagb_gen++;
+			wake_up_all(&pag->pagb_wait);
+		}
+		spin_unlock(&pag->pagb_lock);
+		xfs_perag_put(pag);
+	
+		busyp = next;
+	} while (!list_entry_is_head(busyp, list, list));
 }
 
 /*
diff mbox series

Patch

diff --git a/fs/xfs/xfs_extent_busy.c b/fs/xfs/xfs_extent_busy.c
index 56cfa1498571..686b67372030 100644
--- a/fs/xfs/xfs_extent_busy.c
+++ b/fs/xfs/xfs_extent_busy.c
@@ -534,12 +534,24 @@  xfs_extent_busy_clear_one(
 	kfree(busyp);
 }
 
+/*
+ * Sparse has real trouble with the structure of xfs_extent_busy_clear() and it
+ * is impossible to annotate it correctly if we leave the 'if (pag)' conditional
+ * in xfs_extent_busy_clear(). Hence we always "release" the lock in
+ * xfs_extent_busy_put_pag() so sparse only ever sees one possible path to
+ * drop the lock.
+ */
 static void
 xfs_extent_busy_put_pag(
 	struct xfs_perag	*pag,
 	bool			wakeup)
 		__releases(pag->pagb_lock)
 {
+	if (!pag) {
+		__release(pag->pagb_lock);
+		return;
+	}
+
 	if (wakeup) {
 		pag->pagb_gen++;
 		wake_up_all(&pag->pagb_wait);
@@ -565,10 +577,18 @@  xfs_extent_busy_clear(
 	xfs_agnumber_t		agno = NULLAGNUMBER;
 	bool			wakeup = false;
 
+	/*
+	 * Sparse thinks the locking in the loop below is balanced (one unlock,
+	 * one lock per loop iteration) and doesn't understand that we enter
+	 * with no lock held and exit with a lock held. Hence we need to
+	 * "acquire" the lock to create the correct initial condition for the
+	 * cleanup after loop termination to avoid an unexpected unlock warning.
+	 */
+	__acquire(pag->pagb_lock);
+
 	list_for_each_entry_safe(busyp, n, list, list) {
 		if (busyp->agno != agno) {
-			if (pag)
-				xfs_extent_busy_put_pag(pag, wakeup);
+			xfs_extent_busy_put_pag(pag, wakeup);
 			agno = busyp->agno;
 			pag = xfs_perag_get(mp, agno);
 			spin_lock(&pag->pagb_lock);
@@ -584,8 +604,7 @@  xfs_extent_busy_clear(
 		}
 	}
 
-	if (pag)
-		xfs_extent_busy_put_pag(pag, wakeup);
+	xfs_extent_busy_put_pag(pag, wakeup);
 }
 
 /*