Re: [PATCH 2/3] xfs: fix an AGI lock acquisition ordering problem in xrep_dinode_findmode

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Apr 02, 2024 at 10:18:31PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@xxxxxxxxxx>
> 
> While reviewing the next patch which fixes an ABBA deadlock between the
> AGI and a directory ILOCK, someone asked a question about why we're
> holding the AGI in the first place.  The reason for that is to quiesce
> the inode structures for that AG while we do a repair.
> 
> I then realized that the xrep_dinode_findmode invokes xchk_iscan_iter,
> which walks the inobts (and hence the AGIs) to find all the inodes.
> This itself is also an ABBA vector, since the damaged inode could be in
> AG 5, which we hold while we scan AG 0 for directories.  5 -> 0 is not
> allowed.
> 
> To address this, modify the iscan to allow trylock of the AGI buffer
> using the flags argument to xfs_ialloc_read_agi that the previous patch
> added.
> 
> Signed-off-by: Darrick J. Wong <djwong@xxxxxxxxxx>
> ---
>  fs/xfs/scrub/inode_repair.c |    1 +
>  fs/xfs/scrub/iscan.c        |   36 +++++++++++++++++++++++++++++++++++-
>  fs/xfs/scrub/iscan.h        |   15 +++++++++++++++
>  fs/xfs/scrub/trace.h        |   10 ++++++++--
>  4 files changed, 59 insertions(+), 3 deletions(-)
> 
> 
> diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c
> index eab380e95ef40..35da0193c919e 100644
> --- a/fs/xfs/scrub/inode_repair.c
> +++ b/fs/xfs/scrub/inode_repair.c
> @@ -356,6 +356,7 @@ xrep_dinode_find_mode(
>  	 * so there's a real possibility that _iscan_iter can return EBUSY.
>  	 */
>  	xchk_iscan_start(sc, 5000, 100, &ri->ftype_iscan);
> +	xchk_iscan_set_agi_trylock(&ri->ftype_iscan);
>  	ri->ftype_iscan.skip_ino = sc->sm->sm_ino;
>  	ri->alleged_ftype = XFS_DIR3_FT_UNKNOWN;
>  	while ((error = xchk_iscan_iter(&ri->ftype_iscan, &dp)) == 1) {
> diff --git a/fs/xfs/scrub/iscan.c b/fs/xfs/scrub/iscan.c
> index 66ba0fbd059e0..736ce7c9de6a8 100644
> --- a/fs/xfs/scrub/iscan.c
> +++ b/fs/xfs/scrub/iscan.c
> @@ -243,6 +243,40 @@ xchk_iscan_finish(
>  	mutex_unlock(&iscan->lock);
>  }
>  
> +/*
> + * Grab the AGI to advance the inode scan.  Returns 0 if *agi_bpp is now set,
> + * -ECANCELED if the live scan aborted, -EBUSY if the AGI could not be grabbed,
> + * or the usual negative errno.
> + */
> +STATIC int
> +xchk_iscan_read_agi(
> +	struct xchk_iscan	*iscan,
> +	struct xfs_perag	*pag,
> +	struct xfs_buf		**agi_bpp)
> +{
> +	struct xfs_scrub	*sc = iscan->sc;
> +	unsigned long		relax;
> +	int			ret;
> +
> +	if (!xchk_iscan_agi_trylock(iscan))
> +		return xfs_ialloc_read_agi(pag, sc->tp, 0, agi_bpp);
> +
> +	relax = msecs_to_jiffies(iscan->iget_retry_delay);
> +	do {
> +		ret = xfs_ialloc_read_agi(pag, sc->tp, XFS_IALLOC_FLAG_TRYLOCK,
> +				agi_bpp);

Why is this using xfs_ialloc_read_agi() and not xfs_read_agi()?
How do we get here without the perag AGI state not already
initialised?

i.e. if you just use xfs_read_agi(), all the code that has to plumb
flags into xfs_ialloc_read_agi() goes away and this change because a
lot less intrusive....

> +		if (ret != -EAGAIN)
> +			return ret;
> +		if (!iscan->iget_timeout ||
> +		    time_is_before_jiffies(iscan->__iget_deadline))
> +			return -EBUSY;
> +
> +		trace_xchk_iscan_agi_retry_wait(iscan);
> +	} while (!schedule_timeout_killable(relax) &&
> +		 !xchk_iscan_aborted(iscan));
> +	return -ECANCELED;
> +}
> +
>  /*
>   * Advance ino to the next inode that the inobt thinks is allocated, being
>   * careful to jump to the next AG if we've reached the right end of this AG's
> @@ -281,7 +315,7 @@ xchk_iscan_advance(
>  		if (!pag)
>  			return -ECANCELED;
>  
> -		ret = xfs_ialloc_read_agi(pag, sc->tp, 0, &agi_bp);
> +		ret = xchk_iscan_read_agi(iscan, pag, &agi_bp);
>  		if (ret)
>  			goto out_pag;
>  
> diff --git a/fs/xfs/scrub/iscan.h b/fs/xfs/scrub/iscan.h
> index 71f657552dfac..c9da8f7721f66 100644
> --- a/fs/xfs/scrub/iscan.h
> +++ b/fs/xfs/scrub/iscan.h
> @@ -59,6 +59,9 @@ struct xchk_iscan {
>  /* Set if the scan has been aborted due to some event in the fs. */
>  #define XCHK_ISCAN_OPSTATE_ABORTED	(1)
>  
> +/* Use trylock to acquire the AGI */
> +#define XCHK_ISCAN_OPSTATE_TRYLOCK_AGI	(2)
> +
>  static inline bool
>  xchk_iscan_aborted(const struct xchk_iscan *iscan)
>  {
> @@ -71,6 +74,18 @@ xchk_iscan_abort(struct xchk_iscan *iscan)
>  	set_bit(XCHK_ISCAN_OPSTATE_ABORTED, &iscan->__opstate);
>  }
>  
> +static inline bool
> +xchk_iscan_agi_trylock(const struct xchk_iscan *iscan)
> +{
> +	return test_bit(XCHK_ISCAN_OPSTATE_TRYLOCK_AGI, &iscan->__opstate);
> +}

Function does not actually do any locking, but the name implies it
is actually doing a trylock operation. Perhaps
xchk_iscan_agi_needs_trylock()?

-Dave.
-- 
Dave Chinner
david@xxxxxxxxxxxxx




[Index of Archives]     [XFS Filesystem Development (older mail)]     [Linux Filesystem Development]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux RAID]     [Linux SCSI]


  Powered by Linux