Print this page
5222 l2arc compression buffers "leak"
Author:         Andriy Gapon <avg@FreeBSD.org>
Reviewed by:    Saso Kiselkov <skiselkov.ml@gmail.com>
Reviewed by:    Xin Li <delphij@FreeBSD.org>

@@ -306,10 +306,11 @@
         kstat_named_t arcstat_l2_writes_error;
         kstat_named_t arcstat_l2_writes_hdr_miss;
         kstat_named_t arcstat_l2_evict_lock_retry;
         kstat_named_t arcstat_l2_evict_reading;
         kstat_named_t arcstat_l2_free_on_write;
+        kstat_named_t arcstat_l2_cdata_free_on_write;
         kstat_named_t arcstat_l2_abort_lowmem;
         kstat_named_t arcstat_l2_cksum_bad;
         kstat_named_t arcstat_l2_io_error;
         kstat_named_t arcstat_l2_size;
         kstat_named_t arcstat_l2_asize;

@@ -372,10 +373,11 @@
         { "l2_writes_error",            KSTAT_DATA_UINT64 },
         { "l2_writes_hdr_miss",         KSTAT_DATA_UINT64 },
         { "l2_evict_lock_retry",        KSTAT_DATA_UINT64 },
         { "l2_evict_reading",           KSTAT_DATA_UINT64 },
         { "l2_free_on_write",           KSTAT_DATA_UINT64 },
+        { "l2_cdata_free_on_write",     KSTAT_DATA_UINT64 },
         { "l2_abort_lowmem",            KSTAT_DATA_UINT64 },
         { "l2_cksum_bad",               KSTAT_DATA_UINT64 },
         { "l2_io_error",                KSTAT_DATA_UINT64 },
         { "l2_size",                    KSTAT_DATA_UINT64 },
         { "l2_asize",                   KSTAT_DATA_UINT64 },

@@ -1471,28 +1473,36 @@
         ARCSTAT_CONDSTAT(!(hdr->b_flags & ARC_PREFETCH),
             demand, prefetch, hdr->b_type != ARC_BUFC_METADATA,
             data, metadata, hits);
 }
 
+static void
+arc_buf_free_on_write(void *data, size_t size,
+    void (*free_func)(void *, size_t))
+{
+        l2arc_data_free_t *df;
+
+        df = kmem_alloc(sizeof (l2arc_data_free_t), KM_SLEEP);
+        df->l2df_data = data;
+        df->l2df_size = size;
+        df->l2df_func = free_func;
+        mutex_enter(&l2arc_free_on_write_mtx);
+        list_insert_head(l2arc_free_on_write, df);
+        mutex_exit(&l2arc_free_on_write_mtx);
+}
+
 /*
  * Free the arc data buffer.  If it is an l2arc write in progress,
  * the buffer is placed on l2arc_free_on_write to be freed later.
  */
 static void
 arc_buf_data_free(arc_buf_t *buf, void (*free_func)(void *, size_t))
 {
         arc_buf_hdr_t *hdr = buf->b_hdr;
 
         if (HDR_L2_WRITING(hdr)) {
-                l2arc_data_free_t *df;
-                df = kmem_alloc(sizeof (l2arc_data_free_t), KM_SLEEP);
-                df->l2df_data = buf->b_data;
-                df->l2df_size = hdr->b_size;
-                df->l2df_func = free_func;
-                mutex_enter(&l2arc_free_on_write_mtx);
-                list_insert_head(l2arc_free_on_write, df);
-                mutex_exit(&l2arc_free_on_write_mtx);
+                arc_buf_free_on_write(buf->b_data, hdr->b_size, free_func);
                 ARCSTAT_BUMP(arcstat_l2_free_on_write);
         } else {
                 free_func(buf->b_data, hdr->b_size);
         }
 }

@@ -1500,10 +1510,27 @@
 /*
  * Free up buf->b_data and if 'remove' is set, then pull the
  * arc_buf_t off of the the arc_buf_hdr_t's list and free it.
  */
 static void
