]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
gfs2: gfs2_log_flush withdraw fixes
authorAndreas Gruenbacher <agruenba@redhat.com>
Sun, 5 Apr 2026 14:33:36 +0000 (16:33 +0200)
committerAndreas Gruenbacher <agruenba@redhat.com>
Tue, 7 Apr 2026 20:20:00 +0000 (22:20 +0200)
When a withdraw occurs in gfs2_log_flush() and we are left with an unsubmitted
bio, fail that bio.  Otherwise, the bh's in that bio will remain locked and
gfs2_evict_inode() -> truncate_inode_pages() -> gfs2_invalidate_folio() ->
gfs2_discard() will hang trying to discard the locked bh's.

In addition, when gfs2_log_flush() fails to submit a new transaction, unpin the
buffers in the failing transaction like gfs2_remove_from_journal() does.  If
any of the bd's are on the ail2 list, leave them there and do_withdraw() ->
gfs2_withdraw_glocks() -> inode_go_inval() -> truncate_inode_pages() ->
gfs2_invalidate_folio() -> gfs2_discard() will remove them.  They will be freed
in gfs2_release_folio().

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
fs/gfs2/log.c

index a96f9b9331e8045c419dc6fa6c993191afca2654..3a01d4e7667a1054757f1529fe471fe7159ac8ec 100644 (file)
@@ -983,33 +983,38 @@ static void empty_ail1_list(struct gfs2_sbd *sdp)
        }
 }
 
-static void gfs2_trans_drain_list(struct list_head *list)
+static void gfs2_trans_drain_list(struct gfs2_sbd *sdp, struct list_head *list)
 {
        struct gfs2_bufdata *bd;
 
        while (!list_empty(list)) {
                bd = list_first_entry(list, struct gfs2_bufdata, bd_list);
+               struct buffer_head *bh = bd->bd_bh;
+
+               WARN_ON_ONCE(!buffer_pinned(bh));
+               clear_buffer_pinned(bh);
+               trace_gfs2_pin(bd, 0);
+               atomic_dec(&sdp->sd_log_pinned);
                list_del_init(&bd->bd_list);
-               if (!list_empty(&bd->bd_ail_st_list))
-                       gfs2_remove_from_ail(bd);
-               kmem_cache_free(gfs2_bufdata_cachep, bd);
+               brelse(bh);
        }
 }
 
 /**
  * gfs2_trans_drain - drain the buf and databuf queue for a failed transaction
+ * @sdp: the filesystem
  * @tr: the transaction to drain
  *
  * When this is called, we're taking an error exit for a log write that failed
  * but since we bypassed the after_commit functions, we need to remove the
  * items from the buf and databuf queue.
  */
-static void gfs2_trans_drain(struct gfs2_trans *tr)
+static void gfs2_trans_drain(struct gfs2_sbd *sdp, struct gfs2_trans *tr)
 {
        if (!tr)
                return;
-       gfs2_trans_drain_list(&tr->tr_buf);
-       gfs2_trans_drain_list(&tr->tr_databuf);
+       gfs2_trans_drain_list(sdp, &tr->tr_buf);
+       gfs2_trans_drain_list(sdp, &tr->tr_databuf);
 }
 
 void gfs2_remove_from_journal(struct buffer_head *bh, int meta)
@@ -1185,7 +1190,11 @@ out:
        return;
 
 out_withdraw:
-       gfs2_trans_drain(tr);
+       if (sdp->sd_jdesc->jd_log_bio) {
+               bio_io_error(sdp->sd_jdesc->jd_log_bio);
+               sdp->sd_jdesc->jd_log_bio = NULL;
+       }
+       gfs2_trans_drain(sdp, tr);
        /**
         * If the tr_list is empty, we're withdrawing during a log
         * flush that targets a transaction, but the transaction was