]> git.proxmox.com Git - mirror_zfs.git/blobdiff - module/zfs/dmu.c
Fix dnode_hold_impl() soft lockup
[mirror_zfs.git] / module / zfs / dmu.c
index 1cb967641e70a2f021df1889e2fc647fcad6403e..36fc8815cd07317a1e0d98e466f6f5c110c188a8 100644 (file)
@@ -25,6 +25,7 @@
  * Copyright (c) 2013, Joyent, Inc. All rights reserved.
  * Copyright (c) 2016, Nexenta Systems, Inc. All rights reserved.
  * Copyright (c) 2015 by Chunwei Chen. All rights reserved.
+ * Copyright (c) 2019 Datto Inc.
  */
 
 #include <sys/dmu.h>
 int zfs_nopwrite_enabled = 1;
 
 /*
- * Tunable to control percentage of dirtied blocks from frees in one TXG.
- * After this threshold is crossed, additional dirty blocks from frees
- * wait until the next TXG.
+ * Tunable to control percentage of dirtied L1 blocks from frees allowed into
+ * one TXG. After this threshold is crossed, additional dirty blocks from frees
+ * will wait until the next TXG.
  * A value of zero will disable this throttle.
  */
-unsigned long zfs_per_txg_dirty_frees_percent = 30;
+unsigned long zfs_per_txg_dirty_frees_percent = 5;
 
 /*
  * Enable/disable forcing txg sync when dirty in dmu_offset_next.
@@ -76,65 +77,65 @@ int zfs_dmu_offset_next_sync = 0;
 /*
  * This can be used for testing, to ensure that certain actions happen
  * while in the middle of a remap (which might otherwise complete too
- * quickly).
+ * quickly).  Used by ztest(8).
  */
