]> www.infradead.org Git - users/hch/xfsprogs.git/commitdiff
xfs: allow inode-based btrees to reserve space in the data device
authorDarrick J. Wong <djwong@kernel.org>
Wed, 29 May 2024 04:11:34 +0000 (21:11 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Tue, 9 Jul 2024 22:37:18 +0000 (15:37 -0700)
Create a new space reservation scheme so that btree metadata for the
realtime volume can reserve space in the data device to avoid space
underruns.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
include/xfs_inode.h
include/xfs_mount.h
include/xfs_trace.h
io/inject.c
libxfs/init.c
libxfs/libxfs_priv.h
libxfs/xfs_ag_resv.c
libxfs/xfs_errortag.h
libxfs/xfs_imeta.c
libxfs/xfs_imeta.h
libxfs/xfs_types.h

index b63feac518d9ba13c81b7ebd4bdc1db17e957bca..94f811cbcbe5dfedaa5d23f1e30796e7fb69013b 100644 (file)
@@ -225,7 +225,10 @@ typedef struct xfs_inode {
        struct xfs_ifork        i_df;           /* data fork */
        struct xfs_ifork        i_af;           /* attribute fork */
        struct xfs_inode_log_item *i_itemp;     /* logging information */
-       unsigned int            i_delayed_blks; /* count of delay alloc blks */
+       uint64_t                i_delayed_blks; /* count of delay alloc blks */
+       /* Space that has been set aside to root a btree in this file. */
+       uint64_t                i_meta_resv_asked;
+
        xfs_fsize_t             i_disk_size;    /* number of bytes in file */
        xfs_rfsblock_t          i_nblocks;      /* # of direct & btree blocks */
        prid_t                  i_projid;       /* owner's project id */
index 08ae1204255cbf19b0317247d3e2d2325241a3ef..2cfc4e52453ea75237a045a7226fd4393af97b5b 100644 (file)
@@ -93,6 +93,7 @@ typedef struct xfs_mount {
        uint                    m_rmap_maxlevels; /* max rmap btree levels */
        uint                    m_refc_maxlevels; /* max refc btree levels */
        unsigned int            m_agbtree_maxlevels; /* max level of all AG btrees */
+       unsigned int            m_rtbtree_maxlevels; /* max level of all rt btrees */
        xfs_extlen_t            m_ag_prealloc_blocks; /* reserved ag blocks */
        uint                    m_alloc_set_aside; /* space we can't use */
        uint                    m_ag_max_usable; /* max space per AG */
index 6edaa9321e986b72fa75a31be3a114dc11d81a33..088836f69b26e7ebeca841d284b2ad30c59e0f9e 100644 (file)
 #define trace_xfs_iunlink_remove(...)          ((void) 0)
 #define trace_xfs_iunlink_map_prev_fallback(...)       ((void) 0)
 
+#define trace_xfs_imeta_resv_alloc_extent(...) ((void) 0)
+#define trace_xfs_imeta_resv_critical(...)     ((void) 0)
+#define trace_xfs_imeta_resv_free(...)         ((void) 0)
+#define trace_xfs_imeta_resv_free_extent(...)  ((void) 0)
+#define trace_xfs_imeta_resv_init(...)         ((void) 0)
+#define trace_xfs_imeta_resv_init_error(...)   ((void) 0)
+
 #endif /* __TRACE_H__ */
index 4aeb6da326b4fd166c8276c8a73e612842e52779..1961a312939423591f2fcc38d755ff54d23b39c1 100644 (file)
@@ -64,6 +64,7 @@ error_tag(char *name)
                { XFS_ERRTAG_WB_DELAY_MS,               "wb_delay_ms" },
                { XFS_ERRTAG_WRITE_DELAY_MS,            "write_delay_ms" },
                { XFS_ERRTAG_EXCHMAPS_FINISH_ONE,       "exchmaps_finish_one" },
+               { XFS_ERRTAG_IMETA_RESV_CRITICAL,       "imeta_resv_critical" },
                { XFS_ERRTAG_MAX,                       NULL }
        };
        int     count;
index 98956e8666bdefef4c514dfcd6a8626b803ca6b9..788dc08391f9f16b9255c130f48ed018b89239a5 100644 (file)
@@ -598,6 +598,15 @@ xfs_agbtree_compute_maxlevels(
        mp->m_agbtree_maxlevels = max(levels, mp->m_refc_maxlevels);
 }
 
+/* Compute maximum possible height for realtime btree types for this fs. */
+static inline void
+xfs_rtbtree_compute_maxlevels(
+       struct xfs_mount        *mp)
+{
+       /* This will be filled in later. */
+       mp->m_rtbtree_maxlevels = 0;
+}
+
 /* Compute maximum possible height of all btrees. */
 void
 libxfs_compute_all_maxlevels(
@@ -614,7 +623,7 @@ libxfs_compute_all_maxlevels(
        xfs_refcountbt_compute_maxlevels(mp);
 
        xfs_agbtree_compute_maxlevels(mp);
-
+       xfs_rtbtree_compute_maxlevels(mp);
 }
 
 /* Mount the metadata files under the metadata directory tree. */
index 3f0e0eb4684126cf5f8570f1c85e6faa1d3b5461..5cbb084ecaaf3611cc196a6bdc4dee6ef20d5195 100644 (file)
@@ -222,6 +222,17 @@ uint32_t get_random_u32(void);
 #define get_random_u32()       (0)
 #endif
 
+static inline int
+__percpu_counter_compare(uint64_t *count, int64_t rhs, int32_t batch)
+{
+       if (*count > rhs)
+               return 1;
+       else if (*count < rhs)
+               return -1;
+       return 0;
+}
+
+
 #define PAGE_SIZE              getpagesize()
 
 #define inode_peek_iversion(inode)     (inode)->i_version
index 7e5cbe0cb6afd345f5f7dafd15a53dd73b2bc559..1ecade0f30c4cdb92fc5459c94f9c4cd78b8a592 100644 (file)
@@ -112,6 +112,7 @@ xfs_ag_resv_needed(
        case XFS_AG_RESV_RMAPBT:
                len -= xfs_perag_resv(pag, type)->ar_reserved;
                break;
+       case XFS_AG_RESV_IMETA:
        case XFS_AG_RESV_NONE:
                /* empty */
                break;
@@ -346,6 +347,7 @@ xfs_ag_resv_alloc_extent(
 
        switch (type) {
        case XFS_AG_RESV_AGFL:
+       case XFS_AG_RESV_IMETA:
                return;
        case XFS_AG_RESV_METADATA:
        case XFS_AG_RESV_RMAPBT:
@@ -388,6 +390,7 @@ xfs_ag_resv_free_extent(
 
        switch (type) {
        case XFS_AG_RESV_AGFL:
+       case XFS_AG_RESV_IMETA:
                return;
        case XFS_AG_RESV_METADATA:
        case XFS_AG_RESV_RMAPBT:
index 7002d7676a7884dc185877b7fe7a293f9446a3f4..c682fa17ac168c883c91233fd7ff72178ae5a329 100644 (file)
@@ -64,7 +64,8 @@
 #define XFS_ERRTAG_WB_DELAY_MS                         42
 #define XFS_ERRTAG_WRITE_DELAY_MS                      43
 #define XFS_ERRTAG_EXCHMAPS_FINISH_ONE                 44
-#define XFS_ERRTAG_MAX                                 45
+#define XFS_ERRTAG_IMETA_RESV_CRITICAL                 45
+#define XFS_ERRTAG_MAX                                 46
 
 /*
  * Random factors for above tags, 1 means always, 2 means 1/2 time, etc.
 #define XFS_RANDOM_WB_DELAY_MS                         3000
 #define XFS_RANDOM_WRITE_DELAY_MS                      3000
 #define XFS_RANDOM_EXCHMAPS_FINISH_ONE                 1
+#define XFS_RANDOM_IMETA_RESV_CRITICAL                 4
 
 #endif /* __XFS_ERRORTAG_H_ */
index c8626ec0d18f913a20b3f7d879ce741ddd4b14f1..ab34e234db108a58f1bb89df7c3c2b094d23c014 100644 (file)
@@ -27,6 +27,9 @@
 #include "xfs_dir2_priv.h"
 #include "xfs_parent.h"
 #include "xfs_health.h"
+#include "xfs_errortag.h"
+#include "xfs_btree.h"
+#include "xfs_alloc.h"
 
 /*
  * Metadata Directory Tree
@@ -750,3 +753,203 @@ xfs_imeta_free_path(
        kfree(path->im_path);
        kfree(path);
 }
+
+/*
+ * Is the amount of space that could be allocated towards a given metadata
+ * file at or beneath a certain threshold?
+ */
+static inline bool
+xfs_imeta_resv_can_cover(
+       struct xfs_inode        *ip,
+       int64_t                 rhs)
+{
+       /*
+        * The amount of space that can be allocated to this metadata file is
+        * the remaining reservation for the particular metadata file + the
+        * global free block count.  Take care of the first case to avoid
+        * touching the per-cpu counter.
+        */
+       if (ip->i_delayed_blks >= rhs)
+               return true;
+
+       /*
+        * There aren't enough blocks left in the inode's reservation, but it
+        * isn't critical unless there also isn't enough free space.
+        */
+       return __percpu_counter_compare(&ip->i_mount->m_fdblocks,
+                       rhs - ip->i_delayed_blks, 2048) >= 0;
+}
+
+/*
+ * Is this metadata file critically low on blocks?  For now we'll define that
+ * as the number of blocks we can get our hands on being less than 10% of what
+ * we reserved or less than some arbitrary number (maximum btree height).
+ */
+bool
+xfs_imeta_resv_critical(
+       struct xfs_inode        *ip)
+{
+       uint64_t                asked_low_water;
+
+       if (!ip)
+               return false;
+
+       ASSERT(xfs_is_metadir_inode(ip));
+       trace_xfs_imeta_resv_critical(ip, 0);
+
+       if (!xfs_imeta_resv_can_cover(ip, ip->i_mount->m_rtbtree_maxlevels))
+               return true;
+
+       asked_low_water = div_u64(ip->i_meta_resv_asked, 10);
+       if (!xfs_imeta_resv_can_cover(ip, asked_low_water))
+               return true;
+
+       return XFS_TEST_ERROR(false, ip->i_mount,
+                       XFS_ERRTAG_IMETA_RESV_CRITICAL);
+}
+
+/* Allocate a block from the metadata file's reservation. */
+void
+xfs_imeta_resv_alloc_extent(
+       struct xfs_inode        *ip,
+       struct xfs_alloc_arg    *args)
+{
+       int64_t                 len = args->len;
+
+       ASSERT(xfs_is_metadir_inode(ip));
+       ASSERT(XFS_IS_DQDETACHED(ip->i_mount, ip));
+       ASSERT(args->resv == XFS_AG_RESV_IMETA);
+
+       trace_xfs_imeta_resv_alloc_extent(ip, args->len);
+
+       /*
+        * Allocate the blocks from the metadata inode's block reservation
+        * and update the ondisk sb counter.
+        */
+       if (ip->i_delayed_blks > 0) {
+               int64_t         from_resv;
+
+               from_resv = min_t(int64_t, len, ip->i_delayed_blks);
+               ip->i_delayed_blks -= from_resv;
+               xfs_mod_delalloc(ip, 0, -from_resv);
+               xfs_trans_mod_sb(args->tp, XFS_TRANS_SB_RES_FDBLOCKS,
+                               -from_resv);
+               len -= from_resv;
+       }
+
+       /*
+        * Any allocation in excess of the reservation requires in-core and
+        * on-disk fdblocks updates.  If we can grab @len blocks from the
+        * in-core fdblocks then all we need to do is update the on-disk
+        * superblock; if not, then try to steal some from the transaction's
+        * block reservation.  Overruns are only expected for rmap btrees.
+        */
+       if (len) {
+               unsigned int    field;
+               int             error;
+
+               error = xfs_dec_fdblocks(ip->i_mount, len, true);
+               if (error)
+                       field = XFS_TRANS_SB_FDBLOCKS;
+               else
+                       field = XFS_TRANS_SB_RES_FDBLOCKS;
+
+               xfs_trans_mod_sb(args->tp, field, -len);
+       }
+
+       ip->i_nblocks += args->len;
+       xfs_trans_log_inode(args->tp, ip, XFS_ILOG_CORE);
+}
+
+/* Free a block to the metadata file's reservation. */
+void
+xfs_imeta_resv_free_extent(
+       struct xfs_inode        *ip,
+       struct xfs_trans        *tp,
+       xfs_filblks_t           len)
+{
+       int64_t                 to_resv;
+
+       ASSERT(xfs_is_metadir_inode(ip));
+       ASSERT(XFS_IS_DQDETACHED(ip->i_mount, ip));
+       trace_xfs_imeta_resv_free_extent(ip, len);
+
+       ip->i_nblocks -= len;
+       xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+
+       /*
+        * Add the freed blocks back into the inode's delalloc reservation
+        * until it reaches the maximum size.  Update the ondisk fdblocks only.
+        */
+       to_resv = ip->i_meta_resv_asked - (ip->i_nblocks + ip->i_delayed_blks);
+       if (to_resv > 0) {
+               to_resv = min_t(int64_t, to_resv, len);
+               ip->i_delayed_blks += to_resv;
+               xfs_mod_delalloc(ip, 0, to_resv);
+               xfs_trans_mod_sb(tp, XFS_TRANS_SB_RES_FDBLOCKS, to_resv);
+               len -= to_resv;
+       }
+
+       /*
+        * Everything else goes back to the filesystem, so update the in-core
+        * and on-disk counters.
+        */
+       if (len)
+               xfs_trans_mod_sb(tp, XFS_TRANS_SB_FDBLOCKS, len);
+}
+
+/* Release a metadata file's space reservation. */
+void
+xfs_imeta_resv_free_inode(
+       struct xfs_inode        *ip)
+{
+       if (!ip)
+               return;
+
+       ASSERT(xfs_is_metadir_inode(ip));
+       trace_xfs_imeta_resv_free(ip, 0);
+
+       xfs_mod_delalloc(ip, 0, -ip->i_delayed_blks);
+       xfs_add_fdblocks(ip->i_mount, ip->i_delayed_blks);
+       ip->i_delayed_blks = 0;
+       ip->i_meta_resv_asked = 0;
+}
+
+/* Set up a metadata file's space reservation. */
+int
+xfs_imeta_resv_init_inode(
+       struct xfs_inode        *ip,
+       xfs_filblks_t           ask)
+{
+       xfs_filblks_t           hidden_space;
+       xfs_filblks_t           used;
+       int                     error;
+
+       if (!ip || ip->i_meta_resv_asked > 0)
+               return 0;
+
+       ASSERT(xfs_is_metadir_inode(ip));
+
+       /*
+        * Space taken by all other metadata btrees are accounted on-disk as
+        * used space.  We therefore only hide the space that is reserved but
+        * not used by the trees.
+        */
+       used = ip->i_nblocks;
+       if (used > ask)
+               ask = used;
+       hidden_space = ask - used;
+
+       error = xfs_dec_fdblocks(ip->i_mount, hidden_space, true);
+       if (error) {
+               trace_xfs_imeta_resv_init_error(ip, error, _RET_IP_);
+               return error;
+       }
+
+       xfs_mod_delalloc(ip, 0, hidden_space);
+       ip->i_delayed_blks = hidden_space;
+       ip->i_meta_resv_asked = ask;
+
+       trace_xfs_imeta_resv_init(ip, ask);
+       return 0;
+}
index 5a5db04aa2becbd802927500a7877ebcf13070b4..33d5b6c5e6c6944c288158f693be94fd713f7af6 100644 (file)
@@ -83,6 +83,17 @@ int xfs_imeta_link(struct xfs_imeta_update *upd);
 int xfs_imeta_commit(struct xfs_imeta_update *upd);
 void xfs_imeta_cancel(struct xfs_imeta_update *upd, int error);
 
+/* Space reservations for metadata inodes. */
+struct xfs_alloc_arg;
+
+bool xfs_imeta_resv_critical(struct xfs_inode *ip);
+void xfs_imeta_resv_alloc_extent(struct xfs_inode *ip,
+               struct xfs_alloc_arg *args);
+void xfs_imeta_resv_free_extent(struct xfs_inode *ip, struct xfs_trans *tp,
+               xfs_filblks_t len);
+void xfs_imeta_resv_free_inode(struct xfs_inode *ip);
+int xfs_imeta_resv_init_inode(struct xfs_inode *ip, xfs_filblks_t ask);
+
 /* Code specific to kernel/userspace; must be provided externally. */
 
 int xfs_imeta_iget(struct xfs_trans *tp, xfs_ino_t ino, umode_t mode,
index 9df94230b530bd9bfa2cba56af4d9b545e5c7673..6dac02827a351ec4883e28366281d808407a52a1 100644 (file)
@@ -202,6 +202,13 @@ enum xfs_ag_resv_type {
         * altering fdblocks.  If you think you need this you're wrong.
         */
        XFS_AG_RESV_IGNORE,
+
+       /*
+        * This allocation activity is being done on behalf of a metadata file.
+        * These files maintain their own permanent space reservations and are
+        * required to adjust fdblocks using the xfs_imeta_resv_* helpers.
+        */
+       XFS_AG_RESV_IMETA,
 };
 
 /* Results of scanning a btree keyspace to check occupancy. */