zfs: annotate nested dd_lock in reservation sync accounting

When reservation sync updates a child's reserved space, it rolls the
delta into ancestor space accounting while still holding the child's
dd_lock.  That locking order is intentional, but Linux lockdep sees
the ancestor acquisition as recursive because it lacks a nested lock
subclass annotation.

Teach the reservation-sync space-accounting path to acquire ancestor
dd_lock instances with a nested subclass.  Keep the existing public
interfaces and accounting behavior unchanged by routing only the
ancestor rollup through local helpers.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
Signed-off-by: gality369 <gality369@example.com>
Closes #18497
This commit is contained in:
Gality
2026-05-08 00:14:20 +08:00
committed by GitHub
parent c4545ba037
commit 8fdc866757
+50 -14
View File
@@ -1534,9 +1534,28 @@ dsl_dir_willuse_space(dsl_dir_t *dd, int64_t space, dmu_tx_t *tx)
}
/* call from syncing context when we actually write/free space for this dd */
void
dsl_dir_diduse_space(dsl_dir_t *dd, dd_used_t type,
int64_t used, int64_t compressed, int64_t uncompressed, dmu_tx_t *tx)
static void dsl_dir_diduse_transfer_space_impl(dsl_dir_t *dd, int64_t used,
int64_t compressed, int64_t uncompressed, int64_t tonew,
dd_used_t oldtype, dd_used_t newtype, boolean_t nested, dmu_tx_t *tx);
static void
dsl_dir_lock_enter(dsl_dir_t *dd, boolean_t nested)
{
/*
* lockdep needs an explicit subclass when a child dd_lock
* nests an ancestor.
*/
if (nested) {
mutex_enter_nested(&dd->dd_lock, NESTED_SINGLE);
} else {
mutex_enter(&dd->dd_lock);
}
}
static void
dsl_dir_diduse_space_impl(dsl_dir_t *dd, dd_used_t type,
int64_t used, int64_t compressed, int64_t uncompressed,
boolean_t nested, dmu_tx_t *tx)
{
int64_t accounted_delta;
@@ -1554,7 +1573,7 @@ dsl_dir_diduse_space(dsl_dir_t *dd, dd_used_t type,
*/
boolean_t needlock = !MUTEX_HELD(&dd->dd_lock);
if (needlock)
mutex_enter(&dd->dd_lock);
dsl_dir_lock_enter(dd, nested);
dsl_dir_phys_t *ddp = dsl_dir_phys(dd);
accounted_delta = parent_delta(dd, ddp->dd_used_bytes, used);
ASSERT(used >= 0 || ddp->dd_used_bytes >= -used);
@@ -1582,12 +1601,20 @@ dsl_dir_diduse_space(dsl_dir_t *dd, dd_used_t type,
mutex_exit(&dd->dd_lock);
if (dd->dd_parent != NULL) {
dsl_dir_diduse_transfer_space(dd->dd_parent,
dsl_dir_diduse_transfer_space_impl(dd->dd_parent,
accounted_delta, compressed, uncompressed,
used, DD_USED_CHILD_RSRV, DD_USED_CHILD, tx);
used, DD_USED_CHILD_RSRV, DD_USED_CHILD, nested, tx);
}
}
void
dsl_dir_diduse_space(dsl_dir_t *dd, dd_used_t type, int64_t used,
int64_t compressed, int64_t uncompressed, dmu_tx_t *tx)
{
dsl_dir_diduse_space_impl(dd, type, used, compressed, uncompressed,
B_FALSE, tx);
}
void
dsl_dir_transfer_space(dsl_dir_t *dd, int64_t delta,
dd_used_t oldtype, dd_used_t newtype, dmu_tx_t *tx)
@@ -1612,10 +1639,10 @@ dsl_dir_transfer_space(dsl_dir_t *dd, int64_t delta,
mutex_exit(&dd->dd_lock);
}
void
dsl_dir_diduse_transfer_space(dsl_dir_t *dd, int64_t used,
static void
dsl_dir_diduse_transfer_space_impl(dsl_dir_t *dd, int64_t used,
int64_t compressed, int64_t uncompressed, int64_t tonew,
dd_used_t oldtype, dd_used_t newtype, dmu_tx_t *tx)
dd_used_t oldtype, dd_used_t newtype, boolean_t nested, dmu_tx_t *tx)
{
int64_t accounted_delta;
@@ -1625,7 +1652,7 @@ dsl_dir_diduse_transfer_space(dsl_dir_t *dd, int64_t used,
dmu_buf_will_dirty(dd->dd_dbuf, tx);
mutex_enter(&dd->dd_lock);
dsl_dir_lock_enter(dd, nested);
dsl_dir_phys_t *ddp = dsl_dir_phys(dd);
accounted_delta = parent_delta(dd, ddp->dd_used_bytes, used);
ASSERT(used >= 0 || ddp->dd_used_bytes >= -used);
@@ -1656,12 +1683,21 @@ dsl_dir_diduse_transfer_space(dsl_dir_t *dd, int64_t used,
mutex_exit(&dd->dd_lock);
if (dd->dd_parent != NULL) {
dsl_dir_diduse_transfer_space(dd->dd_parent,
dsl_dir_diduse_transfer_space_impl(dd->dd_parent,
accounted_delta, compressed, uncompressed,
used, DD_USED_CHILD_RSRV, DD_USED_CHILD, tx);
used, DD_USED_CHILD_RSRV, DD_USED_CHILD, nested, tx);
}
}
void
dsl_dir_diduse_transfer_space(dsl_dir_t *dd, int64_t used,
int64_t compressed, int64_t uncompressed, int64_t tonew,
dd_used_t oldtype, dd_used_t newtype, dmu_tx_t *tx)
{
dsl_dir_diduse_transfer_space_impl(dd, used, compressed,
uncompressed, tonew, oldtype, newtype, B_FALSE, tx);
}
typedef struct dsl_dir_set_qr_arg {
const char *ddsqra_name;
zprop_source_t ddsqra_source;
@@ -1828,8 +1864,8 @@ dsl_dir_set_reservation_sync_impl(dsl_dir_t *dd, uint64_t value, dmu_tx_t *tx)
if (dd->dd_parent != NULL) {
/* Roll up this additional usage into our ancestors */
dsl_dir_diduse_space(dd->dd_parent, DD_USED_CHILD_RSRV,
delta, 0, 0, tx);
dsl_dir_diduse_space_impl(dd->dd_parent, DD_USED_CHILD_RSRV,
delta, 0, 0, B_TRUE, tx);
}
mutex_exit(&dd->dd_lock);
}