xfs: Add order IDs to log items in CIL
authorDave Chinner <dchinner@redhat.com>
Thu, 7 Jul 2022 08:53:59 +0000 (18:53 +1000)
committerDave Chinner <david@fromorbit.com>
Thu, 7 Jul 2022 08:53:59 +0000 (18:53 +1000)
Before we split the ordered CIL up into per cpu lists, we need a
mechanism to track the order of the items in the CIL. We need to do
this because there are rules around the order in which related items
must physically appear in the log even inside a single checkpoint
transaction.

An example of this is intents - an intent must appear in the log
before it's intent done record so that log recovery can cancel the
intent correctly. If we have these two records misordered in the
CIL, then they will not be recovered correctly by journal replay.

We also will not be able to move items to the tail of
the CIL list when they are relogged, hence the log items will need
some mechanism to allow the correct log item order to be recreated
before we write log items to the hournal.

Hence we need to have a mechanism for recording global order of
transactions in the log items  so that we can recover that order
from un-ordered per-cpu lists.

Do this with a simple monotonic increasing commit counter in the CIL
context. Each log item in the transaction gets stamped with the
current commit order ID before it is added to the CIL. If the item
is already in the CIL, leave it where it is instead of moving it to
the tail of the list and instead sort the list before we start the
push work.

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

index f02a75d5a03eb5179a29fc28867e84685ab27f39..6bc540898e3a7228e751b87fe71af8d66b999f11 100644 (file)
@@ -550,6 +550,7 @@ xlog_cil_insert_items(
        int                     len = 0;
        int                     iovhdr_res = 0, split_res = 0, ctx_res = 0;
        int                     space_used;
+       int                     order;
        struct xlog_cil_pcp     *cilpcp;
 
        ASSERT(tp);
@@ -645,23 +646,22 @@ xlog_cil_insert_items(
        put_cpu_ptr(cilpcp);
 
        /*
-        * Now (re-)position everything modified at the tail of the CIL.
+        * Now update the order of everything modified in the transaction
+        * and insert items into the CIL if they aren't already there.
         * We do this here so we only need to take the CIL lock once during
         * the transaction commit.
         */
+       order = atomic_inc_return(&ctx->order_id);
        spin_lock(&cil->xc_cil_lock);
        list_for_each_entry(lip, &tp->t_items, li_trans) {
                /* Skip items which aren't dirty in this transaction. */
                if (!test_bit(XFS_LI_DIRTY, &lip->li_flags))
                        continue;
 
-               /*
-                * Only move the item if it isn't already at the tail. This is
-                * to prevent a transient list_empty() state when reinserting
-                * an item that is already the only item in the CIL.
-                */
-               if (!list_is_last(&lip->li_cil, &cil->xc_cil))
-                       list_move_tail(&lip->li_cil, &cil->xc_cil);
+               lip->li_order_id = order;
+               if (!list_empty(&lip->li_cil))
+                       continue;
+               list_add_tail(&lip->li_cil, &cil->xc_cil);
        }
 
        spin_unlock(&cil->xc_cil_lock);
@@ -1082,6 +1082,26 @@ xlog_cil_build_trans_hdr(
        tic->t_curr_res -= lvhdr->lv_bytes;
 }
 
+/*
+ * CIL item reordering compare function. We want to order in ascending ID order,
+ * but we want to leave items with the same ID in the order they were added to
+ * the list. This is important for operations like reflink where we log 4 order
+ * dependent intents in a single transaction when we overwrite an existing
+ * shared extent with a new shared extent. i.e. BUI(unmap), CUI(drop),
+ * CUI (inc), BUI(remap)...
+ */
+static int
+xlog_cil_order_cmp(
+       void                    *priv,
+       const struct list_head  *a,
+       const struct list_head  *b)
+{
+       struct xfs_log_item     *l1 = container_of(a, struct xfs_log_item, li_cil);
+       struct xfs_log_item     *l2 = container_of(b, struct xfs_log_item, li_cil);
+
+       return l1->li_order_id > l2->li_order_id;
+}
+
 /*
  * Pull all the log vectors off the items in the CIL, and remove the items from
  * the CIL. We don't need the CIL lock here because it's only needed on the
@@ -1101,6 +1121,8 @@ xlog_cil_build_lv_chain(
 {
        struct xfs_log_vec      *lv = NULL;
 
+       list_sort(NULL, &cil->xc_cil, xlog_cil_order_cmp);
+
        while (!list_empty(&cil->xc_cil)) {
                struct xfs_log_item     *item;
 
@@ -1114,6 +1136,7 @@ xlog_cil_build_lv_chain(
                }
 
                list_del_init(&item->li_cil);
+               item->li_order_id = 0;
                if (!ctx->lv_chain)
                        ctx->lv_chain = item->li_lv;
                else
index 05a5668d8789c40af776d5c73d35285cbfd57af6..563145ea0f7d78a87bcc804940887b92fa4d8db4 100644 (file)
@@ -229,6 +229,7 @@ struct xfs_cil_ctx {
        struct list_head        committing;     /* ctx committing list */
        struct work_struct      discard_endio_work;
        struct work_struct      push_work;
+       atomic_t                order_id;
 };
 
 /*
index 9561f193e7e13d9b9d6a35c91d7f38ad5f0b73a6..29927ceecf829a61dbd2540439f8ca8ad88f2e83 100644 (file)
@@ -45,6 +45,7 @@ struct xfs_log_item {
        struct xfs_log_vec              *li_lv;         /* active log vector */
        struct xfs_log_vec              *li_lv_shadow;  /* standby vector */
        xfs_csn_t                       li_seq;         /* CIL commit seq */
+       uint32_t                        li_order_id;    /* CIL commit order */
 };
 
 /*