mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00

Unmount of a shutdown filesystem can hang with stale inode cluster buffers in the AIL like so: [95964.140623] Call Trace: [95964.144641] __schedule+0x699/0xb70 [95964.154003] schedule+0x64/0xd0 [95964.156851] xfs_ail_push_all_sync+0x9b/0xf0 [95964.164816] xfs_unmount_flush_inodes+0x41/0x70 [95964.168698] xfs_unmountfs+0x7f/0x170 [95964.171846] xfs_fs_put_super+0x3b/0x90 [95964.175216] generic_shutdown_super+0x77/0x160 [95964.178060] kill_block_super+0x1b/0x40 [95964.180553] xfs_kill_sb+0x12/0x30 [95964.182796] deactivate_locked_super+0x38/0x100 [95964.185735] deactivate_super+0x41/0x50 [95964.188245] cleanup_mnt+0x9f/0x160 [95964.190519] __cleanup_mnt+0x12/0x20 [95964.192899] task_work_run+0x89/0xb0 [95964.195221] resume_user_mode_work+0x4f/0x60 [95964.197931] syscall_exit_to_user_mode+0x76/0xb0 [95964.201003] do_syscall_64+0x74/0x130 $ pstree -N mnt |grep umount |-check-parallel---nsexec---run_test.sh---753---umount It always seems to be generic/753 that triggers this, and repeating a quick group test run triggers it every 10-15 iterations. Hence it generally triggers once up every 30-40 minutes of test time. just running generic/753 by itself or concurrently with a limited group of tests doesn't reproduce this issue at all. Tracing on a hung system shows the AIL repeating every 50ms a log force followed by an attempt to push pinned, aborted inodes from the AIL (trimmed for brevity): xfs_log_force: lsn 0x1c caller xfsaild+0x18e xfs_log_force: lsn 0x0 caller xlog_cil_flush+0xbd xfs_log_force: lsn 0x1c caller xfs_log_force+0x77 xfs_ail_pinned: lip 0xffff88826014afa0 lsn 1/37472 type XFS_LI_INODE flags IN_AIL|ABORTED xfs_ail_pinned: lip 0xffff88814000a708 lsn 1/37472 type XFS_LI_INODE flags IN_AIL|ABORTED xfs_ail_pinned: lip 0xffff88810b850c80 lsn 1/37472 type XFS_LI_INODE flags IN_AIL|ABORTED xfs_ail_pinned: lip 0xffff88810b850af0 lsn 1/37472 type XFS_LI_INODE flags IN_AIL|ABORTED xfs_ail_pinned: lip 0xffff888165cf0a28 lsn 1/37472 type XFS_LI_INODE flags IN_AIL|ABORTED xfs_ail_pinned: lip 0xffff88810b850bb8 lsn 1/37472 type XFS_LI_INODE flags IN_AIL|ABORTED .... The inode log items are marked as aborted, which means that either: a) a transaction commit has occurred, seen an error or shutdown, and called xfs_trans_free_items() to abort the items. This should happen before any pinning of log items occurs. or b) a dirty transaction has been cancelled. This should also happen before any pinning of log items occurs. or c) AIL insertion at journal IO completion is marked as aborted. In this case, the log item is pinned by the CIL until journal IO completes and hence needs to be unpinned. This is then done after the ->iop_committed() callback is run, so the pin count should be balanced correctly. Yet none of these seemed to be occurring. Further tracing indicated this: d) Shutdown during CIL pushing resulting in log item completion being called from checkpoint abort processing. Items are unpinned and released without serialisation against each other, journal IO completion or transaction commit completion. In this case, we may still have a transaction commit in flight that holds a reference to a xfs_buf_log_item (BLI) after CIL insertion. e.g. a synchronous transaction will flush the CIL before the transaction is torn down. The concurrent CIL push then aborts insertion it and drops the commit/AIL reference to the BLI. This can leave the transaction commit context with the last reference to the BLI which is dropped here: xfs_trans_free_items() ->iop_release xfs_buf_item_release xfs_buf_item_put if (XFS_LI_ABORTED) xfs_trans_ail_delete xfs_buf_item_relse() Unlike the journal completion ->iop_unpin path, this path does not run stale buffer completion process when it drops the last reference, hence leaving the stale inodes attached to the buffer sitting the AIL. There are no other references to those inodes, so there is no other mechanism to remove them from the AIL. Hence unmount hangs. The buffer lock context for stale buffers is passed to the last BLI reference. This is normally the last BLI unpin on journal IO completion. The unpin then processes the stale buffer completion and releases the buffer lock. However, if the final unpin from journal IO completion (or CIL push abort) does not hold the last reference to the BLI, there -must- still be a transaction context that references the BLI, and so that context must perform the stale buffer completion processing before the buffer is unlocked and the BLI torn down. The fix for this is to rework the xfs_buf_item_relse() path to run stale buffer completion processing if it drops the last reference to the BLI. We still hold the buffer locked, so the buffer owner and lock context is the same as if we passed the BLI and buffer to the ->iop_unpin() context to finish stale process on journal commit. However, we have to be careful here. In a shutdown state, we can be freeing dirty BLIs from xfs_buf_item_put() via xfs_trans_brelse() and xfs_trans_bdetach(). The existing code handles this case by considering shutdown state as "aborted", but in doing so largely masks the failure to clean up stale BLI state from the xfs_buf_item_relse() path. i.e regardless of the shutdown state and whether the item is in the AIL, we must finish the stale buffer cleanup if we are are dropping the last BLI reference from the ->iop_relse path in transaction commit context. Signed-off-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com> Signed-off-by: Carlos Maiolino <cem@kernel.org>
72 lines
2.3 KiB
C
72 lines
2.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2000-2001,2005 Silicon Graphics, Inc.
|
|
* All Rights Reserved.
|
|
*/
|
|
#ifndef __XFS_BUF_ITEM_H__
|
|
#define __XFS_BUF_ITEM_H__
|
|
|
|
/* kernel only definitions */
|
|
|
|
struct xfs_buf;
|
|
struct xfs_mount;
|
|
|
|
/* buf log item flags */
|
|
#define XFS_BLI_HOLD (1u << 0)
|
|
#define XFS_BLI_DIRTY (1u << 1)
|
|
#define XFS_BLI_STALE (1u << 2)
|
|
#define XFS_BLI_LOGGED (1u << 3)
|
|
#define XFS_BLI_INODE_ALLOC_BUF (1u << 4)
|
|
#define XFS_BLI_STALE_INODE (1u << 5)
|
|
#define XFS_BLI_INODE_BUF (1u << 6)
|
|
#define XFS_BLI_ORDERED (1u << 7)
|
|
|
|
#define XFS_BLI_FLAGS \
|
|
{ XFS_BLI_HOLD, "HOLD" }, \
|
|
{ XFS_BLI_DIRTY, "DIRTY" }, \
|
|
{ XFS_BLI_STALE, "STALE" }, \
|
|
{ XFS_BLI_LOGGED, "LOGGED" }, \
|
|
{ XFS_BLI_INODE_ALLOC_BUF, "INODE_ALLOC" }, \
|
|
{ XFS_BLI_STALE_INODE, "STALE_INODE" }, \
|
|
{ XFS_BLI_INODE_BUF, "INODE_BUF" }, \
|
|
{ XFS_BLI_ORDERED, "ORDERED" }
|
|
|
|
/*
|
|
* This is the in core log item structure used to track information
|
|
* needed to log buffers. It tracks how many times the lock has been
|
|
* locked, and which 128 byte chunks of the buffer are dirty.
|
|
*/
|
|
struct xfs_buf_log_item {
|
|
struct xfs_log_item bli_item; /* common item structure */
|
|
struct xfs_buf *bli_buf; /* real buffer pointer */
|
|
unsigned int bli_flags; /* misc flags */
|
|
unsigned int bli_recur; /* lock recursion count */
|
|
atomic_t bli_refcount; /* cnt of tp refs */
|
|
int bli_format_count; /* count of headers */
|
|
struct xfs_buf_log_format *bli_formats; /* array of in-log header ptrs */
|
|
struct xfs_buf_log_format __bli_format; /* embedded in-log header */
|
|
};
|
|
|
|
int xfs_buf_item_init(struct xfs_buf *, struct xfs_mount *);
|
|
void xfs_buf_item_done(struct xfs_buf *bp);
|
|
void xfs_buf_item_put(struct xfs_buf_log_item *bip);
|
|
void xfs_buf_item_log(struct xfs_buf_log_item *, uint, uint);
|
|
bool xfs_buf_item_dirty_format(struct xfs_buf_log_item *);
|
|
void xfs_buf_inode_iodone(struct xfs_buf *);
|
|
#ifdef CONFIG_XFS_QUOTA
|
|
void xfs_buf_dquot_iodone(struct xfs_buf *);
|
|
#else
|
|
static inline void xfs_buf_dquot_iodone(struct xfs_buf *bp)
|
|
{
|
|
}
|
|
#endif /* CONFIG_XFS_QUOTA */
|
|
void xfs_buf_iodone(struct xfs_buf *);
|
|
bool xfs_buf_log_check_iovec(struct xfs_log_iovec *iovec);
|
|
|
|
unsigned int xfs_buf_inval_log_space(unsigned int map_count,
|
|
unsigned int blocksize);
|
|
|
|
extern struct kmem_cache *xfs_buf_item_cache;
|
|
|
|
#endif /* __XFS_BUF_ITEM_H__ */
|