From: Andreas Gruenbacher Date: Sun, 5 Apr 2026 14:33:36 +0000 (+0200) Subject: gfs2: gfs2_log_flush withdraw fixes X-Git-Tag: v7.1-rc1~137^2~4 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=bb47cce7a1eea1d9d165260328270ddc39e19526;p=thirdparty%2Fkernel%2Flinux.git gfs2: gfs2_log_flush withdraw fixes 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 --- diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c index a96f9b9331e80..3a01d4e7667a1 100644 --- a/fs/gfs2/log.c +++ b/fs/gfs2/log.c @@ -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