+arc_buf_l2_cdata_free(arc_buf_hdr_t *hdr)
+{
+        l2arc_buf_hdr_t *l2hdr = hdr->b_l2hdr;
+
+        ASSERT(MUTEX_HELD(&l2arc_buflist_mtx));
+
+        if (l2hdr->b_tmp_cdata == NULL)
+                return;
+
+        ASSERT(HDR_L2_WRITING(hdr));
+        arc_buf_free_on_write(l2hdr->b_tmp_cdata, hdr->b_size,
+            zio_data_buf_free);
+        ARCSTAT_BUMP(arcstat_l2_cdata_free_on_write);
+        l2hdr->b_tmp_cdata = NULL;
+}
+
+static void
 arc_buf_destroy(arc_buf_t *buf, boolean_t recycle, boolean_t remove)
 {
         arc_buf_t **bufp;
 
         /* free up data associated with the buf */

@@ -1594,10 +1621,11 @@
                         l2hdr = hdr->b_l2hdr;
                 }
 
                 if (l2hdr != NULL) {
                         list_remove(l2hdr->b_dev->l2ad_buflist, hdr);
+                        arc_buf_l2_cdata_free(hdr);
                         ARCSTAT_INCR(arcstat_l2_size, -hdr->b_size);
                         ARCSTAT_INCR(arcstat_l2_asize, -l2hdr->b_asize);
                         vdev_space_update(l2hdr->b_dev->l2ad_vdev,
                             -l2hdr->b_asize, 0, 0);
                         kmem_free(l2hdr, sizeof (l2arc_buf_hdr_t));

@@ -3349,10 +3377,11 @@
         }
 
         l2hdr = hdr->b_l2hdr;
         if (l2hdr) {
                 mutex_enter(&l2arc_buflist_mtx);
+                arc_buf_l2_cdata_free(hdr);
                 hdr->b_l2hdr = NULL;
                 list_remove(l2hdr->b_dev->l2ad_buflist, hdr);
         }
         buf_size = hdr->b_size;
 

@@ -4529,10 +4558,15 @@
                         if (ab->b_l2hdr != NULL) {
                                 abl2 = ab->b_l2hdr;
                                 ARCSTAT_INCR(arcstat_l2_asize, -abl2->b_asize);
                                 bytes_evicted += abl2->b_asize;
                                 ab->b_l2hdr = NULL;
+                                /*
+                                 * We are destroying l2hdr, so ensure that
+                                 * its compressed buffer, if any, is not leaked.
+                                 */
+                                ASSERT(abl2->b_tmp_cdata == NULL);
                                 kmem_free(abl2, sizeof (l2arc_buf_hdr_t));
                                 ARCSTAT_INCR(arcstat_l2_size, -ab->b_size);
                         }
                         list_remove(buflist, ab);
 

@@ -4757,10 +4791,18 @@
                  * (and now potentially also compressed).
                  */
                 buf_data = l2hdr->b_tmp_cdata;
                 buf_sz = l2hdr->b_asize;
 
+                /*
+                 * If the data has not been compressed, then clear b_tmp_cdata
+                 * to make sure that it points only to a temporary compression
+                 * buffer.
+                 */
+                if (!L2ARC_IS_VALID_COMPRESS(l2hdr->b_compress))
+                        l2hdr->b_tmp_cdata = NULL;
+
                 /* Compression may have squashed the buffer to zero length. */
                 if (buf_sz != 0) {
                         uint64_t buf_p_sz;
 
                         wzio = zio_write_phys(pio, dev->l2ad_vdev,

@@ -4947,19 +4989,22 @@
 static void
 l2arc_release_cdata_buf(arc_buf_hdr_t *ab)
 {
         l2arc_buf_hdr_t *l2hdr = ab->b_l2hdr;
 
-        if (l2hdr->b_compress == ZIO_COMPRESS_LZ4) {
+        ASSERT(L2ARC_IS_VALID_COMPRESS(l2hdr->b_compress));
+        if (l2hdr->b_compress != ZIO_COMPRESS_EMPTY) {
                 /*
                  * If the data was compressed, then we've allocated a
                  * temporary buffer for it, so now we need to release it.
                  */
                 ASSERT(l2hdr->b_tmp_cdata != NULL);
                 zio_data_buf_free(l2hdr->b_tmp_cdata, ab->b_size);
-        }
         l2hdr->b_tmp_cdata = NULL;
+        } else {
+                ASSERT(l2hdr->b_tmp_cdata == NULL);
+        }
 }
 
 /*
  * This thread feeds the L2ARC at regular intervals.  This is the beating
  * heart of the L2ARC.