Print this page
5056 ZFS deadlock on db_mtx and dn_holds
Reviewed by: Will Andrews <willa@spectralogic.com>
Reviewed by: Matt Ahrens <mahrens@delphix.com>
Reviewed by: George Wilson <george.wilson@delphix.com>
Approved by: Dan McDonald <danmcd@omniti.com>

@@ -21,10 +21,11 @@
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2012, 2014 by Delphix. All rights reserved.
  * Copyright (c) 2013 Martin Matuska. All rights reserved.
  * Copyright (c) 2014 Joyent, Inc. All rights reserved.
+ * Copyright (c) 2014 Spectra Logic Corporation, All rights reserved.
  */
 
 #include <sys/dmu.h>
 #include <sys/dmu_objset.h>
 #include <sys/dmu_tx.h>

@@ -123,28 +124,29 @@
 
 extern inline dsl_dir_phys_t *dsl_dir_phys(dsl_dir_t *dd);
 
 static uint64_t dsl_dir_space_towrite(dsl_dir_t *dd);
 
-/* ARGSUSED */
 static void
-dsl_dir_evict(dmu_buf_t *db, void *arg)
+dsl_dir_evict(void *dbu)
 {
-        dsl_dir_t *dd = arg;
+        dsl_dir_t *dd = dbu;
         dsl_pool_t *dp = dd->dd_pool;
         int t;
 
+        dd->dd_dbuf = NULL;
+
         for (t = 0; t < TXG_SIZE; t++) {
                 ASSERT(!txg_list_member(&dp->dp_dirty_dirs, dd, t));
                 ASSERT(dd->dd_tempreserved[t] == 0);
                 ASSERT(dd->dd_space_towrite[t] == 0);
         }
 
         if (dd->dd_parent)
-                dsl_dir_rele(dd->dd_parent, dd);
+                dsl_dir_async_rele(dd->dd_parent, dd);
 
-        spa_close(dd->dd_pool->dp_spa, dd);
+        spa_async_close(dd->dd_pool->dp_spa, dd);
 
         /*
          * The props callback list should have been cleaned up by
          * objset_evict().
          */

@@ -236,12 +238,13 @@
                         dd->dd_origin_txg =
                             origin_phys->ds_creation_txg;
                         dmu_buf_rele(origin_bonus, FTAG);
                 }
 
-                winner = dmu_buf_set_user_ie(dbuf, dd, dsl_dir_evict);
-                if (winner) {
+                dmu_buf_init_user(&dd->dd_dbu, dsl_dir_evict, &dd->dd_dbuf);
+                winner = dmu_buf_set_user_ie(dbuf, &dd->dd_dbu);
+                if (winner != NULL) {
                         if (dd->dd_parent)
                                 dsl_dir_rele(dd->dd_parent, dd);
                         mutex_destroy(&dd->dd_lock);
                         kmem_free(dd, sizeof (dsl_dir_t));
                         dd = winner;

@@ -281,10 +284,25 @@
         dprintf_dd(dd, "%s\n", "");
         spa_close(dd->dd_pool->dp_spa, tag);
         dmu_buf_rele(dd->dd_dbuf, tag);
 }
 
+/*
+ * Remove a reference to the given dsl dir that is being asynchronously
+ * released.  Async releases occur from a taskq performing eviction of
+ * dsl datasets and dirs.  This process is identical to a normal release
+ * with the exception of using the async API for releasing the reference on
+ * the spa.
+ */
+void
+dsl_dir_async_rele(dsl_dir_t *dd, void *tag)
+{
+        dprintf_dd(dd, "%s\n", "");
+        spa_async_close(dd->dd_pool->dp_spa, tag);
+        dmu_buf_rele(dd->dd_dbuf, tag);
+}
+
 /* buf must be long enough (MAXNAMELEN + strlen(MOS_DIR_NAME) + 1 should do) */
 void
 dsl_dir_name(dsl_dir_t *dd, char *buf)
 {
         if (dd->dd_parent) {

@@ -411,11 +429,11 @@
         if (err != 0) {
                 return (err);
         }
 
         while (next != NULL) {
-                dsl_dir_t *child_ds;
+                dsl_dir_t *child_dd;
                 err = getcomponent(next, buf, &nextnext);
                 if (err != 0)
                         break;
                 ASSERT(next[0] != '\0');
                 if (next[0] == '@')

@@ -430,15 +448,15 @@
                         if (err == ENOENT)
                                 err = 0;
                         break;
                 }
 
-                err = dsl_dir_hold_obj(dp, ddobj, buf, tag, &child_ds);
+                err = dsl_dir_hold_obj(dp, ddobj, buf, tag, &child_dd);
                 if (err != 0)
                         break;
                 dsl_dir_rele(dd, tag);
-                dd = child_ds;
+                dd = child_dd;
                 next = nextnext;
         }
 
         if (err != 0) {
                 dsl_dir_rele(dd, tag);