]> nv-tegra.nvidia Code Review - linux-2.6.git/blobdiff - fs/xfs/xfs_trans_ail.c
block: make gendisk hold a reference to its queue
[linux-2.6.git] / fs / xfs / xfs_trans_ail.c
index 645928cab42da05d61a9b626aefbbb806aa9001d..3a1e7ca54c2dc75d73b59c89ca30af4138c40d33 100644 (file)
 #include "xfs_trans_priv.h"
 #include "xfs_error.h"
 
-STATIC void xfs_ail_insert(struct xfs_ail *, xfs_log_item_t *);
-STATIC void xfs_ail_delete(struct xfs_ail *, xfs_log_item_t *);
-STATIC xfs_log_item_t * xfs_ail_min(struct xfs_ail *);
-STATIC xfs_log_item_t * xfs_ail_next(struct xfs_ail *, xfs_log_item_t *);
-
 #ifdef DEBUG
-STATIC void xfs_ail_check(struct xfs_ail *, xfs_log_item_t *);
-#else
+/*
+ * Check that the list is sorted as it should be.
+ */
+STATIC void
+xfs_ail_check(
+       struct xfs_ail  *ailp,
+       xfs_log_item_t  *lip)
+{
+       xfs_log_item_t  *prev_lip;
+
+       if (list_empty(&ailp->xa_ail))
+               return;
+
+       /*
+        * Check the next and previous entries are valid.
+        */
+       ASSERT((lip->li_flags & XFS_LI_IN_AIL) != 0);
+       prev_lip = list_entry(lip->li_ail.prev, xfs_log_item_t, li_ail);
+       if (&prev_lip->li_ail != &ailp->xa_ail)
+               ASSERT(XFS_LSN_CMP(prev_lip->li_lsn, lip->li_lsn) <= 0);
+
+       prev_lip = list_entry(lip->li_ail.next, xfs_log_item_t, li_ail);
+       if (&prev_lip->li_ail != &ailp->xa_ail)
+               ASSERT(XFS_LSN_CMP(prev_lip->li_lsn, lip->li_lsn) >= 0);
+
+
+#ifdef XFS_TRANS_DEBUG
+       /*
+        * Walk the list checking lsn ordering, and that every entry has the
+        * XFS_LI_IN_AIL flag set. This is really expensive, so only do it
+        * when specifically debugging the transaction subsystem.
+        */
+       prev_lip = list_entry(&ailp->xa_ail, xfs_log_item_t, li_ail);
+       list_for_each_entry(lip, &ailp->xa_ail, li_ail) {
+               if (&prev_lip->li_ail != &ailp->xa_ail)
+                       ASSERT(XFS_LSN_CMP(prev_lip->li_lsn, lip->li_lsn) <= 0);
+               ASSERT((lip->li_flags & XFS_LI_IN_AIL) != 0);
+               prev_lip = lip;
+       }
+#endif /* XFS_TRANS_DEBUG */
+}
+#else /* !DEBUG */
 #define        xfs_ail_check(a,l)
 #endif /* DEBUG */
 
+/*
+ * Return a pointer to the first item in the AIL.  If the AIL is empty, then
+ * return NULL.
+ */
+static xfs_log_item_t *
+xfs_ail_min(
+       struct xfs_ail  *ailp)
+{
+       if (list_empty(&ailp->xa_ail))
+               return NULL;
+
+       return list_first_entry(&ailp->xa_ail, xfs_log_item_t, li_ail);
+}
+
+ /*
+ * Return a pointer to the last item in the AIL.  If the AIL is empty, then
+ * return NULL.
+ */
+static xfs_log_item_t *
+xfs_ail_max(
+       struct xfs_ail  *ailp)
+{
+       if (list_empty(&ailp->xa_ail))
+               return NULL;
+
+       return list_entry(ailp->xa_ail.prev, xfs_log_item_t, li_ail);
+}
 
 /*
- * This is called by the log manager code to determine the LSN
- * of the tail of the log.  This is exactly the LSN of the first
- * item in the AIL.  If the AIL is empty, then this function
- * returns 0.
+ * Return a pointer to the item which follows the given item in the AIL.  If
+ * the given item is the last item in the list, then return NULL.
+ */
+static xfs_log_item_t *
+xfs_ail_next(
+       struct xfs_ail  *ailp,
+       xfs_log_item_t  *lip)
+{
+       if (lip->li_ail.next == &ailp->xa_ail)
+               return NULL;
+
+       return list_first_entry(&lip->li_ail, xfs_log_item_t, li_ail);
+}
+
+/*
+ * This is called by the log manager code to determine the LSN of the tail of
+ * the log.  This is exactly the LSN of the first item in the AIL.  If the AIL
+ * is empty, then this function returns 0.
  *
- * We need the AIL lock in order to get a coherent read of the
- * lsn of the last item in the AIL.
+ * We need the AIL lock in order to get a coherent read of the lsn of the last
+ * item in the AIL.
  */
 xfs_lsn_t
