/* local state machine functions */
 STATIC void xlog_state_done_syncing(
        struct xlog_in_core     *iclog);
+STATIC void xlog_state_do_callback(
+       struct xlog             *log);
 STATIC int
 xlog_state_get_iclog_space(
        struct xlog             *log,
  * space waiters so they can process the newly set shutdown state. We really
  * don't care what order we process callbacks here because the log is shut down
  * and so state cannot change on disk anymore.
+ *
+ * We avoid processing actively referenced iclogs so that we don't run callbacks
+ * while the iclog owner might still be preparing the iclog for IO submssion.
+ * These will be caught by xlog_state_iclog_release() and call this function
+ * again to process any callbacks that may have been added to that iclog.
  */
 static void
 xlog_state_shutdown_callbacks(
        spin_lock(&log->l_icloglock);
        iclog = log->l_iclog;
        do {
+               if (atomic_read(&iclog->ic_refcnt)) {
+                       /* Reference holder will re-run iclog callbacks. */
+                       continue;
+               }
                list_splice_init(&iclog->ic_callbacks, &cb_list);
+               wake_up_all(&iclog->ic_write_wait);
                wake_up_all(&iclog->ic_force_wait);
        } while ((iclog = iclog->ic_next) != log->l_iclog);
 
        xfs_lsn_t               old_tail_lsn)
 {
        xfs_lsn_t               tail_lsn;
+       bool                    last_ref;
+
        lockdep_assert_held(&log->l_icloglock);
 
        trace_xlog_iclog_release(iclog, _RET_IP_);
-       if (xlog_is_shutdown(log))
-               return -EIO;
-
        /*
         * Grabbing the current log tail needs to be atomic w.r.t. the writing
         * of the tail LSN into the iclog so we guarantee that the log tail does
                        iclog->ic_header.h_tail_lsn = cpu_to_be64(tail_lsn);
        }
 
-       if (!atomic_dec_and_test(&iclog->ic_refcnt))
+       last_ref = atomic_dec_and_test(&iclog->ic_refcnt);
+
+       if (xlog_is_shutdown(log)) {
+               /*
+                * If there are no more references to this iclog, process the
+                * pending iclog callbacks that were waiting on the release of
+                * this iclog.
+                */
+               if (last_ref) {
+                       spin_unlock(&log->l_icloglock);
+                       xlog_state_shutdown_callbacks(log);
+                       spin_lock(&log->l_icloglock);
+               }
+               return -EIO;
+       }
+
+       if (!last_ref)
                return 0;
 
        if (iclog->ic_state != XLOG_STATE_WANT_SYNC) {
 
        xfs_log_ticket_ungrant(log, tic);
 
        /*
-        * Once we attach the ctx to the iclog, a shutdown can process the
-        * iclog, run the callbacks and free the ctx. The only thing preventing
-        * this potential UAF situation here is that we are holding the
-        * icloglock. Hence we cannot access the ctx once we have attached the
-        * callbacks and dropped the icloglock.
+        * Once we attach the ctx to the iclog, it is effectively owned by the
+        * iclog and we can only use it while we still have an active reference
+        * to the iclog. i.e. once we call xlog_state_release_iclog() we can no
+        * longer safely reference the ctx.
         */
        spin_lock(&log->l_icloglock);
        if (xlog_is_shutdown(log)) {
         * wakeup until this commit_iclog is written to disk.  Hence we use the
         * iclog header lsn and compare it to the commit lsn to determine if we
         * need to wait on iclogs or not.
-        *
-        * NOTE: It is not safe to reference the ctx after this check as we drop
-        * the icloglock if we have to wait for completion of other iclogs.
         */
        if (ctx->start_lsn != commit_lsn) {
                xfs_lsn_t       plsn;
         */
        commit_iclog->ic_flags |= XLOG_ICL_NEED_FUA;
        xlog_state_release_iclog(log, commit_iclog, preflush_tail_lsn);
+
+       /* Not safe to reference ctx now! */
+
        spin_unlock(&log->l_icloglock);
        return;