aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--module/zfs/abd.c56
1 files changed, 55 insertions, 1 deletions
diff --git a/module/zfs/abd.c b/module/zfs/abd.c
index 3d1ed63cf..6018a42ca 100644
--- a/module/zfs/abd.c
+++ b/module/zfs/abd.c
@@ -139,12 +139,15 @@ abd_verify(abd_t *abd)
if (abd_is_linear(abd)) {
ASSERT3P(ABD_LINEAR_BUF(abd), !=, NULL);
} else if (abd_is_gang(abd)) {
+ uint_t child_sizes = 0;
for (abd_t *cabd = list_head(&ABD_GANG(abd).abd_gang_chain);
cabd != NULL;
cabd = list_next(&ABD_GANG(abd).abd_gang_chain, cabd)) {
ASSERT(list_link_active(&cabd->abd_gang_link));
+ child_sizes += cabd->abd_size;
abd_verify(cabd);
}
+ ASSERT3U(abd->abd_size, ==, child_sizes);
} else {
abd_verify_scatter(abd);
}
@@ -378,16 +381,67 @@ abd_alloc_gang_abd(void)
}
/*
+ * Add a child gang ABD to a parent gang ABDs chained list.
+ */
+static void
+abd_gang_add_gang(abd_t *pabd, abd_t *cabd, boolean_t free_on_free)
+{
+ ASSERT(abd_is_gang(pabd));
+ ASSERT(abd_is_gang(cabd));
+
+ if (free_on_free) {
+ /*
+ * If the parent is responsible for freeing the child gang
+ * ABD we will just splice the childs children ABD list to
+ * the parents list and immediately free the child gang ABD
+ * struct. The parent gang ABDs children from the child gang
+ * will retain all the free_on_free settings after being
+ * added to the parents list.
+ */
+ pabd->abd_size += cabd->abd_size;
+ list_move_tail(&ABD_GANG(pabd).abd_gang_chain,
+ &ABD_GANG(cabd).abd_gang_chain);
+ ASSERT(list_is_empty(&ABD_GANG(cabd).abd_gang_chain));
+ abd_verify(pabd);
+ abd_free_struct(cabd);
+ } else {
+ for (abd_t *child = list_head(&ABD_GANG(cabd).abd_gang_chain);
+ child != NULL;
+ child = list_next(&ABD_GANG(cabd).abd_gang_chain, child)) {
+ /*
+ * We always pass B_FALSE for free_on_free as it is the
+ * original child gang ABDs responsibilty to determine
+ * if any of its child ABDs should be free'd on the call
+ * to abd_free().
+ */
+ abd_gang_add(pabd, child, B_FALSE);
+ }
+ abd_verify(pabd);
+ }
+}
+
+/*
* Add a child ABD to a gang ABD's chained list.
*/
void
abd_gang_add(abd_t *pabd, abd_t *cabd, boolean_t free_on_free)
{
ASSERT(abd_is_gang(pabd));
- ASSERT(!abd_is_gang(cabd));
abd_t *child_abd = NULL;
/*
+ * If the child being added is a gang ABD, we will add the
+ * childs ABDs to the parent gang ABD. This alllows us to account
+ * for the offset correctly in the parent gang ABD.
+ */
+ if (abd_is_gang(cabd)) {
+ ASSERT(!list_link_active(&cabd->abd_gang_link));
+ ASSERT(!list_is_empty(&ABD_GANG(cabd).abd_gang_chain));
+ return (abd_gang_add_gang(pabd, cabd, free_on_free));
+ }
+ ASSERT(!abd_is_gang(cabd));
+
+ /*
* In order to verify that an ABD is not already part of
* another gang ABD, we must lock the child ABD's abd_mtx
* to check its abd_gang_link status. We unlock the abd_mtx