summaryrefslogtreecommitdiffstats
path: root/module/zfs/dsl_dir.c
diff options
context:
space:
mode:
authorAndrew Barnes <[email protected]>2014-01-20 15:39:28 +1100
committerBrian Behlendorf <[email protected]>2014-03-04 11:22:27 -0800
commit1ba1615925895ebd49d76d8c6ef8d06717515003 (patch)
treea3104d1350b82fbf7140521e74efaaa2be32260d /module/zfs/dsl_dir.c
parent0ad85ed91e2e68f0ba377e7c3d2cef45241eeeef (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.c28
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 */