diff options
author | Darrick J. Wong <djwong@kernel.org> | 2021-10-22 15:31:05 -0700 |
---|---|---|
committer | Darrick J. Wong <djwong@kernel.org> | 2021-12-15 17:29:29 -0800 |
commit | bb8092d02d822601113897de29384972342ed1e8 (patch) | |
tree | b496a8902d1bb28a09623580309e88d66f1193ae /fs/xfs/xfs_mount.c | |
parent | be31442955a3d225b71223eebc056015fab0d698 (diff) |
xfs: allow queued AG intents to drain before scrubbingscrub-drain-intents_2021-12-15
Currently, online scrub isn't sufficiently careful about quiescing
allocation groups before checking them. While scrub does take the AG
header locks, it doesn't serialize against chains of AG update intents
that are being processed concurrently. If there's a collision,
cross-referencing between data structures (e.g. rmapbt and refcountbt)
can yield false corruption events; if repair is running, this results in
incorrect repairs.
Fix this by adding to the perag structure the count of active intents
and make scrub wait until there aren't any to continue. This is a
little stupid since transactions can queue intents without taking buffer
locks, but we'll also wait for those transactions.
XXX: should have instead a per-ag rwsem that gets taken as soon as the
AG[IF] are locked and stays held until the transaction commits or moves
on to the next AG? would we rather have a six lock so that intents can
take an ix lock, and not have to upgrade to x until we actually want to
make changes to that ag? is that how those even work??
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Diffstat (limited to 'fs/xfs/xfs_mount.c')
-rw-r--r-- | fs/xfs/xfs_mount.c | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/fs/xfs/xfs_mount.c b/fs/xfs/xfs_mount.c index 8c4975556a73..fb38e638e1ca 100644 --- a/fs/xfs/xfs_mount.c +++ b/fs/xfs/xfs_mount.c @@ -1510,3 +1510,97 @@ xfs_hook_call( return srcu_notifier_call_chain(&chain->head, val, priv); } #endif /* CONFIG_XFS_LIVE_HOOKS */ + +#ifdef CONFIG_XFS_DRAIN_INTENTS +static inline void xfs_drain_bump(struct xfs_drain *dr) +{ + atomic_inc(&dr->dr_count); +} + +static inline void xfs_drain_drop(struct xfs_drain *dr) +{ + ASSERT(atomic_read(&dr->dr_count) > 0); + + if (atomic_dec_and_test(&dr->dr_count)) + wake_up(&dr->dr_waiters); +} + +static inline int xfs_drain_wait(struct xfs_drain *dr) +{ + return wait_event_killable(dr->dr_waiters, + atomic_read(&dr->dr_count) == 0); +} + +#ifdef CONFIG_XFS_RT +# define xfs_rt_drain_bump(dr) xfs_drain_bump(dr) +# define xfs_rt_drain_drop(dr) xfs_drain_drop(dr) + +/* + * Wait for the pending intent count for realtime metadata to hit zero. + * Callers must not hold any rt metadata inode locks. + */ +int +xfs_rt_drain_intents( + struct xfs_mount *mp) +{ + trace_xfs_rt_wait_intents(mp, __return_address); + return xfs_drain_wait(&mp->m_rt_intents); +} +#else +# define trace_xfs_rt_bump_intents(...) +# define trace_xfs_rt_drop_intents(...) +# define xfs_rt_drain_bump(dr) +# define xfs_rt_drain_drop(dr) +#endif /* CONFIG_XFS_RT */ + +/* Add an item to the pending count. */ +void +xfs_fs_bump_intents( + struct xfs_mount *mp, + bool isrt, + xfs_fsblock_t fsb) +{ + struct xfs_perag *pag; + + if (isrt) { + trace_xfs_rt_bump_intents(mp, __return_address); + xfs_rt_drain_bump(&mp->m_rt_intents); + return; + } + + pag = xfs_perag_get(mp, XFS_FSB_TO_AGNO(mp, fsb)); + trace_xfs_perag_bump_intents(pag, __return_address); + xfs_drain_bump(&pag->pag_intents); + xfs_perag_put(pag); +} + +/* Remove an item from the pending count. */ +void +xfs_fs_drop_intents(struct xfs_mount *mp, bool isrt, xfs_fsblock_t fsb) +{ + struct xfs_perag *pag; + + if (isrt) { + trace_xfs_rt_drop_intents(mp, __return_address); + xfs_rt_drain_drop(&mp->m_rt_intents); + return; + } + + pag = xfs_perag_get(mp, XFS_FSB_TO_AGNO(mp, fsb)); + trace_xfs_perag_drop_intents(pag, __return_address); + xfs_drain_drop(&pag->pag_intents); + xfs_perag_put(pag); +} + +/* + * Wait for the pending intent count for AG metadata to hit zero. + * Callers must not hold any AG header buffers. + */ +int +xfs_perag_drain_intents( + struct xfs_perag *pag) +{ + trace_xfs_perag_wait_intents(pag, __return_address); + return xfs_drain_wait(&pag->pag_intents); +} +#endif /* CONFIG_XFS_DRAIN_INTENTS */ |