diff options
| author | Andreas Gruenbacher <agruenba@redhat.com> | 2026-04-03 15:42:18 +0300 |
|---|---|---|
| committer | Andreas Gruenbacher <agruenba@redhat.com> | 2026-04-07 23:19:59 +0300 |
| commit | f4e4c4e6acdc20a9065064dd164db52e2e0d44ad (patch) | |
| tree | 211aaebfbecaae62f8dd4806e8fdf6cc02ff6c7d | |
| parent | 7d2da6ed172680a7ef06acfe3e44a2326977573f (diff) | |
| download | linux-f4e4c4e6acdc20a9065064dd164db52e2e0d44ad.tar.xz | |
gfs2: fix address space truncation during withdraw
When a withdrawn filesystem's inodes are being evicted, the address spaces of
those inodes still need to be truncated but we can no longer start new
transactions. We still don't want gfs2_invalidate_folio() to race with
gfs2_log_flush(), so take a read lock on sdp->sd_log_flush_lock in that case.
(It may not be obvious, but gfs2_invalidate_folio() is a jdata-only address
space operation.)
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
| -rw-r--r-- | fs/gfs2/log.c | 17 | ||||
| -rw-r--r-- | fs/gfs2/super.c | 41 |
2 files changed, 39 insertions, 19 deletions
diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c index 8397d34527a4..31ee7a0e86a2 100644 --- a/fs/gfs2/log.c +++ b/fs/gfs2/log.c @@ -1024,17 +1024,22 @@ void gfs2_remove_from_journal(struct buffer_head *bh, int meta) trace_gfs2_pin(bd, 0); atomic_dec(&sdp->sd_log_pinned); list_del_init(&bd->bd_list); - if (meta == REMOVE_META) - tr->tr_num_buf_rm++; - else - tr->tr_num_databuf_rm++; - set_bit(TR_TOUCHED, &tr->tr_flags); + if (tr) { + if (meta == REMOVE_META) + tr->tr_num_buf_rm++; + else + tr->tr_num_databuf_rm++; + set_bit(TR_TOUCHED, &tr->tr_flags); + } was_pinned = 1; brelse(bh); } if (bd) { if (bd->bd_tr) { - gfs2_trans_add_revoke(sdp, bd); + if (tr) + gfs2_trans_add_revoke(sdp, bd); + else + gfs2_remove_from_ail(bd); } else if (was_pinned) { bh->b_private = NULL; kmem_cache_free(gfs2_bufdata_cachep, bd); diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c index e4219a04d16e..83b5bab56377 100644 --- a/fs/gfs2/super.c +++ b/fs/gfs2/super.c @@ -1339,27 +1339,44 @@ static int gfs2_truncate_inode_pages(struct inode *inode) struct gfs2_sbd *sdp = GFS2_SB(inode); struct address_space *mapping = &inode->i_data; bool need_trans = gfs2_is_jdata(ip) && mapping->nrpages; - int ret; + int ret = 0; /* * Truncating a jdata inode address space may create revokes in * truncate_inode_pages() -> gfs2_invalidate_folio() -> ... -> * gfs2_remove_from_journal(), so we need a transaction here. * - * FIXME: During a withdraw, no new transactions can be created. - * In that case, we skip the truncate, but that doesn't help because - * truncate_inode_pages_final() will then call gfs2_invalidate_folio() - * again, and outside of a transaction. + * During a withdraw, no new transactions can be created. We still + * take the log flush lock to prevent truncate from racing with + * gfs2_log_flush(). */ if (need_trans) { ret = gfs2_trans_begin(sdp, 0, sdp->sd_jdesc->jd_blocks); if (ret) - return ret; + down_read(&sdp->sd_log_flush_lock); } truncate_inode_pages(mapping, 0); - if (need_trans) - gfs2_trans_end(sdp); - return 0; + if (need_trans) { + if (ret) + up_read(&sdp->sd_log_flush_lock); + else + gfs2_trans_end(sdp); + } + return ret; +} + +static void gfs2_truncate_inode_pages_final(struct inode *inode) +{ + struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_sbd *sdp = GFS2_SB(inode); + struct address_space *mapping = &inode->i_data; + bool need_lock = gfs2_is_jdata(ip) && mapping->nrpages; + + if (need_lock) + down_read(&sdp->sd_log_flush_lock); + truncate_inode_pages_final(mapping); + if (need_lock) + up_read(&sdp->sd_log_flush_lock); } /* @@ -1398,10 +1415,8 @@ static int evict_linked_inode(struct inode *inode, struct gfs2_holder *gh) clean: ret = gfs2_truncate_inode_pages(inode); - if (ret) - return ret; truncate_inode_pages(metamapping, 0); - return 0; + return ret; } /** @@ -1472,7 +1487,7 @@ static void gfs2_evict_inode(struct inode *inode) out: if (gfs2_holder_initialized(&gh)) gfs2_glock_dq_uninit(&gh); - truncate_inode_pages_final(&inode->i_data); + gfs2_truncate_inode_pages_final(inode); if (ip->i_qadata) gfs2_assert_warn(sdp, ip->i_qadata->qa_ref == 0); gfs2_rs_deltree(&ip->i_res); |
