diff --git a/module/zfs/arc.c b/module/zfs/arc.c index 611b50c1e..aa193fe59 100644 --- a/module/zfs/arc.c +++ b/module/zfs/arc.c @@ -3654,7 +3654,13 @@ arc_hdr_destroy(arc_buf_hdr_t *hdr) } ASSERT(!HDR_IO_IN_PROGRESS(hdr)); ASSERT(!HDR_IN_HASH_TABLE(hdr)); + boolean_t l1hdr_destroyed = B_FALSE; + /* + * If L2_WRITING, destroy L1HDR before L2HDR (under mutex) so + * arc_hdr_free_abd() can properly defer ABDs. Otherwise, destroy + * L1HDR outside mutex to minimize contention. + */ if (HDR_HAS_L2HDR(hdr)) { l2arc_dev_t *dev = hdr->b_l2hdr.b_dev; boolean_t buflist_held = MUTEX_HELD(&dev->l2ad_mtx); @@ -3672,9 +3678,26 @@ arc_hdr_destroy(arc_buf_hdr_t *hdr) * want to re-destroy the header's L2 portion. */ if (HDR_HAS_L2HDR(hdr)) { + if (HDR_L2_WRITING(hdr)) { + l1hdr_destroyed = B_TRUE; - if (!HDR_EMPTY(hdr)) - buf_discard_identity(hdr); + if (!HDR_EMPTY(hdr)) + buf_discard_identity(hdr); + + if (HDR_HAS_L1HDR(hdr)) { + arc_cksum_free(hdr); + + while (hdr->b_l1hdr.b_buf != NULL) + arc_buf_destroy_impl( + hdr->b_l1hdr.b_buf); + + if (hdr->b_l1hdr.b_pabd != NULL) + arc_hdr_free_abd(hdr, B_FALSE); + + if (HDR_HAS_RABD(hdr)) + arc_hdr_free_abd(hdr, B_TRUE); + } + } arc_hdr_l2hdr_destroy(hdr); } @@ -3683,26 +3706,22 @@ arc_hdr_destroy(arc_buf_hdr_t *hdr) mutex_exit(&dev->l2ad_mtx); } - /* - * The header's identify can only be safely discarded once it is no - * longer discoverable. This requires removing it from the hash table - * and the l2arc header list. After this point the hash lock can not - * be used to protect the header. - */ - if (!HDR_EMPTY(hdr)) - buf_discard_identity(hdr); + if (!l1hdr_destroyed) { + if (!HDR_EMPTY(hdr)) + buf_discard_identity(hdr); - if (HDR_HAS_L1HDR(hdr)) { - arc_cksum_free(hdr); + if (HDR_HAS_L1HDR(hdr)) { + arc_cksum_free(hdr); - while (hdr->b_l1hdr.b_buf != NULL) - arc_buf_destroy_impl(hdr->b_l1hdr.b_buf); + while (hdr->b_l1hdr.b_buf != NULL) + arc_buf_destroy_impl(hdr->b_l1hdr.b_buf); - if (hdr->b_l1hdr.b_pabd != NULL) - arc_hdr_free_abd(hdr, B_FALSE); + if (hdr->b_l1hdr.b_pabd != NULL) + arc_hdr_free_abd(hdr, B_FALSE); - if (HDR_HAS_RABD(hdr)) - arc_hdr_free_abd(hdr, B_TRUE); + if (HDR_HAS_RABD(hdr)) + arc_hdr_free_abd(hdr, B_TRUE); + } } ASSERT0P(hdr->b_hash_next);