summaryrefslogtreecommitdiff
path: root/fs/xfs/xfs_mount.c
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2021-10-22 15:31:05 -0700
committerDarrick J. Wong <djwong@kernel.org>2021-12-15 17:29:29 -0800
commitbb8092d02d822601113897de29384972342ed1e8 (patch)
treeb496a8902d1bb28a09623580309e88d66f1193ae /fs/xfs/xfs_mount.c
parentbe31442955a3d225b71223eebc056015fab0d698 (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.c94
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 */