diff options
author | Andrew Barnes <[email protected]> | 2014-01-20 15:39:28 +1100 |
---|---|---|
committer | Brian Behlendorf <[email protected]> | 2014-03-04 11:22:27 -0800 |
commit | 1ba1615925895ebd49d76d8c6ef8d06717515003 (patch) | |
tree | a3104d1350b82fbf7140521e74efaaa2be32260d /module/zfs/dsl_dir.c | |
parent | 0ad85ed91e2e68f0ba377e7c3d2cef45241eeeef (diff) |
Remove recursion from dsl_dir_willuse_space()
Remove recursion from dsl_dir_willuse_space() to reduce stack usage.
Issues with stack overflow were observed in zfs recv of zvols,
likelihood of an overflow is proportional to the depth of the dataset
as dsl_dir_willuse_space() recurses to parent datasets.
Signed-off-by: Andrew Barnes <[email protected]>
Signed-off-by: Brian Behlendorf <[email protected]>
Closes #2069
Diffstat (limited to 'module/zfs/dsl_dir.c')
-rw-r--r-- | module/zfs/dsl_dir.c | 28 |
1 files changed, 17 insertions, 11 deletions
diff --git a/module/zfs/dsl_dir.c b/module/zfs/dsl_dir.c index f0a0b116a..f649bba4c 100644 --- a/module/zfs/dsl_dir.c +++ b/module/zfs/dsl_dir.c @@ -808,6 +808,10 @@ dsl_dir_tempreserve_clear(void *tr_cookie, dmu_tx_t *tx) * or free space, for example when dirtying data. Be conservative; it's okay * to write less space or free more, but we don't want to write more or free * less than the amount specified. + * + * NOTE: The behavior of this function is identical to the Illumos / FreeBSD + * version however it has been adjusted to use an iterative rather then + * recursive algorithm to minimize stack usage. */ void dsl_dir_willuse_space(dsl_dir_t *dd, int64_t space, dmu_tx_t *tx) @@ -815,20 +819,22 @@ dsl_dir_willuse_space(dsl_dir_t *dd, int64_t space, dmu_tx_t *tx) int64_t parent_space; uint64_t est_used; - mutex_enter(&dd->dd_lock); - if (space > 0) - dd->dd_space_towrite[tx->tx_txg & TXG_MASK] += space; + do { + mutex_enter(&dd->dd_lock); + if (space > 0) + dd->dd_space_towrite[tx->tx_txg & TXG_MASK] += space; - est_used = dsl_dir_space_towrite(dd) + dd->dd_phys->dd_used_bytes; - parent_space = parent_delta(dd, est_used, space); - mutex_exit(&dd->dd_lock); + est_used = dsl_dir_space_towrite(dd) + + dd->dd_phys->dd_used_bytes; + parent_space = parent_delta(dd, est_used, space); + mutex_exit(&dd->dd_lock); - /* Make sure that we clean up dd_space_to* */ - dsl_dir_dirty(dd, tx); + /* Make sure that we clean up dd_space_to* */ + dsl_dir_dirty(dd, tx); - /* XXX this is potentially expensive and unnecessary... */ - if (parent_space && dd->dd_parent) - dsl_dir_willuse_space(dd->dd_parent, parent_space, tx); + dd = dd->dd_parent; + space = parent_space; + } while (space && dd); } /* call from syncing context when we actually write/free space for this dd */ |