]> granicus.if.org Git - zfs/commitdiff
Remove recursion from dsl_dir_willuse_space()
authorAndrew Barnes <barnes333@gmail.com>
Mon, 20 Jan 2014 04:39:28 +0000 (15:39 +1100)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Tue, 4 Mar 2014 19:22:27 +0000 (11:22 -0800)
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 <barnes333@gmail.com>
Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #2069

module/zfs/dsl_dir.c

index f0a0b116ab1f7d550417520fc94840591781c5e2..f649bba4c880f1261d463071ae7cf2bf9b026753 100644 (file)
@@ -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 */