dsl_dir: avoid dd_lock during snapshots_changed updates

Avoid holding dd_lock while updating the on-disk
snapshots_changed timestamp.

Both dsl_dir_zapify() and zap_update() may dirty buffers
and recurse into space accounting, which can take dd_lock.
Holding dd_lock across either operation can therefore
preserve the lock-order inversion reported by lockdep.

Only protect the in-memory dd_snap_cmtime update
with dd_lock. Perform the zapify and ZAP update without
dd_lock held, and retry the on-disk write if another updater
advanced dd_snap_cmtime while the write was in progress.

Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
Co-authored-by: gality369 <gality369@example.com>
Closes #18472
This commit is contained in:
Gality
2026-05-12 04:13:28 +08:00
committed by GitHub
parent 968f4db039
commit d50f5b6d0b
+18 -11
View File
@@ -2304,22 +2304,29 @@ dsl_dir_snap_cmtime_update(dsl_dir_t *dd, dmu_tx_t *tx)
{ {
dsl_pool_t *dp = dmu_tx_pool(tx); dsl_pool_t *dp = dmu_tx_pool(tx);
inode_timespec_t t; inode_timespec_t t;
ASSERT(dsl_pool_sync_context(dp));
gethrestime(&t); gethrestime(&t);
mutex_enter(&dd->dd_lock); mutex_enter(&dd->dd_lock);
dd->dd_snap_cmtime = t; dd->dd_snap_cmtime = t;
if (spa_feature_is_enabled(dp->dp_spa,
SPA_FEATURE_EXTENSIBLE_DATASET)) {
objset_t *mos = dd->dd_pool->dp_meta_objset;
uint64_t ddobj = dd->dd_object;
dsl_dir_zapify(dd, tx);
VERIFY0(zap_update(mos, ddobj,
DD_FIELD_SNAPSHOTS_CHANGED,
sizeof (uint64_t),
sizeof (inode_timespec_t) / sizeof (uint64_t),
&t, tx));
}
mutex_exit(&dd->dd_lock); mutex_exit(&dd->dd_lock);
if (!spa_feature_is_enabled(dp->dp_spa,
SPA_FEATURE_EXTENSIBLE_DATASET)) {
return;
}
objset_t *mos = dd->dd_pool->dp_meta_objset;
/*
* dsl_dir_zapify() and zap_update() may dirty buffers and recurse
* into space accounting, so do not call them with dd_lock held.
*/
dsl_dir_zapify(dd, tx);
VERIFY0(zap_update(mos, dd->dd_object, DD_FIELD_SNAPSHOTS_CHANGED,
sizeof (uint64_t),
sizeof (inode_timespec_t) / sizeof (uint64_t), &t, tx));
} }
void void