xfs: factor xfs_bmap_btalloc()
authorDave Chinner <dchinner@redhat.com>
Sun, 12 Feb 2023 22:14:53 +0000 (09:14 +1100)
committerDave Chinner <dchinner@redhat.com>
Sun, 12 Feb 2023 22:14:53 +0000 (09:14 +1100)
There are several different contexts xfs_bmap_btalloc() handles, and
large chunks of the code execute independent allocation contexts.
Try to untangle this mess a bit.

Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
fs/xfs/libxfs/xfs_bmap.c

index baf11bc..a28d57b 100644 (file)
@@ -3196,13 +3196,13 @@ xfs_bmap_select_minlen(
        }
 }
 
-STATIC int
+static int
 xfs_bmap_btalloc_select_lengths(
        struct xfs_bmalloca     *ap,
        struct xfs_alloc_arg    *args,
        xfs_extlen_t            *blen)
 {
-       struct xfs_mount        *mp = ap->ip->i_mount;
+       struct xfs_mount        *mp = args->mp;
        struct xfs_perag        *pag;
        xfs_agnumber_t          agno, startag;
        int                     notinit = 0;
@@ -3216,7 +3216,7 @@ xfs_bmap_btalloc_select_lengths(
        }
 
        args->total = ap->total;
-       startag = XFS_FSB_TO_AGNO(mp, args->fsbno);
+       startag = XFS_FSB_TO_AGNO(mp, ap->blkno);
        if (startag == NULLAGNUMBER)
                startag = 0;
 
@@ -3258,7 +3258,7 @@ xfs_bmap_btalloc_filestreams(
        args->type = XFS_ALLOCTYPE_NEAR_BNO;
        args->total = ap->total;
 
-       start_agno = XFS_FSB_TO_AGNO(mp, args->fsbno);
+       start_agno = XFS_FSB_TO_AGNO(mp, ap->blkno);
        if (start_agno == NULLAGNUMBER)
                start_agno = 0;
 
@@ -3496,170 +3496,229 @@ xfs_bmap_exact_minlen_extent_alloc(
 
 #endif
 
-STATIC int
-xfs_bmap_btalloc(
-       struct xfs_bmalloca     *ap)
+/*
+ * If we are not low on available data blocks and we are allocating at
+ * EOF, optimise allocation for contiguous file extension and/or stripe
+ * alignment of the new extent.
+ *
+ * NOTE: ap->aeof is only set if the allocation length is >= the
+ * stripe unit and the allocation offset is at the end of file.
+ */
+static int
+xfs_bmap_btalloc_at_eof(
+       struct xfs_bmalloca     *ap,
+       struct xfs_alloc_arg    *args,
+       xfs_extlen_t            blen,
+       int                     stripe_align)
 {
-       struct xfs_mount        *mp = ap->ip->i_mount;
-       struct xfs_alloc_arg    args = { .tp = ap->tp, .mp = mp };
-       xfs_alloctype_t         atype = 0;
-       xfs_agnumber_t          ag;
-       xfs_fileoff_t           orig_offset;
-       xfs_extlen_t            orig_length;
-       xfs_extlen_t            blen;
-       xfs_extlen_t            nextminlen = 0;
-       int                     isaligned = 0;
+       struct xfs_mount        *mp = args->mp;
+       xfs_alloctype_t         atype;
        int                     error;
-       int                     stripe_align;
 
-       ASSERT(ap->length);
-       orig_offset = ap->offset;
-       orig_length = ap->length;
+       /*
+        * If there are already extents in the file, try an exact EOF block
+        * allocation to extend the file as a contiguous extent. If that fails,
+        * or it's the first allocation in a file, just try for a stripe aligned
+        * allocation.
+        */
+       if (ap->offset) {
+               xfs_extlen_t    nextminlen = 0;
 
-       stripe_align = xfs_bmap_compute_alignments(ap, &args);
+               atype = args->type;
+               args->type = XFS_ALLOCTYPE_THIS_BNO;
+               args->alignment = 1;
 
+               /*
+                * Compute the minlen+alignment for the next case.  Set slop so
+                * that the value of minlen+alignment+slop doesn't go up between
+                * the calls.
+                */
+               if (blen > stripe_align && blen <= args->maxlen)
+                       nextminlen = blen - stripe_align;
+               else
+                       nextminlen = args->minlen;
+               if (nextminlen + stripe_align > args->minlen + 1)
+                       args->minalignslop = nextminlen + stripe_align -
+                                       args->minlen - 1;
+               else
+                       args->minalignslop = 0;
+
+               args->pag = xfs_perag_get(mp, XFS_FSB_TO_AGNO(mp, args->fsbno));
+               error = xfs_alloc_vextent_this_ag(args);
+               xfs_perag_put(args->pag);
+               if (error)
+                       return error;
+
+               if (args->fsbno != NULLFSBLOCK)
+                       return 0;
+               /*
+                * Exact allocation failed. Reset to try an aligned allocation
+                * according to the original allocation specification.
+                */
+               args->pag = NULL;
+               args->type = atype;
+               args->fsbno = ap->blkno;
+               args->alignment = stripe_align;
+               args->minlen = nextminlen;
+               args->minalignslop = 0;
+       } else {
+               args->alignment = stripe_align;
+               atype = args->type;
+               /*
+                * Adjust minlen to try and preserve alignment if we
+                * can't guarantee an aligned maxlen extent.
+                */
+               if (blen > args->alignment &&
+                   blen <= args->maxlen + args->alignment)
+                       args->minlen = blen - args->alignment;
+               args->minalignslop = 0;
+       }
+
+       error = xfs_alloc_vextent(args);
+       if (error)
+               return error;
+
+       if (args->fsbno != NULLFSBLOCK)
+               return 0;
+
+       /*
+        * Allocation failed, so turn return the allocation args to their
+        * original non-aligned state so the caller can proceed on allocation
+        * failure as if this function was never called.
+        */
+       args->type = atype;
+       args->fsbno = ap->blkno;
+       args->alignment = 1;
+       return 0;
+}
+
+static int
+xfs_bmap_btalloc_best_length(
+       struct xfs_bmalloca     *ap,
+       struct xfs_alloc_arg    *args,
+       int                     stripe_align)
+{
+       struct xfs_mount        *mp = args->mp;
+       xfs_extlen_t            blen = 0;
+       int                     error;
+
+       /*
+        * Determine the initial block number we will target for allocation.
+        */
        if ((ap->datatype & XFS_ALLOC_USERDATA) &&
            xfs_inode_is_filestream(ap->ip)) {
-               ag = xfs_filestream_lookup_ag(ap->ip);
-               ag = (ag != NULLAGNUMBER) ? ag : 0;
-               ap->blkno = XFS_AGB_TO_FSB(mp, ag, 0);
+               xfs_agnumber_t  agno = xfs_filestream_lookup_ag(ap->ip);
+               if (agno == NULLAGNUMBER)
+                       agno = 0;
+               ap->blkno = XFS_AGB_TO_FSB(mp, agno, 0);
        } else {
                ap->blkno = XFS_INO_TO_FSB(mp, ap->ip->i_ino);
        }
-
        xfs_bmap_adjacent(ap);
-
-       args.fsbno = ap->blkno;
-       args.oinfo = XFS_RMAP_OINFO_SKIP_UPDATE;
-
-       /* Trim the allocation back to the maximum an AG can fit. */
-       args.maxlen = min(ap->length, mp->m_ag_max_usable);
-       blen = 0;
+       args->fsbno = ap->blkno;
 
        /*
-        * Search for an allocation group with a single extent large
-        * enough for the request.  If one isn't found, then adjust
-        * the minimum allocation size to the largest space found.
+        * Search for an allocation group with a single extent large enough for
+        * the request.  If one isn't found, then adjust the minimum allocation
+        * size to the largest space found.
         */
        if ((ap->datatype & XFS_ALLOC_USERDATA) &&
            xfs_inode_is_filestream(ap->ip))
-               error = xfs_bmap_btalloc_filestreams(ap, &args, &blen);
+               error = xfs_bmap_btalloc_filestreams(ap, args, &blen);
        else
-               error = xfs_bmap_btalloc_select_lengths(ap, &args, &blen);
+               error = xfs_bmap_btalloc_select_lengths(ap, args, &blen);
        if (error)
                return error;
 
        /*
-        * If we are not low on available data blocks, and the underlying
-        * logical volume manager is a stripe, and the file offset is zero then
-        * try to allocate data blocks on stripe unit boundary. NOTE: ap->aeof
-        * is only set if the allocation length is >= the stripe unit and the
-        * allocation offset is at the end of file.
+        * Don't attempt optimal EOF allocation if previous allocations barely
+        * succeeded due to being near ENOSPC. It is highly unlikely we'll get
+        * optimal or even aligned allocations in this case, so don't waste time
+        * trying.
         */
-       if (!(ap->tp->t_flags & XFS_TRANS_LOWMODE) && ap->aeof) {
-               if (!ap->offset) {
-                       args.alignment = stripe_align;
-                       atype = args.type;
-                       isaligned = 1;
-                       /*
-                        * Adjust minlen to try and preserve alignment if we
-                        * can't guarantee an aligned maxlen extent.
-                        */
-                       if (blen > args.alignment &&
-                           blen <= args.maxlen + args.alignment)
-                               args.minlen = blen - args.alignment;
-                       args.minalignslop = 0;
-               } else {
-                       /*
-                        * First try an exact bno allocation.
-                        * If it fails then do a near or start bno
-                        * allocation with alignment turned on.
-                        */
-                       atype = args.type;
-                       args.type = XFS_ALLOCTYPE_THIS_BNO;
-                       args.alignment = 1;
-
-                       /*
-                        * Compute the minlen+alignment for the
-                        * next case.  Set slop so that the value
-                        * of minlen+alignment+slop doesn't go up
-                        * between the calls.
-                        */
-                       if (blen > stripe_align && blen <= args.maxlen)
-                               nextminlen = blen - stripe_align;
-                       else
-                               nextminlen = args.minlen;
-                       if (nextminlen + stripe_align > args.minlen + 1)
-                               args.minalignslop =
-                                       nextminlen + stripe_align -
-                                       args.minlen - 1;
-                       else
-                               args.minalignslop = 0;
-
-                       args.pag = xfs_perag_get(mp,
-                                       XFS_FSB_TO_AGNO(mp, args.fsbno));
-                       error = xfs_alloc_vextent_this_ag(&args);
-                       xfs_perag_put(args.pag);
-                       if (error)
-                               return error;
-
-                       if (args.fsbno != NULLFSBLOCK)
-                               goto out_success;
-                       /*
-                        * Exact allocation failed. Now try with alignment
-                        * turned on.
-                        */
-                       args.pag = NULL;
-                       args.type = atype;
-                       args.fsbno = ap->blkno;
-                       args.alignment = stripe_align;
-                       args.minlen = nextminlen;
-                       args.minalignslop = 0;
-                       isaligned = 1;
-               }
-       } else {
-               args.alignment = 1;
-               args.minalignslop = 0;
+       if (ap->aeof && !(ap->tp->t_flags & XFS_TRANS_LOWMODE)) {
+               error = xfs_bmap_btalloc_at_eof(ap, args, blen, stripe_align);
+               if (error)
+                       return error;
+               if (args->fsbno != NULLFSBLOCK)
+                       return 0;
        }
 
-       error = xfs_alloc_vextent(&args);
+       error = xfs_alloc_vextent(args);
        if (error)
                return error;
+       if (args->fsbno != NULLFSBLOCK)
+               return 0;
 
-       if (isaligned && args.fsbno == NULLFSBLOCK) {
-               /*
-                * allocation failed, so turn off alignment and
-                * try again.
-                */
-               args.type = atype;
-               args.fsbno = ap->blkno;
-               args.alignment = 0;
-               if ((error = xfs_alloc_vextent(&args)))
-                       return error;
-       }
-       if (args.fsbno == NULLFSBLOCK &&
-           args.minlen > ap->minlen) {
-               args.minlen = ap->minlen;
-               args.type = XFS_ALLOCTYPE_START_BNO;
-               args.fsbno = ap->blkno;
-               if ((error = xfs_alloc_vextent(&args)))
-                       return error;
-       }
-       if (args.fsbno == NULLFSBLOCK) {
-               args.fsbno = 0;
-               args.type = XFS_ALLOCTYPE_FIRST_AG;
-               args.total = ap->minlen;
-               if ((error = xfs_alloc_vextent(&args)))
+       /*
+        * Try a locality first full filesystem minimum length allocation whilst
+        * still maintaining necessary total block reservation requirements.
+        */
+       if (args->minlen > ap->minlen) {
+               args->minlen = ap->minlen;
+               args->type = XFS_ALLOCTYPE_START_BNO;
+               args->fsbno = ap->blkno;
+               error = xfs_alloc_vextent(args);
+               if (error)
                        return error;
-               ap->tp->t_flags |= XFS_TRANS_LOWMODE;
        }
-       args.minleft = ap->minleft;
-       args.wasdel = ap->wasdel;
-       args.resv = XFS_AG_RESV_NONE;
-       args.datatype = ap->datatype;
+       if (args->fsbno != NULLFSBLOCK)
+               return 0;
+
+       /*
+        * We are now critically low on space, so this is a last resort
+        * allocation attempt: no reserve, no locality, blocking, minimum
+        * length, full filesystem free space scan. We also indicate to future
+        * allocations in this transaction that we are critically low on space
+        * so they don't waste time on allocation modes that are unlikely to
+        * succeed.
+        */
+       args->fsbno = 0;
+       args->type = XFS_ALLOCTYPE_FIRST_AG;
+       args->total = ap->minlen;
+       error = xfs_alloc_vextent(args);
+       if (error)
+               return error;
+       ap->tp->t_flags |= XFS_TRANS_LOWMODE;
+       return 0;
+}
+
+static int
+xfs_bmap_btalloc(
+       struct xfs_bmalloca     *ap)
+{
+       struct xfs_mount        *mp = ap->ip->i_mount;
+       struct xfs_alloc_arg    args = {
+               .tp             = ap->tp,
+               .mp             = mp,
+               .fsbno          = NULLFSBLOCK,
+               .oinfo          = XFS_RMAP_OINFO_SKIP_UPDATE,
+               .minleft        = ap->minleft,
+               .wasdel         = ap->wasdel,
+               .resv           = XFS_AG_RESV_NONE,
+               .datatype       = ap->datatype,
+               .alignment      = 1,
+               .minalignslop   = 0,
+       };
+       xfs_fileoff_t           orig_offset;
+       xfs_extlen_t            orig_length;
+       int                     error;
+       int                     stripe_align;
+
+       ASSERT(ap->length);
+       orig_offset = ap->offset;
+       orig_length = ap->length;
+
+       stripe_align = xfs_bmap_compute_alignments(ap, &args);
+
+       /* Trim the allocation back to the maximum an AG can fit. */
+       args.maxlen = min(ap->length, mp->m_ag_max_usable);
+
+       error = xfs_bmap_btalloc_best_length(ap, &args, stripe_align);
+       if (error)
+               return error;
 
        if (args.fsbno != NULLFSBLOCK) {
-out_success:
                xfs_bmap_process_allocated_extent(ap, &args, orig_offset,
                        orig_length);
        } else {