-int zfs_object_remap_one_indirect_delay_ticks = 0;
+int zfs_object_remap_one_indirect_delay_ms = 0;
 
 const dmu_object_type_info_t dmu_ot[DMU_OT_NUMTYPES] = {
-       { DMU_BSWAP_UINT8,      TRUE,   FALSE,  "unallocated"           },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "object directory"      },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "object array"          },
-       { DMU_BSWAP_UINT8,      TRUE,   FALSE,  "packed nvlist"         },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "packed nvlist size"    },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "bpobj"                 },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "bpobj header"          },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "SPA space map header"  },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "SPA space map"         },
-       { DMU_BSWAP_UINT64,     TRUE,   TRUE,   "ZIL intent log"        },
-       { DMU_BSWAP_DNODE,      TRUE,   TRUE,   "DMU dnode"             },
-       { DMU_BSWAP_OBJSET,     TRUE,   FALSE,  "DMU objset"            },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "DSL directory"         },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DSL directory child map"},
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DSL dataset snap map"  },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DSL props"             },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "DSL dataset"           },
-       { DMU_BSWAP_ZNODE,      TRUE,   FALSE,  "ZFS znode"             },
-       { DMU_BSWAP_OLDACL,     TRUE,   TRUE,   "ZFS V0 ACL"            },
-       { DMU_BSWAP_UINT8,      FALSE,  TRUE,   "ZFS plain file"        },
-       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "ZFS directory"         },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "ZFS master node"       },
-       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "ZFS delete queue"      },
-       { DMU_BSWAP_UINT8,      FALSE,  TRUE,   "zvol object"           },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "zvol prop"             },
-       { DMU_BSWAP_UINT8,      FALSE,  TRUE,   "other uint8[]"         },
-       { DMU_BSWAP_UINT64,     FALSE,  TRUE,   "other uint64[]"        },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "other ZAP"             },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "persistent error log"  },
-       { DMU_BSWAP_UINT8,      TRUE,   FALSE,  "SPA history"           },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "SPA history offsets"   },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "Pool properties"       },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DSL permissions"       },
-       { DMU_BSWAP_ACL,        TRUE,   TRUE,   "ZFS ACL"               },
-       { DMU_BSWAP_UINT8,      TRUE,   TRUE,   "ZFS SYSACL"            },
-       { DMU_BSWAP_UINT8,      TRUE,   TRUE,   "FUID table"            },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "FUID table size"       },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DSL dataset next clones"},
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "scan work queue"       },
-       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "ZFS user/group/project used" },
-       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "ZFS user/group/project quota"},
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "snapshot refcount tags"},
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DDT ZAP algorithm"     },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DDT statistics"        },
-       { DMU_BSWAP_UINT8,      TRUE,   TRUE,   "System attributes"     },
-       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "SA master node"        },
-       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "SA attr registration"  },
-       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "SA attr layouts"       },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "scan translations"     },
-       { DMU_BSWAP_UINT8,      FALSE,  TRUE,   "deduplicated block"    },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DSL deadlist map"      },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "DSL deadlist map hdr"  },
-       { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DSL dir clones"        },
-       { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "bpobj subobj"          }
+       {DMU_BSWAP_UINT8,  TRUE,  FALSE, FALSE, "unallocated"           },
+       {DMU_BSWAP_ZAP,    TRUE,  TRUE,  FALSE, "object directory"      },
+       {DMU_BSWAP_UINT64, TRUE,  TRUE,  FALSE, "object array"          },
+       {DMU_BSWAP_UINT8,  TRUE,  FALSE, FALSE, "packed nvlist"         },
+       {DMU_BSWAP_UINT64, TRUE,  FALSE, FALSE, "packed nvlist size"    },
+       {DMU_BSWAP_UINT64, TRUE,  FALSE, FALSE, "bpobj"                 },
+       {DMU_BSWAP_UINT64, TRUE,  FALSE, FALSE, "bpobj header"          },
+       {DMU_BSWAP_UINT64, TRUE,  FALSE, FALSE, "SPA space map header"  },
+       {DMU_BSWAP_UINT64, TRUE,  FALSE, FALSE, "SPA space map"         },
+       {DMU_BSWAP_UINT64, TRUE,  FALSE, TRUE,  "ZIL intent log"        },
+       {DMU_BSWAP_DNODE,  TRUE,  FALSE, TRUE,  "DMU dnode"             },
+       {DMU_BSWAP_OBJSET, TRUE,  TRUE,  FALSE, "DMU objset"            },
+       {DMU_BSWAP_UINT64, TRUE,  TRUE,  FALSE, "DSL directory"         },
+       {DMU_BSWAP_ZAP,    TRUE,  TRUE,  FALSE, "DSL directory child map"},
+       {DMU_BSWAP_ZAP,    TRUE,  TRUE,  FALSE, "DSL dataset snap map"  },
+       {DMU_BSWAP_ZAP,    TRUE,  TRUE,  FALSE, "DSL props"             },
+       {DMU_BSWAP_UINT64, TRUE,  TRUE,  FALSE, "DSL dataset"           },
+       {DMU_BSWAP_ZNODE,  TRUE,  FALSE, FALSE, "ZFS znode"             },
+       {DMU_BSWAP_OLDACL, TRUE,  FALSE, TRUE,  "ZFS V0 ACL"            },
+       {DMU_BSWAP_UINT8,  FALSE, FALSE, TRUE,  "ZFS plain file"        },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, TRUE,  "ZFS directory"         },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, FALSE, "ZFS master node"       },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, TRUE,  "ZFS delete queue"      },
+       {DMU_BSWAP_UINT8,  FALSE, FALSE, TRUE,  "zvol object"           },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, FALSE, "zvol prop"             },
+       {DMU_BSWAP_UINT8,  FALSE, FALSE, TRUE,  "other uint8[]"         },
+       {DMU_BSWAP_UINT64, FALSE, FALSE, TRUE,  "other uint64[]"        },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, FALSE, "other ZAP"             },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, FALSE, "persistent error log"  },
+       {DMU_BSWAP_UINT8,  TRUE,  FALSE, FALSE, "SPA history"           },
+       {DMU_BSWAP_UINT64, TRUE,  FALSE, FALSE, "SPA history offsets"   },
+       {DMU_BSWAP_ZAP,    TRUE,  TRUE,  FALSE, "Pool properties"       },
+       {DMU_BSWAP_ZAP,    TRUE,  TRUE,  FALSE, "DSL permissions"       },
+       {DMU_BSWAP_ACL,    TRUE,  FALSE, TRUE,  "ZFS ACL"               },
+       {DMU_BSWAP_UINT8,  TRUE,  FALSE, TRUE,  "ZFS SYSACL"            },
+       {DMU_BSWAP_UINT8,  TRUE,  FALSE, TRUE,  "FUID table"            },
+       {DMU_BSWAP_UINT64, TRUE,  FALSE, FALSE, "FUID table size"       },
+       {DMU_BSWAP_ZAP,    TRUE,  TRUE,  FALSE, "DSL dataset next clones"},
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, FALSE, "scan work queue"       },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, TRUE,  "ZFS user/group/project used" },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, TRUE,  "ZFS user/group/project quota"},
+       {DMU_BSWAP_ZAP,    TRUE,  TRUE,  FALSE, "snapshot refcount tags"},
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, FALSE, "DDT ZAP algorithm"     },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, FALSE, "DDT statistics"        },
+       {DMU_BSWAP_UINT8,  TRUE,  FALSE, TRUE,  "System attributes"     },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, TRUE,  "SA master node"        },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, TRUE,  "SA attr registration"  },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, TRUE,  "SA attr layouts"       },
+       {DMU_BSWAP_ZAP,    TRUE,  FALSE, FALSE, "scan translations"     },
+       {DMU_BSWAP_UINT8,  FALSE, FALSE, TRUE,  "deduplicated block"    },
+       {DMU_BSWAP_ZAP,    TRUE,  TRUE,  FALSE, "DSL deadlist map"      },
+       {DMU_BSWAP_UINT64, TRUE,  TRUE,  FALSE, "DSL deadlist map hdr"  },
+       {DMU_BSWAP_ZAP,    TRUE,  TRUE,  FALSE, "DSL dir clones"        },
+       {DMU_BSWAP_UINT64, TRUE,  FALSE, FALSE, "bpobj subobj"          }
 };
 
 const dmu_object_byteswap_info_t dmu_ot_byteswap[DMU_BSWAP_NUMFUNCS] = {
@@ -330,13 +331,13 @@ dmu_rm_spill(objset_t *os, uint64_t object, dmu_tx_t *tx)
 }
 
 /*
- * returns ENOENT, EIO, or 0.
+ * Lookup and hold the bonus buffer for the provided dnode.  If the dnode
+ * has not yet been allocated a new bonus dbuf a will be allocated.
+ * Returns ENOENT, EIO, or 0.
  */
