From 48ce292ea06e527489d5c1a6f9d2c737209e1507 Mon Sep 17 00:00:00 2001 From: Brian Behlendorf Date: Mon, 23 Jun 2025 12:48:30 -0700 Subject: [PATCH] Clarify and restrict dmu_tx_assign() errors There are three possible cases where dmu_tx_assign() may encounter a fatal error. When there is a true lack of free space (ENOSPC), when there is a lack of quota space (EDQUOT), or when data required to perform the transaction cannot be read from disk (EIO). See the dmu_tx_check_ioerr() function for additional details of on the motivation for check for I/O error early. Prior to this change dmu_tx_assign() would return the contents of tx->tx_err which covered a wide range of possible error codes (EIO, ECKSUM, ESRCH, etc). In practice, none of the callers could do anything useful with this level of detail and simply returned the error. Therefore, this change converts all tx->tx_err errors to EIO, adds ASSERTs to dmu_tx_assign() to cover the only possible errors, and clarifies the function comment to include EIO as a possible fatal error. Reviewed-by: Alexander Motin Reviewed-by: Brian Behlendorf Signed-off-by: Brian D Behlendorf Closes #17463 --- cmd/ztest.c | 7 ++++--- module/zfs/dmu_tx.c | 10 +++++++--- module/zfs/dsl_dir.c | 2 ++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cmd/ztest.c b/cmd/ztest.c index 3b63ad43f..89264c97f 100644 --- a/cmd/ztest.c +++ b/cmd/ztest.c @@ -1816,7 +1816,7 @@ ztest_zd_fini(ztest_ds_t *zd) (ztest_random(10) == 0 ? DMU_TX_NOWAIT : DMU_TX_WAIT) static uint64_t -ztest_tx_assign(dmu_tx_t *tx, uint64_t txg_how, const char *tag) +ztest_tx_assign(dmu_tx_t *tx, dmu_tx_flag_t txg_how, const char *tag) { uint64_t txg; int error; @@ -1829,9 +1829,10 @@ ztest_tx_assign(dmu_tx_t *tx, uint64_t txg_how, const char *tag) if (error == ERESTART) { ASSERT3U(txg_how, ==, DMU_TX_NOWAIT); dmu_tx_wait(tx); - } else { - ASSERT3U(error, ==, ENOSPC); + } else if (error == ENOSPC) { ztest_record_enospc(tag); + } else { + ASSERT(error == EDQUOT || error == EIO); } dmu_tx_abort(tx); return (0); diff --git a/module/zfs/dmu_tx.c b/module/zfs/dmu_tx.c index b5e9c7e6e..d85d8b894 100644 --- a/module/zfs/dmu_tx.c +++ b/module/zfs/dmu_tx.c @@ -1054,7 +1054,7 @@ dmu_tx_try_assign(dmu_tx_t *tx) if (tx->tx_err) { DMU_TX_STAT_BUMP(dmu_tx_error); - return (tx->tx_err); + return (SET_ERROR(EIO)); } if (spa_suspended(spa)) { @@ -1190,7 +1190,8 @@ dmu_tx_unassign(dmu_tx_t *tx) * If DMU_TX_WAIT is set and the currently open txg is full, this function * will wait until there's a new txg. This should be used when no locks * are being held. With this bit set, this function will only fail if - * we're truly out of space (or over quota). + * we're truly out of space (ENOSPC), over quota (EDQUOT), or required + * data for the transaction could not be read from disk (EIO). * * If DMU_TX_WAIT is *not* set and we can't assign into the currently open * txg without blocking, this function will return immediately with @@ -1279,8 +1280,11 @@ dmu_tx_assign(dmu_tx_t *tx, dmu_tx_flag_t flags) * Return unless we decided to retry, or the caller does not * want to block. */ - if (err != ERESTART || !(flags & DMU_TX_WAIT)) + if (err != ERESTART || !(flags & DMU_TX_WAIT)) { + ASSERT(err == EDQUOT || err == ENOSPC || + err == ERESTART || err == EIO); return (err); + } /* * Wait until there's room in this txg, or until it's been diff --git a/module/zfs/dsl_dir.c b/module/zfs/dsl_dir.c index 8c59fbf60..f24cd2049 100644 --- a/module/zfs/dsl_dir.c +++ b/module/zfs/dsl_dir.c @@ -1450,6 +1450,8 @@ dsl_dir_tempreserve_space(dsl_dir_t *dd, uint64_t lsize, uint64_t asize, MSEC2NSEC(10), MSEC2NSEC(10)); err = SET_ERROR(ERESTART); } + + ASSERT3U(err, ==, ERESTART); } if (err == 0) {