-xfs_trans_ail_tail(
+xfs_ail_min_lsn(
        struct xfs_ail  *ailp)
 {
-       xfs_lsn_t       lsn;
+       xfs_lsn_t       lsn = 0;
        xfs_log_item_t  *lip;
 
        spin_lock(&ailp->xa_lock);
        lip = xfs_ail_min(ailp);
-       if (lip == NULL) {
-               lsn = (xfs_lsn_t)0;
-       } else {
+       if (lip)
                lsn = lip->li_lsn;
-       }
        spin_unlock(&ailp->xa_lock);
 
        return lsn;
 }
 
 /*
- * xfs_trans_push_ail
- *
- * This routine is called to move the tail of the AIL forward.  It does this by
- * trying to flush items in the AIL whose lsns are below the given
- * threshold_lsn.
- *
- * the push is run asynchronously in a separate thread, so we return the tail
- * of the log right now instead of the tail after the push. This means we will
- * either continue right away, or we will sleep waiting on the async thread to
- * do its work.
- *
- * We do this unlocked - we only need to know whether there is anything in the
- * AIL at the time we are called. We don't need to access the contents of
- * any of the objects, so the lock is not needed.
+ * Return the maximum lsn held in the AIL, or zero if the AIL is empty.
  */
-void
-xfs_trans_ail_push(
-       struct xfs_ail  *ailp,
-       xfs_lsn_t       threshold_lsn)
+static xfs_lsn_t
+xfs_ail_max_lsn(
+       struct xfs_ail  *ailp)
 {
-       xfs_log_item_t  *lip;
+       xfs_lsn_t       lsn = 0;
+       xfs_log_item_t  *lip;
 
-       lip = xfs_ail_min(ailp);
-       if (lip && !XFS_FORCED_SHUTDOWN(ailp->xa_mount)) {
-               if (XFS_LSN_CMP(threshold_lsn, ailp->xa_target) > 0)
-                       xfsaild_wakeup(ailp, threshold_lsn);
-       }
+       spin_lock(&ailp->xa_lock);
+       lip = xfs_ail_max(ailp);
+       if (lip)
+               lsn = lip->li_lsn;
+       spin_unlock(&ailp->xa_lock);
+
+       return lsn;
 }
 
 /*
- * AIL traversal cursor initialisation.
- *
- * The cursor keeps track of where our current traversal is up
- * to by tracking the next ƣtem in the list for us. However, for
- * this to be safe, removing an object from the AIL needs to invalidate
- * any cursor that points to it. hence the traversal cursor needs to
- * be linked to the struct xfs_ail so that deletion can search all the
- * active cursors for invalidation.
- *
- * We don't link the push cursor because it is embedded in the struct
- * xfs_ail and hence easily findable.
+ * The cursor keeps track of where our current traversal is up to by tracking
+ * the next item in the list for us. However, for this to be safe, removing an
+ * object from the AIL needs to invalidate any cursor that points to it. hence
+ * the traversal cursor needs to be linked to the struct xfs_ail so that
+ * deletion can search all the active cursors for invalidation.
  */
 STATIC void
 xfs_trans_ail_cursor_init(
@@ -117,31 +173,12 @@ xfs_trans_ail_cursor_init(
        struct xfs_ail_cursor   *cur)
 {
        cur->item = NULL;
-       if (cur == &ailp->xa_cursors)
-               return;
-
-       cur->next = ailp->xa_cursors.next;
-       ailp->xa_cursors.next = cur;
+       list_add_tail(&cur->list, &ailp->xa_cursors);
 }
 
 /*
- * Set the cursor to the next item, because when we look
- * up the cursor the current item may have been freed.
- */
-STATIC void
-xfs_trans_ail_cursor_set(
-       struct xfs_ail          *ailp,
-       struct xfs_ail_cursor   *cur,
-       struct xfs_log_item     *lip)
-{
-       if (lip)
-               cur->item = xfs_ail_next(ailp, lip);
-}
-
-/*
- * Get the next item in the traversal and advance the cursor.
- * If the cursor was invalidated (inidicated by a lip of 1),
- * restart the traversal.
+ * Get the next item in the traversal and advance the cursor.  If the cursor
+ * was invalidated (indicated by a lip of 1), restart the traversal.
  */
 struct xfs_log_item *
 xfs_trans_ail_cursor_next(
@@ -152,45 +189,31 @@ xfs_trans_ail_cursor_next(
 
        if ((__psint_t)lip & 1)
                lip = xfs_ail_min(ailp);
-       xfs_trans_ail_cursor_set(ailp, cur, lip);
+       if (lip)
+               cur->item = xfs_ail_next(ailp, lip);
        return lip;
 }
 
 /*
- * Now that the traversal is complete, we need to remove the cursor
- * from the list of traversing cursors. Avoid removing the embedded
- * push cursor, but use the fact it is always present to make the
- * list deletion simple.
+ * When the traversal is complete, we need to remove the cursor from the list
+ * of traversing cursors.
  */
 void
 xfs_trans_ail_cursor_done(
        struct xfs_ail          *ailp,
-       struct xfs_ail_cursor   *done)
+       struct xfs_ail_cursor   *cur)
 {
-       struct xfs_ail_cursor   *prev = NULL;
-       struct xfs_ail_cursor   *cur;
-
-       done->item = NULL;
-       if (done == &ailp->xa_cursors)
-               return;
-       prev = &ailp->xa_cursors;
-       for (cur = prev->next; cur; prev = cur, cur = prev->next) {
-               if (cur == done) {
-                       prev->next = cur->next;
-                       break;
-               }
-       }
-       ASSERT(cur);
+       cur->item = NULL;
+       list_del_init(&cur->list);
 }
 
 /*
- * Invalidate any cursor that is pointing to this item. This is
- * called when an item is removed from the AIL. Any cursor pointing
- * to this object is now invalid and the traversal needs to be
- * terminated so it doesn't reference a freed object. We set the
- * cursor item to a value of 1 so we can distinguish between an
- * invalidation and the end of the list when getting the next item
- * from the cursor.
+ * Invalidate any cursor that is pointing to this item. This is called when an
+ * item is removed from the AIL. Any cursor pointing to this object is now
+ * invalid and the traversal needs to be terminated so it doesn't reference a
+ * freed object. We set the low bit of the cursor item pointer so we can
+ * distinguish between an invalidation and the end of the list when getting the
+ * next item from the cursor.
  */
 STATIC void
 xfs_trans_ail_cursor_clear(
@@ -199,8 +222,7 @@ xfs_trans_ail_cursor_clear(
 {
        struct xfs_ail_cursor   *cur;
 
-       /* need to search all cursors */
-       for (cur = &ailp->xa_cursors; cur; cur = cur->next) {
+       list_for_each_entry(cur, &ailp->xa_cursors, list) {
                if (cur->item == lip)
                        cur->item = (struct xfs_log_item *)
                                        ((__psint_t)cur->item | 1);
@@ -208,9 +230,10 @@ xfs_trans_ail_cursor_clear(
 }
 
 /*
- * Return the item in the AIL with the current lsn.
- * Return the current tree generation number for use
- * in calls to xfs_trans_next_ail().
+ * Find the first item in the AIL with the given @lsn by searching in ascending
+ * LSN order and initialise the cursor to point to the next item for a
+ * ascending traversal.  Pass a @lsn of zero to initialise the cursor to the
+ * first item in the AIL. Returns NULL if the list is empty.
  */
 xfs_log_item_t *
 xfs_trans_ail_cursor_first(
@@ -221,50 +244,141 @@ xfs_trans_ail_cursor_first(
        xfs_log_item_t          *lip;
 
        xfs_trans_ail_cursor_init(ailp, cur);
-       lip = xfs_ail_min(ailp);
-       if (lsn == 0)
+
+       if (lsn == 0) {
+               lip = xfs_ail_min(ailp);
                goto out;
+       }
 
        list_for_each_entry(lip, &ailp->xa_ail, li_ail) {
                if (XFS_LSN_CMP(lip->li_lsn, lsn) >= 0)
                        goto out;
        }
-       lip = NULL;
+       return NULL;
+
 out:
-       xfs_trans_ail_cursor_set(ailp, cur, lip);
+       if (lip)
+               cur->item = xfs_ail_next(ailp, lip);
        return lip;
 }
 
+static struct xfs_log_item *
+__xfs_trans_ail_cursor_last(
+       struct xfs_ail          *ailp,
+       xfs_lsn_t               lsn)
+{
+       xfs_log_item_t          *lip;
+
+       list_for_each_entry_reverse(lip, &ailp->xa_ail, li_ail) {
+               if (XFS_LSN_CMP(lip->li_lsn, lsn) <= 0)
+                       return lip;
+       }
+       return NULL;
+}
+
 /*
- * xfsaild_push does the work of pushing on the AIL.  Returning a timeout of
- * zero indicates that the caller should sleep until woken.
+ * Find the last item in the AIL with the given @lsn by searching in descending
+ * LSN order and initialise the cursor to point to that item.  If there is no
+ * item with the value of @lsn, then it sets the cursor to the last item with an
+ * LSN lower than @lsn.  Returns NULL if the list is empty.
  */
-long
+struct xfs_log_item *
+xfs_trans_ail_cursor_last(
+       struct xfs_ail          *ailp,
+       struct xfs_ail_cursor   *cur,
+       xfs_lsn_t               lsn)
+{
+       xfs_trans_ail_cursor_init(ailp, cur);
+       cur->item = __xfs_trans_ail_cursor_last(ailp, lsn);
+       return cur->item;
+}
+
+/*
+ * Splice the log item list into the AIL at the given LSN. We splice to the
+ * tail of the given LSN to maintain insert order for push traversals. The
+ * cursor is optional, allowing repeated updates to the same LSN to avoid
+ * repeated traversals.  This should not be called with an empty list.
+ */
+static void
+xfs_ail_splice(
+       struct xfs_ail          *ailp,
+       struct xfs_ail_cursor   *cur,
+       struct list_head        *list,
+       xfs_lsn_t               lsn)
+{
+       struct xfs_log_item     *lip;
+
+       ASSERT(!list_empty(list));
+
+       /*
+        * Use the cursor to determine the insertion point if one is
+        * provided.  If not, or if the one we got is not valid,
+        * find the place in the AIL where the items belong.
+        */
+       lip = cur ? cur->item : NULL;
+       if (!lip || (__psint_t) lip & 1)
+               lip = __xfs_trans_ail_cursor_last(ailp, lsn);
+
+       /*
+        * If a cursor is provided, we know we're processing the AIL
+        * in lsn order, and future items to be spliced in will
+        * follow the last one being inserted now.  Update the
+        * cursor to point to that last item, now while we have a
+        * reliable pointer to it.
+        */
+       if (cur)
+               cur->item = list_entry(list->prev, struct xfs_log_item, li_ail);
+
+       /*
+        * Finally perform the splice.  Unless the AIL was empty,
+        * lip points to the item in the AIL _after_ which the new
+        * items should go.  If lip is null the AIL was empty, so
+        * the new items go at the head of the AIL.
+        */
+       if (lip)
+               list_splice(list, &lip->li_ail);
+       else
+               list_splice(list, &ailp->xa_ail);
+}
+
+/*
+ * Delete the given item from the AIL.  Return a pointer to the item.
+ */
+static void
+xfs_ail_delete(
+       struct xfs_ail  *ailp,
+       xfs_log_item_t  *lip)
+{
+       xfs_ail_check(ailp, lip);
+       list_del(&lip->li_ail);
+       xfs_trans_ail_cursor_clear(ailp, lip);
+}
+
+static long
 xfsaild_push(
-       struct xfs_ail  *ailp,
-       xfs_lsn_t       *last_lsn)
+       struct xfs_ail          *ailp)
 {
-       long            tout = 0;
-       xfs_lsn_t       last_pushed_lsn = *last_lsn;
-       xfs_lsn_t       target =  ailp->xa_target;
-       xfs_lsn_t       lsn;
-       xfs_log_item_t  *lip;
-       int             flush_log, count, stuck;
-       xfs_mount_t     *mp = ailp->xa_mount;
-       struct xfs_ail_cursor   *cur = &ailp->xa_cursors;
-       int             push_xfsbufd = 0;
+       xfs_mount_t             *mp = ailp->xa_mount;
+       struct xfs_ail_cursor   cur;
+       xfs_log_item_t          *lip;
+       xfs_lsn_t               lsn;
+       xfs_lsn_t               target;
+       long                    tout = 10;
+       int                     flush_log = 0;
+       int                     stuck = 0;
+       int                     count = 0;
+       int                     push_xfsbufd = 0;
 
        spin_lock(&ailp->xa_lock);
-       xfs_trans_ail_cursor_init(ailp, cur);
-       lip = xfs_trans_ail_cursor_first(ailp, cur, *last_lsn);
+       target = ailp->xa_target;
+       lip = xfs_trans_ail_cursor_first(ailp, &cur, ailp->xa_last_pushed_lsn);
        if (!lip || XFS_FORCED_SHUTDOWN(mp)) {
                /*
                 * AIL is empty or our push has reached the end.
                 */
-               xfs_trans_ail_cursor_done(ailp, cur);
+               xfs_trans_ail_cursor_done(ailp, &cur);
                spin_unlock(&ailp->xa_lock);
-               *last_lsn = 0;
-               return tout;
+               goto out_done;
        }
 
        XFS_STATS_INC(xs_push_ail);
@@ -281,8 +395,7 @@ xfsaild_push(
         * lots of contention on the AIL lists.
         */
        lsn = lip->li_lsn;
-       flush_log = stuck = count = 0;
-       while ((XFS_LSN_CMP(lip->li_lsn, target) < 0)) {
+       while ((XFS_LSN_CMP(lip->li_lsn, target) <= 0)) {
                int     lock_result;
                /*
                 * If we can lock the item without sleeping, unlock the AIL
@@ -301,13 +414,18 @@ xfsaild_push(
                case XFS_ITEM_SUCCESS:
                        XFS_STATS_INC(xs_push_ail_success);
                        IOP_PUSH(lip);
-                       last_pushed_lsn = lsn;
+                       ailp->xa_last_pushed_lsn = lsn;
                        break;
 
                case XFS_ITEM_PUSHBUF:
                        XFS_STATS_INC(xs_push_ail_pushbuf);
-                       IOP_PUSHBUF(lip);
-                       last_pushed_lsn = lsn;
+
+                       if (!IOP_PUSHBUF(lip)) {
+                               stuck++;
+                               flush_log = 1;
+                       } else {
+                               ailp->xa_last_pushed_lsn = lsn;
+                       }
                        push_xfsbufd = 1;
                        break;
 
@@ -319,7 +437,6 @@ xfsaild_push(
 
                case XFS_ITEM_LOCKED:
                        XFS_STATS_INC(xs_push_ail_locked);
-                       last_pushed_lsn = lsn;
                        stuck++;
                        break;
 
@@ -351,12 +468,12 @@ xfsaild_push(
                if (stuck > 100)
                        break;
 
-               lip = xfs_trans_ail_cursor_next(ailp, cur);
+               lip = xfs_trans_ail_cursor_next(ailp, &cur);
                if (lip == NULL)
                        break;
                lsn = lip->li_lsn;
        }
-       xfs_trans_ail_cursor_done(ailp, cur);
+       xfs_trans_ail_cursor_done(ailp, &cur);
        spin_unlock(&ailp->xa_lock);
 
        if (flush_log) {
@@ -374,9 +491,13 @@ xfsaild_push(
                wake_up_process(mp->m_ddev_targp->bt_task);
        }
 
+       /* assume we have more work to do in a short while */
+out_done:
        if (!count) {
                /* We're past our target or empty, so idle */
-               last_pushed_lsn = 0;
+               ailp->xa_last_pushed_lsn = 0;
+
+               tout = 50;
        } else if (XFS_LSN_CMP(lsn, target) >= 0) {
                /*
                 * We reached the target so wait a bit longer for I/O to
@@ -384,7 +505,7 @@ xfsaild_push(
                 * start the next scan from the start of the AIL.
                 */
                tout = 50;
-               last_pushed_lsn = 0;
+               ailp->xa_last_pushed_lsn = 0;
        } else if ((stuck * 100) / count > 90) {
                /*
                 * Either there is a lot of contention on the AIL or we
@@ -396,14 +517,83 @@ xfsaild_push(
                 * continuing from where we were.
                 */
                tout = 20;
-       } else {
-               /* more to do, but wait a short while before continuing */
-               tout = 10;
        }
-       *last_lsn = last_pushed_lsn;
+
        return tout;
 }
 
+static int
+xfsaild(
+       void            *data)
+{
+       struct xfs_ail  *ailp = data;
+       long            tout = 0;       /* milliseconds */
+
+       while (!kthread_should_stop()) {
+               if (tout && tout <= 20)
+                       __set_current_state(TASK_KILLABLE);
+               else
+                       __set_current_state(TASK_INTERRUPTIBLE);
+               schedule_timeout(tout ?
+                                msecs_to_jiffies(tout) : MAX_SCHEDULE_TIMEOUT);
+
+               try_to_freeze();
+
+               tout = xfsaild_push(ailp);
+       }
+
+       return 0;
+}
+
+/*
+ * This routine is called to move the tail of the AIL forward.  It does this by
+ * trying to flush items in the AIL whose lsns are below the given
+ * threshold_lsn.
+ *
+ * The push is run asynchronously in a workqueue, which means the caller needs
+ * to handle waiting on the async flush for space to become available.
+ * We don't want to interrupt any push that is in progress, hence we only queue
+ * work if we set the pushing bit approriately.
+ *
+ * We do this unlocked - we only need to know whether there is anything in the
+ * AIL at the time we are called. We don't need to access the contents of
+ * any of the objects, so the lock is not needed.
+ */
+void
+xfs_ail_push(
+       struct xfs_ail  *ailp,
+       xfs_lsn_t       threshold_lsn)
+{
+       xfs_log_item_t  *lip;
+
+       lip = xfs_ail_min(ailp);
+       if (!lip || XFS_FORCED_SHUTDOWN(ailp->xa_mount) ||
+           XFS_LSN_CMP(threshold_lsn, ailp->xa_target) <= 0)
+               return;
+
+       /*
+        * Ensure that the new target is noticed in push code before it clears
+        * the XFS_AIL_PUSHING_BIT.
+        */
+       smp_wmb();
+       xfs_trans_ail_copy_lsn(ailp, &ailp->xa_target, &threshold_lsn);
+       smp_wmb();
+
+       wake_up_process(ailp->xa_task);
+}
+
+/*
+ * Push out all items in the AIL immediately
+ */
+void
+xfs_ail_push_all(
+       struct xfs_ail  *ailp)
+{
+       xfs_lsn_t       threshold_lsn = xfs_ail_max_lsn(ailp);
+
+       if (threshold_lsn)
+               xfs_ail_push(ailp, threshold_lsn);
+}
 
 /*
  * This is to be called when an item is unlocked that may have
@@ -449,123 +639,155 @@ xfs_trans_unlocked_item(
                xfs_log_move_tail(ailp->xa_mount, 1);
 }      /* xfs_trans_unlocked_item */
 
-
 /*
- * Update the position of the item in the AIL with the new
- * lsn.  If it is not yet in the AIL, add it.  Otherwise, move
- * it to its new position by removing it and re-adding it.
+ * xfs_trans_ail_update - bulk AIL insertion operation.
+ *
+ * @xfs_trans_ail_update takes an array of log items that all need to be
+ * positioned at the same LSN in the AIL. If an item is not in the AIL, it will
+ * be added.  Otherwise, it will be repositioned  by removing it and re-adding
+ * it to the AIL. If we move the first item in the AIL, update the log tail to
+ * match the new minimum LSN in the AIL.
+ *
+ * This function takes the AIL lock once to execute the update operations on
+ * all the items in the array, and as such should not be called with the AIL
+ * lock held. As a result, once we have the AIL lock, we need to check each log
+ * item LSN to confirm it needs to be moved forward in the AIL.
  *
- * Wakeup anyone with an lsn less than the item's lsn.  If the item
- * we move in the AIL is the minimum one, update the tail lsn in the
- * log manager.
+ * To optimise the insert operation, we delete all the items from the AIL in
+ * the first pass, moving them into a temporary list, then splice the temporary
+ * list into the correct position in the AIL. This avoids needing to do an
+ * insert operation on every item.
  *
- * This function must be called with the AIL lock held.  The lock
- * is dropped before returning.
+ * This function must be called with the AIL lock held.  The lock is dropped
+ * before returning.
  */
 void
-xfs_trans_ail_update(
-       struct xfs_ail  *ailp,
-       xfs_log_item_t  *lip,
-       xfs_lsn_t       lsn) __releases(ailp->xa_lock)
+xfs_trans_ail_update_bulk(
+       struct xfs_ail          *ailp,
+       struct xfs_ail_cursor   *cur,
+       struct xfs_log_item     **log_items,
+       int                     nr_items,
+       xfs_lsn_t               lsn) __releases(ailp->xa_lock)
 {
-       xfs_log_item_t          *mlip;  /* ptr to minimum lip */
+       xfs_log_item_t          *mlip;
        xfs_lsn_t               tail_lsn;
+       int                     mlip_changed = 0;
+       int                     i;
+       LIST_HEAD(tmp);
 
+       ASSERT(nr_items > 0);           /* Not required, but true. */
        mlip = xfs_ail_min(ailp);
 
-       if (lip->li_flags & XFS_LI_IN_AIL) {
-               xfs_ail_delete(ailp, lip);
-       } else {
-               lip->li_flags |= XFS_LI_IN_AIL;
+       for (i = 0; i < nr_items; i++) {
+               struct xfs_log_item *lip = log_items[i];
+               if (lip->li_flags & XFS_LI_IN_AIL) {
+                       /* check if we really need to move the item */
+                       if (XFS_LSN_CMP(lsn, lip->li_lsn) <= 0)
+                               continue;
+
+                       xfs_ail_delete(ailp, lip);
+                       if (mlip == lip)
+                               mlip_changed = 1;
+               } else {
+                       lip->li_flags |= XFS_LI_IN_AIL;
+               }
+               lip->li_lsn = lsn;
+               list_add(&lip->li_ail, &tmp);
        }
 
-       lip->li_lsn = lsn;
-       xfs_ail_insert(ailp, lip);
+       if (!list_empty(&tmp))
+               xfs_ail_splice(ailp, cur, &tmp, lsn);
 
-       if (mlip == lip) {
-               mlip = xfs_ail_min(ailp);
-               /*
-                * It is not safe to access mlip after the AIL lock is
-                * dropped, so we must get a copy of li_lsn before we do
-                * so.  This is especially important on 32-bit platforms
-                * where accessing and updating 64-bit values like li_lsn
-                * is not atomic.
-                */
-               tail_lsn = mlip->li_lsn;
-               spin_unlock(&ailp->xa_lock);
-               xfs_log_move_tail(ailp->xa_mount, tail_lsn);
-       } else {
+       if (!mlip_changed) {
                spin_unlock(&ailp->xa_lock);
+               return;
        }
 
-
-}      /* xfs_trans_update_ail */
+       /*
+        * It is not safe to access mlip after the AIL lock is dropped, so we
+        * must get a copy of li_lsn before we do so.  This is especially
+        * important on 32-bit platforms where accessing and updating 64-bit
+        * values like li_lsn is not atomic.
+        */
+       mlip = xfs_ail_min(ailp);
+       tail_lsn = mlip->li_lsn;
+       spin_unlock(&ailp->xa_lock);
+       xfs_log_move_tail(ailp->xa_mount, tail_lsn);
+}
 
 /*
- * Delete the given item from the AIL.  It must already be in
- * the AIL.
+ * xfs_trans_ail_delete_bulk - remove multiple log items from the AIL
+ *
+ * @xfs_trans_ail_delete_bulk takes an array of log items that all need to
+ * removed from the AIL. The caller is already holding the AIL lock, and done
+ * all the checks necessary to ensure the items passed in via @log_items are
+ * ready for deletion. This includes checking that the items are in the AIL.
  *
- * Wakeup anyone with an lsn less than item's lsn.    If the item
- * we delete in the AIL is the minimum one, update the tail lsn in the
- * log manager.
+ * For each log item to be removed, unlink it  from the AIL, clear the IN_AIL
+ * flag from the item and reset the item's lsn to 0. If we remove the first
+ * item in the AIL, update the log tail to match the new minimum LSN in the
+ * AIL.
  *
- * Clear the IN_AIL flag from the item, reset its lsn to 0, and
- * bump the AIL's generation count to indicate that the tree
- * has changed.
+ * This function will not drop the AIL lock until all items are removed from
+ * the AIL to minimise the amount of lock traffic on the AIL. This does not
+ * greatly increase the AIL hold time, but does significantly reduce the amount
+ * of traffic on the lock, especially during IO completion.
  *
- * This function must be called with the AIL lock held.  The lock
- * is dropped before returning.
+ * This function must be called with the AIL lock held.  The lock is dropped
+ * before returning.
  */
 void
-xfs_trans_ail_delete(
-       struct xfs_ail  *ailp,
-       xfs_log_item_t  *lip) __releases(ailp->xa_lock)
+xfs_trans_ail_delete_bulk(
+       struct xfs_ail          *ailp,
+       struct xfs_log_item     **log_items,
+       int                     nr_items) __releases(ailp->xa_lock)
 {
        xfs_log_item_t          *mlip;
        xfs_lsn_t               tail_lsn;
+       int                     mlip_changed = 0;
+       int                     i;
 
-       if (lip->li_flags & XFS_LI_IN_AIL) {
-               mlip = xfs_ail_min(ailp);
-               xfs_ail_delete(ailp, lip);
-
+       mlip = xfs_ail_min(ailp);
 
-               lip->li_flags &= ~XFS_LI_IN_AIL;
-               lip->li_lsn = 0;
+       for (i = 0; i < nr_items; i++) {
+               struct xfs_log_item *lip = log_items[i];
+               if (!(lip->li_flags & XFS_LI_IN_AIL)) {
+                       struct xfs_mount        *mp = ailp->xa_mount;
 
-               if (mlip == lip) {
-                       mlip = xfs_ail_min(ailp);
-                       /*
-                        * It is not safe to access mlip after the AIL lock
-                        * is dropped, so we must get a copy of li_lsn
-                        * before we do so.  This is especially important
-                        * on 32-bit platforms where accessing and updating
-                        * 64-bit values like li_lsn is not atomic.
-                        */
-                       tail_lsn = mlip ? mlip->li_lsn : 0;
-                       spin_unlock(&ailp->xa_lock);
-                       xfs_log_move_tail(ailp->xa_mount, tail_lsn);
-               } else {
                        spin_unlock(&ailp->xa_lock);
+                       if (!XFS_FORCED_SHUTDOWN(mp)) {
+                               xfs_alert_tag(mp, XFS_PTAG_AILDELETE,
+               "%s: attempting to delete a log item that is not in the AIL",
+                                               __func__);
+                               xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_INCORE);
+                       }
+                       return;
                }
+
+               xfs_ail_delete(ailp, lip);
+               lip->li_flags &= ~XFS_LI_IN_AIL;
+               lip->li_lsn = 0;
+               if (mlip == lip)
+                       mlip_changed = 1;
        }
-       else {
-               /*
-                * If the file system is not being shutdown, we are in
-                * serious trouble if we get to this stage.
-                */
-               struct xfs_mount        *mp = ailp->xa_mount;
 
+       if (!mlip_changed) {
                spin_unlock(&ailp->xa_lock);
-               if (!XFS_FORCED_SHUTDOWN(mp)) {
-                       xfs_cmn_err(XFS_PTAG_AILDELETE, CE_ALERT, mp,
-               "%s: attempting to delete a log item that is not in the AIL",
-                                       __func__);
-                       xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_INCORE);
-               }
+               return;
        }
-}
-
 
+       /*
+        * It is not safe to access mlip after the AIL lock is dropped, so we
+        * must get a copy of li_lsn before we do so.  This is especially
+        * important on 32-bit platforms where accessing and updating 64-bit
+        * values like li_lsn is not atomic. It is possible we've emptied the
+        * AIL here, so if that is the case, pass an LSN of 0 to the tail move.
+        */
+       mlip = xfs_ail_min(ailp);
+       tail_lsn = mlip ? mlip->li_lsn : 0;
+       spin_unlock(&ailp->xa_lock);
+       xfs_log_move_tail(ailp->xa_mount, tail_lsn);
+}
 
 /*
  * The active item list (AIL) is a doubly linked list of log
@@ -586,7 +808,6 @@ xfs_trans_ail_init(
        xfs_mount_t     *mp)
 {
        struct xfs_ail  *ailp;
-       int             error;
 
        ailp = kmem_zalloc(sizeof(struct xfs_ail), KM_MAYFAIL);
        if (!ailp)
@@ -594,16 +815,20 @@ xfs_trans_ail_init(
 
        ailp->xa_mount = mp;
        INIT_LIST_HEAD(&ailp->xa_ail);
+       INIT_LIST_HEAD(&ailp->xa_cursors);
        spin_lock_init(&ailp->xa_lock);
-       error = xfsaild_start(ailp);
-       if (error)
+
+       ailp->xa_task = kthread_run(xfsaild, ailp, "xfsaild/%s",
+                       ailp->xa_mount->m_fsname);
+       if (IS_ERR(ailp->xa_task))
                goto out_free_ailp;
+
        mp->m_ail = ailp;
        return 0;
 
 out_free_ailp:
        kmem_free(ailp);
-       return error;
+       return ENOMEM;
 }
 
 void
@@ -612,128 +837,6 @@ xfs_trans_ail_destroy(
 {
        struct xfs_ail  *ailp = mp->m_ail;
 
-       xfsaild_stop(ailp);
+       kthread_stop(ailp->xa_task);
        kmem_free(ailp);
 }
-
-/*
- * Insert the given log item into the AIL.
- * We almost always insert at the end of the list, so on inserts
- * we search from the end of the list to find where the
- * new item belongs.
- */
-STATIC void
-xfs_ail_insert(
-       struct xfs_ail  *ailp,
-       xfs_log_item_t  *lip)
-{
-       xfs_log_item_t  *next_lip;
-
-       /*
-        * If the list is empty, just insert the item.
-        */
-       if (list_empty(&ailp->xa_ail)) {
-               list_add(&lip->li_ail, &ailp->xa_ail);
-               return;
-       }
-
-       list_for_each_entry_reverse(next_lip, &ailp->xa_ail, li_ail) {
-               if (XFS_LSN_CMP(next_lip->li_lsn, lip->li_lsn) <= 0)
-                       break;
-       }
-
-       ASSERT((&next_lip->li_ail == &ailp->xa_ail) ||
-              (XFS_LSN_CMP(next_lip->li_lsn, lip->li_lsn) <= 0));
-
-       list_add(&lip->li_ail, &next_lip->li_ail);
-
-       xfs_ail_check(ailp, lip);
-       return;
-}
-
-/*
- * Delete the given item from the AIL.  Return a pointer to the item.
- */
-STATIC void
-xfs_ail_delete(
-       struct xfs_ail  *ailp,
-       xfs_log_item_t  *lip)
-{
-       xfs_ail_check(ailp, lip);
-       list_del(&lip->li_ail);
-       xfs_trans_ail_cursor_clear(ailp, lip);
-}
-
-/*
- * Return a pointer to the first item in the AIL.
- * If the AIL is empty, then return NULL.
- */
-STATIC xfs_log_item_t *
-xfs_ail_min(
-       struct xfs_ail  *ailp)
-{
-       if (list_empty(&ailp->xa_ail))
-               return NULL;
-
-       return list_first_entry(&ailp->xa_ail, xfs_log_item_t, li_ail);
-}
-
-/*
- * Return a pointer to the item which follows
- * the given item in the AIL.  If the given item
- * is the last item in the list, then return NULL.
- */
-STATIC xfs_log_item_t *
-xfs_ail_next(
-       struct xfs_ail  *ailp,
-       xfs_log_item_t  *lip)
-{
-       if (lip->li_ail.next == &ailp->xa_ail)
-               return NULL;
-
-       return list_first_entry(&lip->li_ail, xfs_log_item_t, li_ail);
-}
-
-#ifdef DEBUG
-/*
- * Check that the list is sorted as it should be.
- */
-STATIC void
-xfs_ail_check(
-       struct xfs_ail  *ailp,
-       xfs_log_item_t  *lip)
-{
-       xfs_log_item_t  *prev_lip;
-
-       if (list_empty(&ailp->xa_ail))
-               return;
-
-       /*
-        * Check the next and previous entries are valid.
-        */
-       ASSERT((lip->li_flags & XFS_LI_IN_AIL) != 0);
-       prev_lip = list_entry(lip->li_ail.prev, xfs_log_item_t, li_ail);
-       if (&prev_lip->li_ail != &ailp->xa_ail)
-               ASSERT(XFS_LSN_CMP(prev_lip->li_lsn, lip->li_lsn) <= 0);
-
-       prev_lip = list_entry(lip->li_ail.next, xfs_log_item_t, li_ail);
-       if (&prev_lip->li_ail != &ailp->xa_ail)
-               ASSERT(XFS_LSN_CMP(prev_lip->li_lsn, lip->li_lsn) >= 0);
-
-
-#ifdef XFS_TRANS_DEBUG
-       /*
-        * Walk the list checking lsn ordering, and that every entry has the
-        * XFS_LI_IN_AIL flag set. This is really expensive, so only do it
-        * when specifically debugging the transaction subsystem.
-        */
-       prev_lip = list_entry(&ailp->xa_ail, xfs_log_item_t, li_ail);
-       list_for_each_entry(lip, &ailp->xa_ail, li_ail) {
-               if (&prev_lip->li_ail != &ailp->xa_ail)
-                       ASSERT(XFS_LSN_CMP(prev_lip->li_lsn, lip->li_lsn) <= 0);
-               ASSERT((lip->li_flags & XFS_LI_IN_AIL) != 0);
-               prev_lip = lip;
-       }
-#endif /* XFS_TRANS_DEBUG */
-}
-#endif /* DEBUG */