zbd: improve replay range validation
[fio.git] / zbd.c
diff --git a/zbd.c b/zbd.c
index 123b39087db05fc9ab533ee15edb2c80095ea281..1ee238e85f242e3e35f736ae72e09f57b478f98c 100644 (file)
--- a/zbd.c
+++ b/zbd.c
@@ -166,7 +166,8 @@ static bool zbd_zone_full(const struct fio_file *f, struct fio_zone_info *z,
                z->wp + required > zbd_zone_capacity_end(z);
 }
 
-static void zone_lock(struct thread_data *td, struct fio_file *f, struct fio_zone_info *z)
+static void zone_lock(struct thread_data *td, const struct fio_file *f,
+                     struct fio_zone_info *z)
 {
        struct zoned_block_device_info *zbd = f->zbd_info;
        uint32_t nz = z - zbd->zone_info;
@@ -174,6 +175,8 @@ static void zone_lock(struct thread_data *td, struct fio_file *f, struct fio_zon
        /* A thread should never lock zones outside its working area. */
        assert(f->min_zone <= nz && nz < f->max_zone);
 
+       assert(z->has_wp);
+
        /*
         * Lock the io_u target zone. The zone will be unlocked if io_u offset
         * is changed or when io_u completes and zbd_put_io() executed.
@@ -194,6 +197,7 @@ static inline void zone_unlock(struct fio_zone_info *z)
 {
        int ret;
 
+       assert(z->has_wp);
        ret = pthread_mutex_unlock(&z->mutex);
        assert(!ret);
 }
@@ -321,10 +325,6 @@ static bool zbd_verify_sizes(void)
                                         (unsigned long long) new_end - f->file_offset);
                                f->io_size = new_end - f->file_offset;
                        }
-
-                       f->min_zone = zbd_zone_idx(f, f->file_offset);
-                       f->max_zone = zbd_zone_idx(f, f->file_offset + f->io_size);
-                       assert(f->min_zone < f->max_zone);
                }
        }
 
@@ -526,8 +526,9 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f)
                offset = z->start + z->len;
                if (j >= nr_zones)
                        break;
-               nrz = zbd_report_zones(td, f, offset,
-                                           zones, ZBD_REPORT_MAX_ZONES);
+               nrz = zbd_report_zones(td, f, offset, zones,
+                                      min((uint32_t)(nr_zones - j),
+                                          ZBD_REPORT_MAX_ZONES));
                if (nrz < 0) {
                        ret = nrz;
                        log_info("fio: report zones (offset %llu) failed for %s (%d).\n",
@@ -676,6 +677,18 @@ int zbd_setup_files(struct thread_data *td)
                if (!zbd)
                        continue;
 
+               f->min_zone = zbd_zone_idx(f, f->file_offset);
+               f->max_zone = zbd_zone_idx(f, f->file_offset + f->io_size);
+
+               /*
+                * When all zones in the I/O range are conventional, io_size
+                * can be smaller than zone size, making min_zone the same
+                * as max_zone. This is why the assert below needs to be made
+                * conditional.
+                */
+               if (zbd_is_seq_job(f))
+                       assert(f->min_zone < f->max_zone);
+
                zbd->max_open_zones = zbd->max_open_zones ?: ZBD_MAX_OPEN_ZONES;
 
                if (td->o.max_open_zones > 0 &&
@@ -730,9 +743,10 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f,
 {
        uint64_t offset = z->start;
        uint64_t length = (z+1)->start - offset;
+       uint64_t data_in_zone = z->wp - z->start;
        int ret = 0;
 
-       if (z->wp == z->start)
+       if (!data_in_zone)
                return 0;
 
        assert(is_valid_offset(f, offset + length - 1));
@@ -751,7 +765,8 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f,
        }
 
        pthread_mutex_lock(&f->zbd_info->mutex);
-       f->zbd_info->sectors_with_data -= z->wp - z->start;
+       f->zbd_info->sectors_with_data -= data_in_zone;
+       f->zbd_info->wp_sectors_with_data -= data_in_zone;
        pthread_mutex_unlock(&f->zbd_info->mutex);
        z->wp = z->start;
        z->verify_block = 0;
@@ -771,11 +786,8 @@ static void zbd_close_zone(struct thread_data *td, const struct fio_file *f,
                if (f->zbd_info->open_zones[open_zone_idx] == zone_idx)
                        break;
        }
-       if (open_zone_idx == f->zbd_info->num_open_zones) {
-               dprint(FD_ZBD, "%s: zone %d is not open\n",
-                      f->file_name, zone_idx);
+       if (open_zone_idx == f->zbd_info->num_open_zones)
                return;
-       }
 
        dprint(FD_ZBD, "%s: closing zone %d\n", f->file_name, zone_idx);
        memmove(f->zbd_info->open_zones + open_zone_idx,
@@ -879,29 +891,37 @@ enum swd_action {
 };
 
 /* Calculate the number of sectors with data (swd) and perform action 'a' */
-static uint64_t zbd_process_swd(const struct fio_file *f, enum swd_action a)
+static uint64_t zbd_process_swd(struct thread_data *td,
+                               const struct fio_file *f, enum swd_action a)
 {
        struct fio_zone_info *zb, *ze, *z;
        uint64_t swd = 0;
+       uint64_t wp_swd = 0;
 
        zb = get_zone(f, f->min_zone);
        ze = get_zone(f, f->max_zone);
        for (z = zb; z < ze; z++) {
-               pthread_mutex_lock(&z->mutex);
+               if (z->has_wp) {
+                       zone_lock(td, f, z);
+                       wp_swd += z->wp - z->start;
+               }
                swd += z->wp - z->start;
        }
        pthread_mutex_lock(&f->zbd_info->mutex);
        switch (a) {
        case CHECK_SWD:
                assert(f->zbd_info->sectors_with_data == swd);
+               assert(f->zbd_info->wp_sectors_with_data == wp_swd);
                break;
        case SET_SWD:
                f->zbd_info->sectors_with_data = swd;
+               f->zbd_info->wp_sectors_with_data = wp_swd;
                break;
        }
        pthread_mutex_unlock(&f->zbd_info->mutex);
        for (z = zb; z < ze; z++)
-               zone_unlock(z);
+               if (z->has_wp)
+                       zone_unlock(z);
 
        return swd;
 }
@@ -912,37 +932,28 @@ static uint64_t zbd_process_swd(const struct fio_file *f, enum swd_action a)
  */
 static const bool enable_check_swd = false;
 
-/* Check whether the value of zbd_info.sectors_with_data is correct. */
-static void zbd_check_swd(const struct fio_file *f)
-{
-       if (!enable_check_swd)
-               return;
-
-       zbd_process_swd(f, CHECK_SWD);
-}
-
-static void zbd_init_swd(struct fio_file *f)
+/* Check whether the values of zbd_info.*sectors_with_data are correct. */
+static void zbd_check_swd(struct thread_data *td, const struct fio_file *f)
 {
-       uint64_t swd;
-
        if (!enable_check_swd)
                return;
 
-       swd = zbd_process_swd(f, SET_SWD);
-       dprint(FD_ZBD, "%s(%s): swd = %" PRIu64 "\n", __func__, f->file_name,
-              swd);
+       zbd_process_swd(td, f, CHECK_SWD);
 }
 
 void zbd_file_reset(struct thread_data *td, struct fio_file *f)
 {
        struct fio_zone_info *zb, *ze;
+       uint64_t swd;
 
        if (!f->zbd_info || !td_write(td))
                return;
 
        zb = get_zone(f, f->min_zone);
        ze = get_zone(f, f->max_zone);
-       zbd_init_swd(f);
+       swd = zbd_process_swd(td, f, SET_SWD);
+       dprint(FD_ZBD, "%s(%s): swd = %" PRIu64 "\n", __func__, f->file_name,
+              swd);
        /*
         * If data verification is enabled reset the affected zones before
         * writing any data to avoid that a zone reset has to be issued while
@@ -1032,7 +1043,8 @@ static uint32_t pick_random_zone_idx(const struct fio_file *f,
 /*
  * Modify the offset of an I/O unit that does not refer to an open zone such
  * that it refers to an open zone. Close an open zone and open a new zone if
- * necessary. This algorithm can only work correctly if all write pointers are
+ * necessary. The open zone is searched across sequential zones.
+ * This algorithm can only work correctly if all write pointers are
  * a multiple of the fio block size. The caller must neither hold z->mutex
  * nor f->zbd_info->mutex. Returns with z->mutex held upon success.
  */
@@ -1075,15 +1087,18 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
                uint32_t tmp_idx;
 
                z = get_zone(f, zone_idx);
-
-               zone_lock(td, f, z);
+               if (z->has_wp)
+                       zone_lock(td, f, z);
                pthread_mutex_lock(&f->zbd_info->mutex);
-               if (td->o.max_open_zones == 0 && td->o.job_max_open_zones == 0)
-                       goto examine_zone;
-               if (f->zbd_info->num_open_zones == 0) {
-                       dprint(FD_ZBD, "%s(%s): no zones are open\n",
-                              __func__, f->file_name);
-                       goto open_other_zone;
+               if (z->has_wp) {
+                       if (z->cond != ZBD_ZONE_COND_OFFLINE &&
+                           td->o.max_open_zones == 0 && td->o.job_max_open_zones == 0)
+                               goto examine_zone;
+                       if (f->zbd_info->num_open_zones == 0) {
+                               dprint(FD_ZBD, "%s(%s): no zones are open\n",
+                                      __func__, f->file_name);
+                               goto open_other_zone;
+                       }
                }
 
                /*
@@ -1111,7 +1126,8 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
                dprint(FD_ZBD, "%s(%s): no candidate zone\n",
                        __func__, f->file_name);
                pthread_mutex_unlock(&f->zbd_info->mutex);
-               zone_unlock(z);
+               if (z->has_wp)
+                       zone_unlock(z);
                return NULL;
 
 found_candidate_zone:
@@ -1120,7 +1136,8 @@ found_candidate_zone:
                        break;
                zone_idx = new_zone_idx;
                pthread_mutex_unlock(&f->zbd_info->mutex);
-               zone_unlock(z);
+               if (z->has_wp)
+                       zone_unlock(z);
        }
 
        /* Both z->mutex and f->zbd_info->mutex are held. */
@@ -1157,7 +1174,8 @@ open_other_zone:
        /* Zone 'z' is full, so try to open a new zone. */
        for (i = f->io_size / f->zbd_info->zone_size; i > 0; i--) {
                zone_idx++;
-               zone_unlock(z);
+               if (z->has_wp)
+                       zone_unlock(z);
                z++;
                if (!is_valid_offset(f, z->start)) {
                        /* Wrap-around. */
@@ -1165,6 +1183,8 @@ open_other_zone:
                        z = get_zone(f, zone_idx);
                }
                assert(is_valid_offset(f, z->start));
+               if (!z->has_wp)
+                       continue;
                zone_lock(td, f, z);
                if (z->open)
                        continue;
@@ -1200,6 +1220,8 @@ out:
        dprint(FD_ZBD, "%s(%s): returning zone %d\n", __func__, f->file_name,
               zone_idx);
        io_u->offset = z->start;
+       assert(z->has_wp);
+       assert(z->cond != ZBD_ZONE_COND_OFFLINE);
        return z;
 }
 
@@ -1217,20 +1239,33 @@ static struct fio_zone_info *zbd_replay_write_order(struct thread_data *td,
                assert(z);
        }
 
-       if (z->verify_block * min_bs >= z->capacity)
+       if (z->verify_block * min_bs >= z->capacity) {
                log_err("%s: %d * %d >= %llu\n", f->file_name, z->verify_block,
                        min_bs, (unsigned long long)z->capacity);
-       io_u->offset = z->start + z->verify_block++ * min_bs;
+               /*
+                * If the assertion below fails during a test run, adding
+                * "--experimental_verify=1" to the command line may help.
+                */
+               assert(false);
+       }
+       io_u->offset = z->start + z->verify_block * min_bs;
+       if (io_u->offset + io_u->buflen >= zbd_zone_capacity_end(z)) {
+               log_err("%s: %llu + %llu >= %lu\n", f->file_name, io_u->offset,
+                       io_u->buflen, zbd_zone_capacity_end(z));
+               assert(false);
+       }
+       z->verify_block += io_u->buflen / min_bs;
+
        return z;
 }
 
 /*
- * Find another zone for which @io_u fits below the write pointer. Start
- * searching in zones @zb + 1 .. @zl and continue searching in zones
- * @zf .. @zb - 1.
+ * Find another zone for which @io_u fits in the readable data in the zone.
+ * Search in zones @zb + 1 .. @zl. For random workload, also search in zones
+ * @zb - 1 .. @zf.
  *
- * Either returns NULL or returns a zone pointer and holds the mutex for that
- * zone.
+ * Either returns NULL or returns a zone pointer. When the zone has write
+ * pointer, hold the mutex for the zone.
  */
 static struct fio_zone_info *
 zbd_find_zone(struct thread_data *td, struct io_u *io_u,
@@ -1247,19 +1282,23 @@ zbd_find_zone(struct thread_data *td, struct io_u *io_u,
         */
        for (z1 = zb + 1, z2 = zb - 1; z1 < zl || z2 >= zf; z1++, z2--) {
                if (z1 < zl && z1->cond != ZBD_ZONE_COND_OFFLINE) {
-                       zone_lock(td, f, z1);
+                       if (z1->has_wp)
+                               zone_lock(td, f, z1);
                        if (z1->start + min_bs <= z1->wp)
                                return z1;
-                       zone_unlock(z1);
+                       if (z1->has_wp)
+                               zone_unlock(z1);
                } else if (!td_random(td)) {
                        break;
                }
                if (td_random(td) && z2 >= zf &&
                    z2->cond != ZBD_ZONE_COND_OFFLINE) {
-                       zone_lock(td, f, z2);
+                       if (z2->has_wp)
+                               zone_lock(td, f, z2);
                        if (z2->start + min_bs <= z2->wp)
                                return z2;
-                       zone_unlock(z2);
+                       if (z2->has_wp)
+                               zone_unlock(z2);
                }
        }
        dprint(FD_ZBD, "%s: adjusting random read offset failed\n",
@@ -1314,8 +1353,7 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q,
        assert(zone_idx < zbd_info->nr_zones);
        z = get_zone(f, zone_idx);
 
-       if (!z->has_wp)
-               return;
+       assert(z->has_wp);
 
        if (!success)
                goto unlock;
@@ -1333,8 +1371,10 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q,
                 * z->wp > zone_end means that one or more I/O errors
                 * have occurred.
                 */
-               if (z->wp <= zone_end)
+               if (z->wp <= zone_end) {
                        zbd_info->sectors_with_data += zone_end - z->wp;
+                       zbd_info->wp_sectors_with_data += zone_end - z->wp;
+               }
                pthread_mutex_unlock(&zbd_info->mutex);
                z->wp = zone_end;
                break;
@@ -1374,8 +1414,7 @@ static void zbd_put_io(struct thread_data *td, const struct io_u *io_u)
        assert(zone_idx < zbd_info->nr_zones);
        z = get_zone(f, zone_idx);
 
-       if (!z->has_wp)
-               return;
+       assert(z->has_wp);
 
        dprint(FD_ZBD,
               "%s: terminate I/O (%lld, %llu) for zone %u\n",
@@ -1384,7 +1423,7 @@ static void zbd_put_io(struct thread_data *td, const struct io_u *io_u)
        zbd_end_zone_io(td, io_u, z);
 
        zone_unlock(z);
-       zbd_check_swd(f);
+       zbd_check_swd(td, f);
 }
 
 /*
@@ -1538,9 +1577,31 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
        zb = get_zone(f, zone_idx_b);
        orig_zb = zb;
 
-       /* Accept the I/O offset for conventional zones. */
-       if (!zb->has_wp)
+       if (!zb->has_wp) {
+               /* Accept non-write I/Os for conventional zones. */
+               if (io_u->ddir != DDIR_WRITE)
+                       return io_u_accept;
+               /*
+                * Make sure that writes to conventional zones
+                * don't cross over to any sequential zones.
+                */
+               if (!(zb + 1)->has_wp ||
+                   io_u->offset + io_u->buflen <= (zb + 1)->start)
+                       return io_u_accept;
+
+               if (io_u->offset + min_bs > (zb + 1)->start) {
+                       dprint(FD_IO,
+                              "%s: off=%llu + min_bs=%u > next zone %lu\n",
+                              f->file_name, io_u->offset, min_bs,
+                              (zb + 1)->start);
+                       io_u->offset = zb->start + (zb + 1)->start - io_u->offset;
+                       new_len = min(io_u->buflen, (zb + 1)->start - io_u->offset);
+               } else {
+                       new_len = (zb + 1)->start - io_u->offset;
+               }
+               io_u->buflen = new_len / min_bs * min_bs;
                return io_u_accept;
+       }
 
        /*
         * Accept the I/O offset for reads if reading beyond the write pointer
@@ -1550,7 +1611,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
            io_u->ddir == DDIR_READ && td->o.read_beyond_wp)
                return io_u_accept;
 
-       zbd_check_swd(f);
+       zbd_check_swd(td, f);
 
        zone_lock(td, f, zb);
 
@@ -1558,7 +1619,12 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
        case DDIR_READ:
                if (td->runstate == TD_VERIFYING && td_write(td)) {
                        zb = zbd_replay_write_order(td, io_u, zb);
-                       zone_unlock(zb);
+                       /*
+                        * Since we return with the zone lock still held,
+                        * add an annotation to let Coverity know that it
+                        * is intentional.
+                        */
+                       /* coverity[missing_unlock] */
                        goto accept;
                }
                /*
@@ -1600,6 +1666,12 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
                        io_u->offset = zb->start +
                                ((io_u->offset - orig_zb->start) %
                                 (range - io_u->buflen)) / min_bs * min_bs;
+               /*
+                * When zbd_find_zone() returns a conventional zone,
+                * we can simply accept the new i/o offset here.
+                */
+               if (!zb->has_wp)
+                       return io_u_accept;
                /*
                 * Make sure the I/O does not cross over the zone wp position.
                 */
@@ -1626,7 +1698,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
                }
                /* Check whether the zone reset threshold has been exceeded */
                if (td->o.zrf.u.f) {
-                       if (f->zbd_info->sectors_with_data >=
+                       if (f->zbd_info->wp_sectors_with_data >=
                            f->io_size * td->o.zrt.u.f &&
                            zbd_dec_and_reset_write_cnt(td, f)) {
                                zb->reset_zone = 1;
@@ -1696,7 +1768,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
        assert(false);
 
 accept:
-       assert(zb);
+       assert(zb->has_wp);
        assert(zb->cond != ZBD_ZONE_COND_OFFLINE);
        assert(!io_u->zbd_queue_io);
        assert(!io_u->zbd_put_io);
@@ -1705,7 +1777,7 @@ accept:
        return io_u_accept;
 
 eof:
-       if (zb)
+       if (zb && zb->has_wp)
                zone_unlock(zb);
        return io_u_eof;
 }