]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/test/crimson/seastore/test_transaction_manager.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / test / crimson / seastore / test_transaction_manager.cc
index 9906f938a63ba6873419f75c37b95a1582961a13..8a7d0f03141edef11dda1cd6fe10c88b9460ebd9 100644 (file)
@@ -3,6 +3,8 @@
 
 #include <random>
 
+#include <boost/iterator/counting_iterator.hpp>
+
 #include "test/crimson/gtest_seastar.h"
 #include "test/crimson/seastore/transaction_manager_test_state.h"
 
@@ -58,7 +60,6 @@ struct transaction_manager_test_t :
 
   transaction_manager_test_t()
     : gen(rd()) {
-    init();
   }
 
   laddr_t get_random_laddr(size_t block_size, laddr_t limit) {
@@ -79,23 +80,169 @@ struct transaction_manager_test_t :
   }
 
   struct test_extents_t : std::map<laddr_t, test_extent_record_t> {
-  private:
-    void check_available(laddr_t addr, extent_len_t len) {
-      auto iter = upper_bound(addr);
-      if (iter != begin()) {
-       auto liter = iter;
-       liter--;
-       EXPECT_FALSE(liter->first + liter->second.desc.len > addr);
+    using delta_t = std::map<laddr_t, std::optional<test_extent_record_t>>;
+
+    struct delta_overlay_t {
+      const test_extents_t &extents;
+      const delta_t &delta;
+
+      delta_overlay_t(
+       const test_extents_t &extents,
+       const delta_t &delta)
+       : extents(extents), delta(delta) {}
+
+
+      class iterator {
+       friend class test_extents_t;
+
+       const delta_overlay_t &parent;
+       test_extents_t::const_iterator biter;
+       delta_t::const_iterator oiter;
+       std::optional<std::pair<laddr_t, test_extent_record_t>> cur;
+
+       iterator(
+         const delta_overlay_t &parent,
+         test_extents_t::const_iterator biter,
+         delta_t::const_iterator oiter)
+         : parent(parent), biter(biter), oiter(oiter) {}
+
+       laddr_t get_bkey() {
+         return biter == parent.extents.end() ? L_ADDR_MAX : biter->first;
+       }
+
+       laddr_t get_okey() {
+         return oiter == parent.delta.end() ? L_ADDR_MAX : oiter->first;
+       }
+
+       bool is_end() {
+         return oiter == parent.delta.end() && biter == parent.extents.end();
+       }
+
+       bool is_valid() {
+         return is_end() ||
+           ((get_okey() < get_bkey()) && (oiter->second)) ||
+           (get_okey() > get_bkey());
+       }
+
+       auto get_pair() {
+         assert(is_valid());
+         assert(!is_end());
+         auto okey = get_okey();
+         auto bkey = get_bkey();
+         return (
+           bkey < okey ?
+           std::pair<laddr_t, test_extent_record_t>(*biter) :
+           std::make_pair(okey, *(oiter->second)));
+       }
+
+       void adjust() {
+         while (!is_valid()) {
+           if (get_okey() < get_bkey()) {
+             assert(!oiter->second);
+             ++oiter;
+           } else {
+             assert(get_okey() == get_bkey());
+             ++biter;
+           }
+         }
+         assert(is_valid());
+         if (!is_end()) {
+           cur = get_pair();
+         } else {
+           cur = std::nullopt;
+         }
+       }
+
+      public:
+       iterator(const iterator &) = default;
+       iterator(iterator &&) = default;
+
+       iterator &operator++() {
+         assert(is_valid());
+         assert(!is_end());
+         if (get_bkey() < get_okey()) {
+           ++biter;
+         } else {
+           ++oiter;
+         }
+         adjust();
+         return *this;
+       }
+
+       bool operator==(const iterator &o) const {
+         return o.biter == biter && o.oiter == oiter;
+       }
+       bool operator!=(const iterator &o) const {
+         return !(*this == o);
+       }
+
+       auto operator*() {
+         assert(!is_end());
+         return *cur;
+       }
+       auto operator->() {
+         assert(!is_end());
+         return &*cur;
+       }
+      };
+
+      iterator begin() {
+       auto ret = iterator{*this, extents.begin(), delta.begin()};
+       ret.adjust();
+       return ret;
       }
-      if (iter != end()) {
-       EXPECT_FALSE(iter->first < addr + len);
+
+      iterator end() {
+       auto ret = iterator{*this, extents.end(), delta.end()};
+       // adjust unnecessary
+       return ret;
+      }
+
+      iterator lower_bound(laddr_t l) {
+       auto ret = iterator{*this, extents.lower_bound(l), delta.lower_bound(l)};
+       ret.adjust();
+       return ret;
+      }
+
+      iterator upper_bound(laddr_t l) {
+       auto ret = iterator{*this, extents.upper_bound(l), delta.upper_bound(l)};
+       ret.adjust();
+       return ret;
+      }
+
+      iterator find(laddr_t l) {
+       auto ret = lower_bound(l);
+       if (ret == end() || ret->first != l) {
+         return end();
+       } else {
+         return ret;
+       }
+      }
+    };
+  private:
+    void check_available(
+      laddr_t addr, extent_len_t len, const delta_t &delta
+    ) const {
+      delta_overlay_t overlay(*this, delta);
+      for (const auto &i: overlay) {
+       if (i.first < addr) {
+         EXPECT_FALSE(i.first + i.second.desc.len > addr);
+       } else {
+         EXPECT_FALSE(addr + len > i.first);
+       }
       }
     }
-    void check_hint(laddr_t hint, laddr_t addr, extent_len_t len) {
-      auto iter = lower_bound(hint);
+
+    void check_hint(
+      laddr_t hint,
+      laddr_t addr,
+      extent_len_t len,
+      delta_t &delta) const {
+      delta_overlay_t overlay(*this, delta);
+      auto iter = overlay.lower_bound(hint);
       laddr_t last = hint;
       while (true) {
-       if (iter == end() || iter->first > addr) {
+       if (iter == overlay.end() || iter->first > addr) {
          EXPECT_EQ(addr, last);
          break;
        }
@@ -104,32 +251,109 @@ struct transaction_manager_test_t :
        ++iter;
       }
     }
+
+    std::optional<test_extent_record_t> &populate_delta(
+      laddr_t addr, delta_t &delta, const test_extent_desc_t *desc) const {
+      auto diter = delta.find(addr);
+      if (diter != delta.end())
+       return diter->second;
+
+      auto iter = find(addr);
+      if (iter == end()) {
+       assert(desc);
+       auto ret = delta.emplace(
+         std::make_pair(addr, test_extent_record_t{*desc, 0}));
+       assert(ret.second);
+       return ret.first->second;
+      } else {
+       auto ret = delta.emplace(*iter);
+       assert(ret.second);
+       return ret.first->second;
+      }
+    }
   public:
-    void insert(TestBlock &extent) {
-      check_available(extent.get_laddr(), extent.get_length());
-      emplace(
-       std::make_pair(
-         extent.get_laddr(),
-         test_extent_record_t{extent.get_desc(), 1}
-       ));
-    }
-    void alloced(laddr_t hint, TestBlock &extent) {
-      check_hint(hint, extent.get_laddr(), extent.get_length());
-      insert(extent);
+    delta_overlay_t get_overlay(const delta_t &delta) const {
+      return delta_overlay_t{*this, delta};
+    }
+
+    void insert(TestBlock &extent, delta_t &delta) const {
+      check_available(extent.get_laddr(), extent.get_length(), delta);
+      delta[extent.get_laddr()] =
+       test_extent_record_t{extent.get_desc(), 1};
     }
+
+    void alloced(laddr_t hint, TestBlock &extent, delta_t &delta) const {
+      check_hint(hint, extent.get_laddr(), extent.get_length(), delta);
+      insert(extent, delta);
+    }
+
+    bool contains(laddr_t addr, const delta_t &delta) const {
+      delta_overlay_t overlay(*this, delta);
+      return overlay.find(addr) != overlay.end();
+    }
+
+    test_extent_record_t get(laddr_t addr, const delta_t &delta) const {
+      delta_overlay_t overlay(*this, delta);
+      auto iter = overlay.find(addr);
+      assert(iter != overlay.end());
+      return iter->second;
+    }
+
+    void update(
+      laddr_t addr,
+      const test_extent_desc_t &desc,
+      delta_t &delta) const {
+      auto &rec = populate_delta(addr, delta, &desc);
+      assert(rec);
+      rec->desc = desc;
+    }
+
+    int inc_ref(
+      laddr_t addr,
+      delta_t &delta) const {
+      auto &rec = populate_delta(addr, delta, nullptr);
+      assert(rec);
+      return ++rec->refcount;
+    }
+
+    int dec_ref(
+      laddr_t addr,
+      delta_t &delta) const {
+      auto &rec = populate_delta(addr, delta, nullptr);
+      assert(rec);
+      assert(rec->refcount > 0);
+      rec->refcount--;
+      if (rec->refcount == 0) {
+       delta[addr] = std::nullopt;
+       return 0;
+      } else {
+       return rec->refcount;
+      }
+    }
+
+    void consume(const delta_t &delta) {
+      for (const auto &i : delta) {
+       if (i.second) {
+         (*this)[i.first] = *i.second;
+       } else {
+         erase(i.first);
+       }
+      }
+    }
+
   } test_mappings;
 
   struct test_transaction_t {
     TransactionRef t;
-    test_extents_t mappings;
+    test_extents_t::delta_t mapping_delta;
   };
 
   test_transaction_t create_transaction() {
-    return { tm->create_transaction(), test_mappings };
+    return { create_mutate_transaction(), {} };
   }
 
-  test_transaction_t create_weak_transaction() {
-    return { tm->create_weak_transaction(), test_mappings };
+  test_transaction_t create_weak_test_transaction() {
+    return { create_weak_transaction(), {} };
   }
 
   TestBlockRef alloc_extent(
@@ -137,14 +361,13 @@ struct transaction_manager_test_t :
     laddr_t hint,
     extent_len_t len,
     char contents) {
-    auto extent = tm->alloc_extent<TestBlock>(
-      *(t.t),
-      hint,
-      len).unsafe_get0();
+    auto extent = with_trans_intr(*(t.t), [&](auto& trans) {
+      return tm->alloc_extent<TestBlock>(trans, hint, len);
+    }).unsafe_get0();
     extent->set_contents(contents);
-    EXPECT_FALSE(t.mappings.count(extent->get_laddr()));
+    EXPECT_FALSE(test_mappings.contains(extent->get_laddr(), t.mapping_delta));
     EXPECT_EQ(len, extent->get_length());
-    t.mappings.alloced(hint, *extent);
+    test_mappings.alloced(hint, *extent, t.mapping_delta);
     return extent;
   }
 
@@ -160,15 +383,19 @@ struct transaction_manager_test_t :
   }
 
   bool check_usage() {
-    auto t = create_weak_transaction();
+    auto t = create_weak_test_transaction();
     SpaceTrackerIRef tracker(segment_cleaner->get_empty_space_tracker());
-    lba_manager->scan_mapped_space(
+    with_trans_intr(
       *t.t,
-      [&tracker](auto offset, auto len) {
-       tracker->allocate(
-         offset.segment,
-         offset.offset,
-         len);
+      [this, &tracker](auto &t) {
+       return lba_manager->scan_mapped_space(
+         t,
+         [&tracker](auto offset, auto len) {
+           tracker->allocate(
+             offset.as_seg_paddr().get_segment_id(),
+             offset.as_seg_paddr().get_segment_off(),
+             len);
+         });
       }).unsafe_get0();
     return segment_cleaner->debug_check_space(*tracker);
   }
@@ -186,7 +413,7 @@ struct transaction_manager_test_t :
   }
 
   void check_mappings() {
-    auto t = create_weak_transaction();
+    auto t = create_weak_test_transaction();
     check_mappings(t);
   }
 
@@ -194,74 +421,189 @@ struct transaction_manager_test_t :
     test_transaction_t &t,
     laddr_t addr,
     extent_len_t len) {
-    ceph_assert(t.mappings.count(addr));
-    ceph_assert(t.mappings[addr].desc.len == len);
-
-    auto ret_list = tm->read_extents<TestBlock>(
-      *t.t, addr, len
-    ).unsafe_get0();
-    EXPECT_EQ(ret_list.size(), 1);
-    auto &ext = ret_list.begin()->second;
-    auto &laddr = ret_list.begin()->first;
-    EXPECT_EQ(addr, laddr);
+    ceph_assert(test_mappings.contains(addr, t.mapping_delta));
+    ceph_assert(test_mappings.get(addr, t.mapping_delta).desc.len == len);
+
+    auto ext = with_trans_intr(*(t.t), [&](auto& trans) {
+      return tm->read_extent<TestBlock>(trans, addr, len);
+    }).unsafe_get0();
     EXPECT_EQ(addr, ext->get_laddr());
     return ext;
   }
 
+  TestBlockRef try_get_extent(
+    test_transaction_t &t,
+    laddr_t addr,
+    extent_len_t len) {
+    ceph_assert(test_mappings.contains(addr, t.mapping_delta));
+    ceph_assert(test_mappings.get(addr, t.mapping_delta).desc.len == len);
+
+    using ertr = with_trans_ertr<TransactionManager::read_extent_iertr>;
+    using ret = ertr::future<TestBlockRef>;
+    auto ext = with_trans_intr(*(t.t), [&](auto& trans) {
+      return tm->read_extent<TestBlock>(trans, addr, len);
+    }).safe_then([](auto ext) -> ret {
+      return ertr::make_ready_future<TestBlockRef>(ext);
+    }).handle_error(
+      [](const crimson::ct_error::eagain &e) {
+       return seastar::make_ready_future<TestBlockRef>();
+      },
+      crimson::ct_error::assert_all{
+       "get_extent got invalid error"
+      }
+    ).get0();
+    if (ext) {
+      EXPECT_EQ(addr, ext->get_laddr());
+    }
+    return ext;
+  }
+
   test_block_mutator_t mutator;
   TestBlockRef mutate_extent(
     test_transaction_t &t,
     TestBlockRef ref) {
-    ceph_assert(t.mappings.count(ref->get_laddr()));
-    ceph_assert(t.mappings[ref->get_laddr()].desc.len == ref->get_length());
+    ceph_assert(test_mappings.contains(ref->get_laddr(), t.mapping_delta));
+    ceph_assert(
+      test_mappings.get(ref->get_laddr(), t.mapping_delta).desc.len ==
+      ref->get_length());
+
     auto ext = tm->get_mutable_extent(*t.t, ref)->cast<TestBlock>();
     EXPECT_EQ(ext->get_laddr(), ref->get_laddr());
     EXPECT_EQ(ext->get_desc(), ref->get_desc());
     mutator.mutate(*ext, gen);
-    t.mappings[ext->get_laddr()].update(ext->get_desc());
+
+    test_mappings.update(ext->get_laddr(), ext->get_desc(), t.mapping_delta);
+    return ext;
+  }
+
+  TestBlockRef mutate_addr(
+    test_transaction_t &t,
+    laddr_t offset,
+    size_t length) {
+    auto ext = get_extent(t, offset, length);
+    mutate_extent(t, ext);
     return ext;
   }
 
   void inc_ref(test_transaction_t &t, laddr_t offset) {
-    ceph_assert(t.mappings.count(offset));
-    ceph_assert(t.mappings[offset].refcount > 0);
-    auto refcnt = tm->inc_ref(*t.t, offset).unsafe_get0();
-    t.mappings[offset].refcount++;
-    EXPECT_EQ(refcnt, t.mappings[offset].refcount);
+    ceph_assert(test_mappings.contains(offset, t.mapping_delta));
+    ceph_assert(test_mappings.get(offset, t.mapping_delta).refcount > 0);
+
+    auto refcnt = with_trans_intr(*(t.t), [&](auto& trans) {
+      return tm->inc_ref(trans, offset);
+    }).unsafe_get0();
+    auto check_refcnt = test_mappings.inc_ref(offset, t.mapping_delta);
+    EXPECT_EQ(refcnt, check_refcnt);
   }
 
   void dec_ref(test_transaction_t &t, laddr_t offset) {
-    ceph_assert(t.mappings.count(offset));
-    ceph_assert(t.mappings[offset].refcount > 0);
-    auto refcnt = tm->dec_ref(*t.t, offset).unsafe_get0();
-    t.mappings[offset].refcount--;
-    EXPECT_EQ(refcnt, t.mappings[offset].refcount);
-    if (t.mappings[offset].refcount == 0) {
-      t.mappings.erase(offset);
-    }
+    ceph_assert(test_mappings.contains(offset, t.mapping_delta));
+    ceph_assert(test_mappings.get(offset, t.mapping_delta).refcount > 0);
+
+    auto refcnt = with_trans_intr(*(t.t), [&](auto& trans) {
+      return tm->dec_ref(trans, offset);
+    }).unsafe_get0();
+    auto check_refcnt = test_mappings.dec_ref(offset, t.mapping_delta);
+    EXPECT_EQ(refcnt, check_refcnt);
+    if (refcnt == 0)
+      logger().debug("dec_ref: {} at refcount 0", offset);
   }
 
   void check_mappings(test_transaction_t &t) {
-    for (auto &i: t.mappings) {
+    auto overlay = test_mappings.get_overlay(t.mapping_delta);
+    for (const auto &i: overlay) {
       logger().debug("check_mappings: {}->{}", i.first, i.second);
       auto ext = get_extent(t, i.first, i.second.desc.len);
       EXPECT_EQ(i.second, ext->get_desc());
     }
-    auto lt = create_weak_transaction();
-    lba_manager->scan_mappings(
-      *lt.t,
-      0,
-      L_ADDR_MAX,
-      [iter=lt.mappings.begin(), &lt](auto l, auto p, auto len) mutable {
-       EXPECT_NE(iter, lt.mappings.end());
-       EXPECT_EQ(l, iter->first);
-       ++iter;
+    with_trans_intr(
+      *t.t,
+      [this, &overlay](auto &t) {
+       return lba_manager->scan_mappings(
+         t,
+         0,
+         L_ADDR_MAX,
+         [iter=overlay.begin(), &overlay](auto l, auto p, auto len) mutable {
+           EXPECT_NE(iter, overlay.end());
+           logger().debug(
+             "check_mappings: scan {}",
+             l);
+           EXPECT_EQ(l, iter->first);
+           ++iter;
+         });
       }).unsafe_get0();
   }
 
-  void submit_transaction(test_transaction_t t) {
-    tm->submit_transaction(std::move(t.t)).unsafe_get();
-    test_mappings = t.mappings;
+  bool try_submit_transaction(test_transaction_t t) {
+    using ertr = with_trans_ertr<TransactionManager::submit_transaction_iertr>;
+    using ret = ertr::future<bool>;
+    bool success = submit_transaction_fut(*t.t
+    ).safe_then([]() -> ret {
+      return ertr::make_ready_future<bool>(true);
+    }).handle_error(
+      [](const crimson::ct_error::eagain &e) {
+       return seastar::make_ready_future<bool>(false);
+      },
+      crimson::ct_error::assert_all{
+       "try_submit_transaction hit invalid error"
+      }
+    ).then([this](auto ret) {
+      return segment_cleaner->run_until_halt().then([ret] { return ret; });
+    }).get0();
+
+    if (success) {
+      test_mappings.consume(t.mapping_delta);
+    }
+
+    return success;
+  }
+
+  void submit_transaction(test_transaction_t &&t) {
+    bool success = try_submit_transaction(std::move(t));
+    EXPECT_TRUE(success);
+  }
+
+  void submit_transaction_expect_conflict(test_transaction_t &&t) {
+    bool success = try_submit_transaction(std::move(t));
+    EXPECT_FALSE(success);
+  }
+
+  auto allocate_sequentially(const size_t& size, int &num) {
+    return repeat_eagain([&, this] {
+      return seastar::do_with(
+       create_transaction(),
+       [&, this](auto &t) {
+         return with_trans_intr(
+           *t.t,
+           [&, this](auto &) {
+             return trans_intr::do_for_each(
+               boost::make_counting_iterator(0),
+               boost::make_counting_iterator(num),
+               [&, this](auto) {
+                 return tm->alloc_extent<TestBlock>(
+                   *(t.t), L_ADDR_MIN, size
+                 ).si_then([&, this](auto extent) {
+                   extent->set_contents(get_random_contents());
+                   EXPECT_FALSE(
+                     test_mappings.contains(extent->get_laddr(), t.mapping_delta));
+                   EXPECT_EQ(size, extent->get_length());
+                   test_mappings.alloced(extent->get_laddr(), *extent, t.mapping_delta);
+                   return seastar::now();
+                 });
+               }).si_then([&t, this] {
+                 return tm->submit_transaction(*t.t);
+               });
+           }).safe_then([&t, this] {
+             test_mappings.consume(t.mapping_delta);
+           });
+       });
+    }).safe_then([this]() {
+      return segment_cleaner->run_until_halt();
+    }).handle_error(
+      crimson::ct_error::assert_all{
+       "Invalid error in SeaStore::list_collections"
+      }
+    );
   }
 };
 
@@ -324,6 +666,110 @@ TEST_F(transaction_manager_test_t, mutate)
   });
 }
 
+TEST_F(transaction_manager_test_t, allocate_lba_conflict)
+{
+  constexpr laddr_t SIZE = 4096;
+  run_async([this] {
+    constexpr laddr_t ADDR = 0xFF * SIZE;
+    constexpr laddr_t ADDR2 = 0xFE * SIZE;
+    auto t = create_transaction();
+    auto t2 = create_transaction();
+
+    // These should conflict as they should both modify the lba root
+    auto extent = alloc_extent(
+      t,
+      ADDR,
+      SIZE,
+      'a');
+    ASSERT_EQ(ADDR, extent->get_laddr());
+    check_mappings(t);
+    check();
+
+    auto extent2 = alloc_extent(
+      t2,
+      ADDR2,
+      SIZE,
+      'a');
+    ASSERT_EQ(ADDR2, extent2->get_laddr());
+    check_mappings(t2);
+    extent2.reset();
+
+    submit_transaction(std::move(t2));
+    submit_transaction_expect_conflict(std::move(t));
+  });
+}
+
+TEST_F(transaction_manager_test_t, mutate_lba_conflict)
+{
+  constexpr laddr_t SIZE = 4096;
+  run_async([this] {
+    {
+      auto t = create_transaction();
+      for (unsigned i = 0; i < 300; ++i) {
+       auto extent = alloc_extent(
+         t,
+         laddr_t(i * SIZE),
+         SIZE);
+      }
+      check_mappings(t);
+      submit_transaction(std::move(t));
+      check();
+    }
+
+    constexpr laddr_t ADDR = 150 * SIZE;
+    {
+      auto t = create_transaction();
+      auto t2 = create_transaction();
+
+      mutate_addr(t, ADDR, SIZE);
+      mutate_addr(t2, ADDR, SIZE);
+
+      submit_transaction(std::move(t));
+      submit_transaction_expect_conflict(std::move(t2));
+    }
+    check();
+
+    {
+      auto t = create_transaction();
+      mutate_addr(t, ADDR, SIZE);
+      submit_transaction(std::move(t));
+    }
+    check();
+  });
+}
+
+TEST_F(transaction_manager_test_t, concurrent_mutate_lba_no_conflict)
+{
+  constexpr laddr_t SIZE = 4096;
+  constexpr size_t NUM = 500;
+  constexpr laddr_t addr = 0;
+  constexpr laddr_t addr2 = SIZE * (NUM - 1);
+  run_async([this] {
+    {
+      auto t = create_transaction();
+      for (unsigned i = 0; i < NUM; ++i) {
+       auto extent = alloc_extent(
+         t,
+         laddr_t(i * SIZE),
+         SIZE);
+      }
+      submit_transaction(std::move(t));
+    }
+
+    {
+      auto t = create_transaction();
+      auto t2 = create_transaction();
+
+      mutate_addr(t, addr, SIZE);
+      mutate_addr(t2, addr2, SIZE);
+
+      submit_transaction(std::move(t));
+      submit_transaction(std::move(t2));
+    }
+    check();
+  });
+}
+
 TEST_F(transaction_manager_test_t, create_remove_same_transaction)
 {
   constexpr laddr_t SIZE = 4096;
@@ -385,7 +831,6 @@ TEST_F(transaction_manager_test_t, split_merge_read_same_transaction)
   });
 }
 
-
 TEST_F(transaction_manager_test_t, inc_dec_ref)
 {
   constexpr laddr_t SIZE = 4096;
@@ -493,3 +938,81 @@ TEST_F(transaction_manager_test_t, random_writes)
     }
   });
 }
+
+TEST_F(transaction_manager_test_t, random_writes_concurrent)
+{
+  constexpr unsigned WRITE_STREAMS = 256;
+
+  constexpr size_t TOTAL = 4<<20;
+  constexpr size_t BSIZE = 4<<10;
+  constexpr size_t BLOCKS = TOTAL / BSIZE;
+  run_async([this] {
+    seastar::parallel_for_each(
+      boost::make_counting_iterator(0u),
+      boost::make_counting_iterator(WRITE_STREAMS),
+      [&](auto idx) {
+       for (unsigned i = idx; i < BLOCKS; i += WRITE_STREAMS) {
+         while (true) {
+           auto t = create_transaction();
+           auto extent = alloc_extent(
+             t,
+             i * BSIZE,
+             BSIZE);
+           ASSERT_EQ(i * BSIZE, extent->get_laddr());
+           if (try_submit_transaction(std::move(t)))
+             break;
+         }
+       }
+      }).get0();
+
+    int writes = 0;
+    unsigned failures = 0;
+    seastar::parallel_for_each(
+      boost::make_counting_iterator(0u),
+      boost::make_counting_iterator(WRITE_STREAMS),
+      [&](auto) {
+        return seastar::async([&] {
+          while (writes < 300) {
+            auto t = create_transaction();
+            auto ext = try_get_extent(
+              t,
+              get_random_laddr(BSIZE, TOTAL),
+              BSIZE);
+            if (!ext){
+              failures++;
+              continue;
+            }
+           auto mut = mutate_extent(t, ext);
+           auto success = try_submit_transaction(std::move(t));
+           writes += success;
+           failures += !success;
+         }
+       });
+      }).get0();
+    replay();
+    logger().debug("random_writes: checking");
+    check();
+    logger().debug(
+      "random_writes: {} suceeded, {} failed",
+      writes,
+      failures
+    );
+  });
+}
+
+TEST_F(transaction_manager_test_t, find_hole_assert_trigger)
+{
+  constexpr unsigned max = 10;
+  constexpr size_t BSIZE = 4<<10;
+  int num = 40;
+  run([&, this] {
+    return seastar::parallel_for_each(
+      boost::make_counting_iterator(0u),
+      boost::make_counting_iterator(max),
+      [&, this](auto idx) {
+        return allocate_sequentially(BSIZE, num);
+    });
+  });
+
+}
+