-int
-dmu_bonus_hold_impl(objset_t *os, uint64_t object, void *tag, uint32_t flags,
-    dmu_buf_t **dbp)
+int dmu_bonus_hold_by_dnode(dnode_t *dn, void *tag, dmu_buf_t **dbp,
+    uint32_t flags)
 {
-       dnode_t *dn;
        dmu_buf_impl_t *db;
        int error;
        uint32_t db_flags = DB_RF_MUST_SUCCEED;
@@ -346,10 +347,6 @@ dmu_bonus_hold_impl(objset_t *os, uint64_t object, void *tag, uint32_t flags,
        if (flags & DMU_READ_NO_DECRYPT)
                db_flags |= DB_RF_NO_DECRYPT;
 
-       error = dnode_hold(os, object, FTAG, &dn);
-       if (error)
-               return (error);
-
        rw_enter(&dn->dn_struct_rwlock, RW_READER);
        if (dn->dn_bonus == NULL) {
                rw_exit(&dn->dn_struct_rwlock);
@@ -360,7 +357,7 @@ dmu_bonus_hold_impl(objset_t *os, uint64_t object, void *tag, uint32_t flags,
        db = dn->dn_bonus;
 
        /* as long as the bonus buf is held, the dnode will be held */
-       if (refcount_add(&db->db_holds, tag) == 1) {
+       if (zfs_refcount_add(&db->db_holds, tag) == 1) {
                VERIFY(dnode_add_ref(dn, db));
                atomic_inc_32(&dn->dn_dbufs_count);
        }
@@ -372,8 +369,6 @@ dmu_bonus_hold_impl(objset_t *os, uint64_t object, void *tag, uint32_t flags,
         */
        rw_exit(&dn->dn_struct_rwlock);
 
-       dnode_rele(dn, FTAG);
-
        error = dbuf_read(db, NULL, db_flags);
        if (error) {
                dnode_evict_bonus(dn);
@@ -387,9 +382,19 @@ dmu_bonus_hold_impl(objset_t *os, uint64_t object, void *tag, uint32_t flags,
 }
 
 int
-dmu_bonus_hold(objset_t *os, uint64_t obj, void *tag, dmu_buf_t **dbp)
+dmu_bonus_hold(objset_t *os, uint64_t object, void *tag, dmu_buf_t **dbp)
 {
-       return (dmu_bonus_hold_impl(os, obj, tag, DMU_READ_NO_PREFETCH, dbp));
+       dnode_t *dn;
+       int error;
+
+       error = dnode_hold(os, object, FTAG, &dn);
+       if (error)
+               return (error);
+
+       error = dmu_bonus_hold_by_dnode(dn, tag, dbp, DMU_READ_NO_PREFETCH);
+       dnode_rele(dn, FTAG);
+
+       return (error);
 }
 
 /*
@@ -705,11 +710,13 @@ dmu_prefetch(objset_t *os, uint64_t object, int64_t level, uint64_t offset,
  *
  * On input, *start should be the first offset that does not need to be
  * freed (e.g. "offset + length").  On return, *start will be the first
- * offset that should be freed.
+ * offset that should be freed and l1blks is set to the number of level 1
+ * indirect blocks found within the chunk.
  */
 static int
-get_next_chunk(dnode_t *dn, uint64_t *start, uint64_t minimum)
+get_next_chunk(dnode_t *dn, uint64_t *start, uint64_t minimum, uint64_t *l1blks)
 {
+       uint64_t blks;
        uint64_t maxblks = DMU_MAX_ACCESS >> (dn->dn_indblkshift + 1);
        /* bytes of data covered by a level-1 indirect block */
        uint64_t iblkrange =
@@ -719,11 +726,16 @@ get_next_chunk(dnode_t *dn, uint64_t *start, uint64_t minimum)
 
        if (*start - minimum <= iblkrange * maxblks) {
                *start = minimum;
+               /*
+                * Assume full L1 blocks and 128k recordsize to approximate the
+                * expected number of L1 blocks in this chunk
+                */
+               *l1blks = minimum / (1024 * 128 * 1024);
                return (0);
        }
        ASSERT(ISP2(iblkrange));
 
-       for (uint64_t blks = 0; *start > minimum && blks < maxblks; blks++) {
+       for (blks = 0; *start > minimum && blks < maxblks; blks++) {
                int err;
 
                /*
@@ -741,6 +753,7 @@ get_next_chunk(dnode_t *dn, uint64_t *start, uint64_t minimum)
                        *start = minimum;
                        break;
                } else if (err != 0) {
+                       *l1blks = blks;
                        return (err);
                }
 
@@ -749,6 +762,7 @@ get_next_chunk(dnode_t *dn, uint64_t *start, uint64_t minimum)
        }
        if (*start < minimum)
                *start = minimum;
+       *l1blks = blks;
        return (0);
 }
 
@@ -788,7 +802,7 @@ dmu_free_long_range_impl(objset_t *os, dnode_t *dn, uint64_t offset,
                dirty_frees_threshold =
                    zfs_per_txg_dirty_frees_percent * zfs_dirty_data_max / 100;
        else
-               dirty_frees_threshold = zfs_dirty_data_max / 4;
+               dirty_frees_threshold = zfs_dirty_data_max / 20;
 
        if (length == DMU_OBJECT_END || offset + length > object_size)
                length = object_size - offset;
@@ -796,6 +810,7 @@ dmu_free_long_range_impl(objset_t *os, dnode_t *dn, uint64_t offset,
        while (length != 0) {
                uint64_t chunk_end, chunk_begin, chunk_len;
                uint64_t long_free_dirty_all_txgs = 0;
+               uint64_t l1blks;
                dmu_tx_t *tx;
 
                if (dmu_objset_zfs_unmounting(dn->dn_objset))
@@ -804,7 +819,7 @@ dmu_free_long_range_impl(objset_t *os, dnode_t *dn, uint64_t offset,
                chunk_end = chunk_begin = offset + length;
 
                /* move chunk_begin backwards to the beginning of this chunk */
-               err = get_next_chunk(dn, &chunk_begin, offset);
+               err = get_next_chunk(dn, &chunk_begin, offset, &l1blks);
                if (err)
                        return (err);
                ASSERT3U(chunk_begin, >=, offset);
@@ -826,6 +841,7 @@ dmu_free_long_range_impl(objset_t *os, dnode_t *dn, uint64_t offset,
                 */
                if (dirty_frees_threshold != 0 &&
                    long_free_dirty_all_txgs >= dirty_frees_threshold) {
+                       DMU_TX_STAT_BUMP(dmu_tx_dirty_frees_delay);
                        txg_wait_open(dp, 0);
                        continue;
                }
@@ -844,9 +860,19 @@ dmu_free_long_range_impl(objset_t *os, dnode_t *dn, uint64_t offset,
                        return (err);
                }
 
+               /*
+                * In order to prevent unnecessary write throttling, for each
+                * TXG, we track the cumulative size of L1 blocks being dirtied
+                * in dnode_free_range() below. We compare this number to a
+                * tunable threshold, past which we prevent new L1 dirty freeing
+                * blocks from being added into the open TXG. See
+                * dmu_free_long_range_impl() for details. The threshold
+                * prevents write throttle activation due to dirty freeing L1
+                * blocks taking up a large percentage of zfs_dirty_data_max.
+                */
                mutex_enter(&dp->dp_lock);
                dp->dp_long_free_dirty_pertxg[dmu_tx_get_txg(tx) & TXG_MASK] +=
-                   chunk_len;
+                   l1blks << dn->dn_indblkshift;
                mutex_exit(&dp->dp_lock);
                DTRACE_PROBE3(free__long__range,
                    uint64_t, long_free_dirty_all_txgs, uint64_t, chunk_len,
@@ -1074,6 +1100,7 @@ dmu_object_remap_one_indirect(objset_t *os, dnode_t *dn,
     uint64_t last_removal_txg, uint64_t offset)
 {
        uint64_t l1blkid = dbuf_whichblock(dn, 1, offset);
+       dnode_t *dn_tx;
        int err = 0;
 
        rw_enter(&dn->dn_struct_rwlock, RW_READER);
@@ -1092,7 +1119,9 @@ dmu_object_remap_one_indirect(objset_t *os, dnode_t *dn,
 
        /*
         * If this L1 was already written after the last removal, then we've
-        * already tried to remap it.
+        * already tried to remap it.  An additional hold is taken after the
+        * dmu_tx_assign() to handle the case where the dnode is freed while
+        * waiting for the next open txg.
         */
        if (birth <= last_removal_txg &&
            dbuf_read(dbuf, NULL, DB_RF_MUST_SUCCEED) == 0 &&
@@ -1101,7 +1130,11 @@ dmu_object_remap_one_indirect(objset_t *os, dnode_t *dn,
                dmu_tx_hold_remap_l1indirect(tx, dn->dn_object);
                err = dmu_tx_assign(tx, TXG_WAIT);
                if (err == 0) {
-                       (void) dbuf_dirty(dbuf, tx);
+                       err = dnode_hold(os, dn->dn_object, FTAG, &dn_tx);
+                       if (err == 0) {
+                               (void) dbuf_dirty(dbuf, tx);
+                               dnode_rele(dn_tx, FTAG);
+                       }
                        dmu_tx_commit(tx);
                } else {
                        dmu_tx_abort(tx);
@@ -1110,7 +1143,7 @@ dmu_object_remap_one_indirect(objset_t *os, dnode_t *dn,
 
        dbuf_rele(dbuf, FTAG);
 
-       delay(zfs_object_remap_one_indirect_delay_ticks);
+       delay(MSEC_TO_TICK(zfs_object_remap_one_indirect_delay_ms));
 
        return (err);
 }
@@ -1132,7 +1165,7 @@ dmu_object_remap_indirects(objset_t *os, uint64_t object,
 {
        uint64_t offset, l1span;
        int err;
-       dnode_t *dn;
+       dnode_t *dn, *dn_tx;
 
        err = dnode_hold(os, object, FTAG, &dn);
        if (err != 0) {
@@ -1147,13 +1180,20 @@ dmu_object_remap_indirects(objset_t *os, uint64_t object,
                /*
                 * If the dnode has no indirect blocks, we cannot dirty them.
                 * We still want to remap the blkptr(s) in the dnode if
-                * appropriate, so mark it as dirty.
+                * appropriate, so mark it as dirty.  An additional hold is
+                * taken after the dmu_tx_assign() to handle the case where
+                * the dnode is freed while waiting for the next open txg.
                 */
                if (err == 0 && dnode_needs_remap(dn)) {
                        dmu_tx_t *tx = dmu_tx_create(os);
-                       dmu_tx_hold_bonus(tx, dn->dn_object);
-                       if ((err = dmu_tx_assign(tx, TXG_WAIT)) == 0) {
-                               dnode_setdirty(dn, tx);
+                       dmu_tx_hold_bonus(tx, object);
+                       err = dmu_tx_assign(tx, TXG_WAIT);
+                       if (err == 0) {
+                               err = dnode_hold(os, object, FTAG, &dn_tx);
+                               if (err == 0) {
+                                       dnode_setdirty(dn_tx, tx);
+                                       dnode_rele(dn_tx, FTAG);
+                               }
                                dmu_tx_commit(tx);
                        } else {
                                dmu_tx_abort(tx);
@@ -1621,6 +1661,7 @@ dmu_copy_from_buf(objset_t *os, uint64_t object, uint64_t offset,
        dmu_buf_t *dst_handle;
        dmu_buf_impl_t *dstdb;
        dmu_buf_impl_t *srcdb = (dmu_buf_impl_t *)handle;
+       dmu_object_type_t type;
        arc_buf_t *abuf;
        uint64_t datalen;
        boolean_t byteorder;
@@ -1636,11 +1677,15 @@ dmu_copy_from_buf(objset_t *os, uint64_t object, uint64_t offset,
        dstdb = (dmu_buf_impl_t *)dst_handle;
        datalen = arc_buf_size(srcdb->db_buf);
 
+       DB_DNODE_ENTER(dstdb);
+       type = DB_DNODE(dstdb)->dn_type;
+       DB_DNODE_EXIT(dstdb);
+
        /* allocated an arc buffer that matches the type of srcdb->db_buf */
        if (arc_is_encrypted(srcdb->db_buf)) {
                arc_get_raw_params(srcdb->db_buf, &byteorder, salt, iv, mac);
                abuf = arc_loan_raw_buf(os->os_spa, dmu_objset_id(os),
-                   byteorder, salt, iv, mac, DB_DNODE(dstdb)->dn_type,
+                   byteorder, salt, iv, mac, type,
                    datalen, arc_buf_lsize(srcdb->db_buf),
                    arc_get_compression(srcdb->db_buf));
        } else {
@@ -1648,7 +1693,7 @@ dmu_copy_from_buf(objset_t *os, uint64_t object, uint64_t offset,
                ASSERT3U(arc_get_compression(srcdb->db_buf),
                    ==, ZIO_COMPRESS_OFF);
                abuf = arc_loan_buf(os->os_spa,
-                   DMU_OT_IS_METADATA(DB_DNODE(dstdb)->dn_type), datalen);
+                   DMU_OT_IS_METADATA(type), datalen);
        }
 
        ASSERT3U(datalen, ==, arc_buf_size(abuf));
@@ -1664,7 +1709,7 @@ dmu_copy_from_buf(objset_t *os, uint64_t object, uint64_t offset,
  * If this is not possible copy the contents of passed arc buf via
  * dmu_write().
  */
-void
+int
 dmu_assign_arcbuf_by_dnode(dnode_t *dn, uint64_t offset, arc_buf_t *buf,
     dmu_tx_t *tx)
 {
@@ -1676,7 +1721,9 @@ dmu_assign_arcbuf_by_dnode(dnode_t *dn, uint64_t offset, arc_buf_t *buf,
 
        rw_enter(&dn->dn_struct_rwlock, RW_READER);
        blkid = dbuf_whichblock(dn, 0, offset);
-       VERIFY((db = dbuf_hold(dn, blkid, FTAG)) != NULL);
+       db = dbuf_hold(dn, blkid, FTAG);
+       if (db == NULL)
+               return (SET_ERROR(EIO));
        rw_exit(&dn->dn_struct_rwlock);
 
        /*
@@ -1696,17 +1743,22 @@ dmu_assign_arcbuf_by_dnode(dnode_t *dn, uint64_t offset, arc_buf_t *buf,
                dmu_return_arcbuf(buf);
                XUIOSTAT_BUMP(xuiostat_wbuf_copied);
        }
+
+       return (0);
 }
 
-void
+int
 dmu_assign_arcbuf_by_dbuf(dmu_buf_t *handle, uint64_t offset, arc_buf_t *buf,
     dmu_tx_t *tx)
 {
+       int err;
        dmu_buf_impl_t *dbuf = (dmu_buf_impl_t *)handle;
 
        DB_DNODE_ENTER(dbuf);
-       dmu_assign_arcbuf_by_dnode(DB_DNODE(dbuf), offset, buf, tx);
+       err = dmu_assign_arcbuf_by_dnode(DB_DNODE(dbuf), offset, buf, tx);
        DB_DNODE_EXIT(dbuf);
+
+       return (err);
 }
 
 typedef struct {
@@ -1751,6 +1803,15 @@ dmu_sync_done(zio_t *zio, arc_buf_t *buf, void *varg)
        dmu_sync_arg_t *dsa = varg;
        dbuf_dirty_record_t *dr = dsa->dsa_dr;
        dmu_buf_impl_t *db = dr->dr_dbuf;
+       zgd_t *zgd = dsa->dsa_zgd;
+
+       /*
+        * Record the vdev(s) backing this blkptr so they can be flushed after
+        * the writes for the lwb have completed.
+        */
+       if (zio->io_error == 0) {
+               zil_lwb_add_block(zgd->zgd_lwb, zgd->zgd_bp);
+       }
 
        mutex_enter(&db->db_mtx);
        ASSERT(dr->dt.dl.dr_override_state == DR_IN_DMU_SYNC);
@@ -1800,14 +1861,23 @@ dmu_sync_late_arrival_done(zio_t *zio)
 {
        blkptr_t *bp = zio->io_bp;
        dmu_sync_arg_t *dsa = zio->io_private;
-       ASSERTV(blkptr_t *bp_orig = &zio->io_bp_orig);
-
-       if (zio->io_error == 0 && !BP_IS_HOLE(bp)) {
-               ASSERT(!(zio->io_flags & ZIO_FLAG_NOPWRITE));
-               ASSERT(BP_IS_HOLE(bp_orig) || !BP_EQUAL(bp, bp_orig));
-               ASSERT(zio->io_bp->blk_birth == zio->io_txg);
-               ASSERT(zio->io_txg > spa_syncing_txg(zio->io_spa));
-               zio_free(zio->io_spa, zio->io_txg, zio->io_bp);
+       zgd_t *zgd = dsa->dsa_zgd;
+
+       if (zio->io_error == 0) {
+               /*
+                * Record the vdev(s) backing this blkptr so they can be
+                * flushed after the writes for the lwb have completed.
+                */
+               zil_lwb_add_block(zgd->zgd_lwb, zgd->zgd_bp);
+
+               if (!BP_IS_HOLE(bp)) {
+                       ASSERTV(blkptr_t *bp_orig = &zio->io_bp_orig);
+                       ASSERT(!(zio->io_flags & ZIO_FLAG_NOPWRITE));
+                       ASSERT(BP_IS_HOLE(bp_orig) || !BP_EQUAL(bp, bp_orig));
+                       ASSERT(zio->io_bp->blk_birth == zio->io_txg);
+                       ASSERT(zio->io_txg > spa_syncing_txg(zio->io_spa));
+                       zio_free(zio->io_spa, zio->io_txg, zio->io_bp);
+               }
        }
 
        dmu_tx_commit(dsa->dsa_tx);
@@ -1918,11 +1988,6 @@ dmu_sync(zio_t *pio, uint64_t txg, dmu_sync_cb_t *done, zgd_t *zgd)
        ASSERT(pio != NULL);
        ASSERT(txg != 0);
 
-       /* dbuf is within the locked range */
-       ASSERT3U(db->db.db_offset, >=, zgd->zgd_rl->r_off);
-       ASSERT3U(db->db.db_offset + db->db.db_size, <=,
-           zgd->zgd_rl->r_off + zgd->zgd_rl->r_len);
-
        SET_BOOKMARK(&zb, ds->ds_object,
            db->db.db_object, db->db_level, db->db_blkid);
 
@@ -2275,6 +2340,8 @@ dmu_write_policy(objset_t *os, dnode_t *dn, int level, int wp, zio_prop_t *zp)
        bzero(zp->zp_salt, ZIO_DATA_SALT_LEN);
        bzero(zp->zp_iv, ZIO_DATA_IV_LEN);
        bzero(zp->zp_mac, ZIO_DATA_MAC_LEN);
+       zp->zp_zpl_smallblk = DMU_OT_IS_FILE(zp->zp_type) ?
+           os->os_zpl_special_smallblock : 0;
 
        ASSERT3U(zp->zp_compress, !=, ZIO_COMPRESS_INHERIT);
 }
@@ -2512,6 +2579,7 @@ dmu_fini(void)
 
 #if defined(_KERNEL)
 EXPORT_SYMBOL(dmu_bonus_hold);
+EXPORT_SYMBOL(dmu_bonus_hold_by_dnode);
 EXPORT_SYMBOL(dmu_buf_hold_array_by_bonus);
 EXPORT_SYMBOL(dmu_buf_rele_array);
 EXPORT_SYMBOL(dmu_prefetch);