summaryrefslogtreecommitdiffstats
path: root/src/test/crimson/seastore/test_transaction_manager.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/test/crimson/seastore/test_transaction_manager.cc
parentInitial commit. (diff)
downloadceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz
ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/test/crimson/seastore/test_transaction_manager.cc')
-rw-r--r--src/test/crimson/seastore/test_transaction_manager.cc1995
1 files changed, 1995 insertions, 0 deletions
diff --git a/src/test/crimson/seastore/test_transaction_manager.cc b/src/test/crimson/seastore/test_transaction_manager.cc
new file mode 100644
index 000000000..1148884a0
--- /dev/null
+++ b/src/test/crimson/seastore/test_transaction_manager.cc
@@ -0,0 +1,1995 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <random>
+
+#include <boost/iterator/counting_iterator.hpp>
+
+#include "test/crimson/gtest_seastar.h"
+#include "test/crimson/seastore/transaction_manager_test_state.h"
+
+#include "crimson/os/seastore/cache.h"
+#include "crimson/os/seastore/transaction_manager.h"
+#include "crimson/os/seastore/segment_manager/ephemeral.h"
+#include "crimson/os/seastore/segment_manager.h"
+
+#include "test/crimson/seastore/test_block.h"
+
+using namespace crimson;
+using namespace crimson::os;
+using namespace crimson::os::seastore;
+
+namespace {
+ [[maybe_unused]] seastar::logger& logger() {
+ return crimson::get_logger(ceph_subsys_test);
+ }
+}
+
+struct test_extent_record_t {
+ test_extent_desc_t desc;
+ unsigned refcount = 0;
+ test_extent_record_t() = default;
+ test_extent_record_t(
+ const test_extent_desc_t &desc,
+ unsigned refcount) : desc(desc), refcount(refcount) {}
+
+ void update(const test_extent_desc_t &to) {
+ desc = to;
+ }
+
+ bool operator==(const test_extent_desc_t &rhs) const {
+ return desc == rhs;
+ }
+ bool operator!=(const test_extent_desc_t &rhs) const {
+ return desc != rhs;
+ }
+};
+
+template<>
+struct fmt::formatter<test_extent_record_t> : fmt::formatter<std::string_view> {
+ template <typename FormatContext>
+ auto format(const test_extent_record_t& r, FormatContext& ctx) const {
+ return fmt::format_to(ctx.out(), "test_extent_record_t({}, refcount={})",
+ r.desc, r.refcount);
+ }
+};
+
+struct transaction_manager_test_t :
+ public seastar_test_suite_t,
+ TMTestState {
+
+ std::random_device rd;
+ std::mt19937 gen;
+
+ transaction_manager_test_t(std::size_t num_main_devices, std::size_t num_cold_devices)
+ : TMTestState(num_main_devices, num_cold_devices), gen(rd()) {
+ }
+
+ laddr_t get_random_laddr(size_t block_size, laddr_t limit) {
+ return block_size *
+ std::uniform_int_distribution<>(0, (limit / block_size) - 1)(gen);
+ }
+
+ char get_random_contents() {
+ return static_cast<char>(std::uniform_int_distribution<>(0, 255)(gen));
+ }
+
+ seastar::future<> set_up_fut() final {
+ return tm_setup();
+ }
+
+ seastar::future<> tear_down_fut() final {
+ return tm_teardown();
+ }
+
+ struct test_extents_t : std::map<laddr_t, test_extent_record_t> {
+ using delta_t = std::map<laddr_t, std::optional<test_extent_record_t>>;
+ std::map<laddr_t, uint64_t> laddr_write_seq;
+
+ 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;
+ }
+
+ 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,
+ delta_t &delta) const {
+ delta_overlay_t overlay(*this, delta);
+ auto iter = overlay.lower_bound(hint);
+ laddr_t last = hint;
+ while (true) {
+ if (iter == overlay.end() || iter->first > addr) {
+ EXPECT_EQ(addr, last);
+ break;
+ }
+ EXPECT_FALSE(iter->first - last > len);
+ last = iter->first + iter->second.desc.len;
+ ++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:
+ 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, const uint64_t write_seq = 0) {
+ for (const auto &i : delta) {
+ if (i.second) {
+ if (laddr_write_seq.find(i.first) == laddr_write_seq.end() ||
+ laddr_write_seq[i.first] <= write_seq) {
+ (*this)[i.first] = *i.second;
+ laddr_write_seq[i.first] = write_seq;
+ }
+ } else {
+ erase(i.first);
+ }
+ }
+ }
+
+ } test_mappings;
+
+ struct test_transaction_t {
+ TransactionRef t;
+ test_extents_t::delta_t mapping_delta;
+ };
+
+ test_transaction_t create_transaction() {
+ return { create_mutate_transaction(), {} };
+ }
+
+ test_transaction_t create_read_test_transaction() {
+ return {create_read_transaction(), {} };
+ }
+
+ test_transaction_t create_weak_test_transaction() {
+ return { create_weak_transaction(), {} };
+ }
+
+ TestBlockRef alloc_extent(
+ test_transaction_t &t,
+ laddr_t hint,
+ extent_len_t len,
+ char contents) {
+ 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(test_mappings.contains(extent->get_laddr(), t.mapping_delta));
+ EXPECT_EQ(len, extent->get_length());
+ test_mappings.alloced(hint, *extent, t.mapping_delta);
+ return extent;
+ }
+
+ TestBlockRef alloc_extent(
+ test_transaction_t &t,
+ laddr_t hint,
+ extent_len_t len) {
+ return alloc_extent(
+ t,
+ hint,
+ len,
+ get_random_contents());
+ }
+
+ bool check_usage() {
+ return epm->check_usage();
+ }
+
+ void replay() {
+ EXPECT_TRUE(check_usage());
+ restart();
+ }
+
+ void check() {
+ check_mappings();
+ check_usage();
+ }
+
+ void check_mappings() {
+ auto t = create_weak_test_transaction();
+ check_mappings(t);
+ }
+
+ TestBlockRef 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);
+
+ 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) {
+ ceph_assert(test_mappings.contains(addr, t.mapping_delta));
+
+ 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);
+ }).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;
+ }
+
+ 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;
+ }
+
+ TestBlockRef try_read_pin(
+ test_transaction_t &t,
+ LBAMappingRef &&pin) {
+ using ertr = with_trans_ertr<TransactionManager::base_iertr>;
+ using ret = ertr::future<TestBlockRef>;
+ auto addr = pin->get_key();
+ auto ext = with_trans_intr(*(t.t), [&](auto& trans) {
+ return tm->read_pin<TestBlock>(trans, std::move(pin));
+ }).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{
+ "read_pin got invalid error"
+ }
+ ).get0();
+ if (ext) {
+ EXPECT_EQ(addr, ext->get_laddr());
+ }
+ if (t.t->is_conflicted()) {
+ return nullptr;
+ }
+ return ext;
+ }
+
+ test_block_mutator_t mutator;
+ TestBlockRef mutate_extent(
+ test_transaction_t &t,
+ TestBlockRef ref) {
+ 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);
+
+ 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;
+ }
+
+ LBAMappingRef get_pin(
+ test_transaction_t &t,
+ laddr_t offset) {
+ ceph_assert(test_mappings.contains(offset, t.mapping_delta));
+ auto pin = with_trans_intr(*(t.t), [&](auto& trans) {
+ return tm->get_pin(trans, offset);
+ }).unsafe_get0();
+ EXPECT_EQ(offset, pin->get_key());
+ return pin;
+ }
+
+ LBAMappingRef try_get_pin(
+ test_transaction_t &t,
+ laddr_t offset) {
+ ceph_assert(test_mappings.contains(offset, t.mapping_delta));
+ using ertr = with_trans_ertr<TransactionManager::get_pin_iertr>;
+ using ret = ertr::future<LBAMappingRef>;
+ auto pin = with_trans_intr(*(t.t), [&](auto& trans) {
+ return tm->get_pin(trans, offset);
+ }).safe_then([](auto pin) -> ret {
+ return ertr::make_ready_future<LBAMappingRef>(std::move(pin));
+ }).handle_error(
+ [](const crimson::ct_error::eagain &e) {
+ return seastar::make_ready_future<LBAMappingRef>();
+ },
+ crimson::ct_error::assert_all{
+ "get_extent got invalid error"
+ }
+ ).get0();
+ if (pin) {
+ EXPECT_EQ(offset, pin->get_key());
+ }
+ return pin;
+ }
+
+ void inc_ref(test_transaction_t &t, laddr_t 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->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(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) {
+ 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());
+ }
+ 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)with_trans_intr(
+ *t.t,
+ [=, this](auto &t) {
+ return lba_manager->check_child_trackers(t);
+ }).unsafe_get0();
+ }
+
+ bool try_submit_transaction(test_transaction_t t) {
+ using ertr = with_trans_ertr<TransactionManager::submit_transaction_iertr>;
+ using ret = ertr::future<bool>;
+ uint64_t write_seq = 0;
+ bool success = submit_transaction_fut_with_seq(*t.t
+ ).safe_then([&write_seq](auto seq) -> ret {
+ write_seq = seq;
+ 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 epm->run_background_work_until_halt(
+ ).then([ret] { return ret; });
+ }).get0();
+
+ if (success) {
+ test_mappings.consume(t.mapping_delta, write_seq);
+ }
+
+ 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, const int num, bool run_clean = true) {
+ return repeat_eagain([this, size, num] {
+ return seastar::do_with(
+ create_transaction(),
+ [this, size, num](auto &t) {
+ return with_trans_intr(
+ *t.t,
+ [&t, this, size, num](auto &) {
+ return trans_intr::do_for_each(
+ boost::make_counting_iterator(0),
+ boost::make_counting_iterator(num),
+ [&t, this, size](auto) {
+ return tm->alloc_extent<TestBlock>(
+ *(t.t), L_ADDR_MIN, size
+ ).si_then([&t, this, size](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, run_clean]() {
+ if (run_clean) {
+ return epm->run_background_work_until_halt();
+ } else {
+ return epm->background_process.trimmer->trim();
+ }
+ }).handle_error(
+ crimson::ct_error::assert_all{
+ "Invalid error in SeaStore::list_collections"
+ }
+ );
+ }
+
+ void test_parallel_extent_read() {
+ constexpr size_t TOTAL = 4<<20;
+ constexpr size_t BSIZE = 4<<10;
+ constexpr size_t BLOCKS = TOTAL / BSIZE;
+ run_async([this] {
+ for (unsigned i = 0; i < BLOCKS; ++i) {
+ auto t = create_transaction();
+ auto extent = alloc_extent(
+ t,
+ i * BSIZE,
+ BSIZE);
+ ASSERT_EQ(i * BSIZE, extent->get_laddr());
+ submit_transaction(std::move(t));
+ }
+
+ seastar::do_with(
+ create_read_test_transaction(),
+ [this](auto &t) {
+ return with_trans_intr(*(t.t), [this](auto &t) {
+ return trans_intr::parallel_for_each(
+ boost::make_counting_iterator(0lu),
+ boost::make_counting_iterator(BLOCKS),
+ [this, &t](auto i) {
+ return tm->read_extent<TestBlock>(t, i * BSIZE, BSIZE
+ ).si_then([](auto) {
+ return seastar::now();
+ });
+ });
+ });
+ }).unsafe_get0();
+ });
+ }
+
+ void test_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] {
+ std::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;
+ }
+ }
+ });
+
+ 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().info("random_writes_concurrent: checking");
+ check();
+ logger().info(
+ "random_writes_concurrent: {} suceeded, {} failed",
+ writes,
+ failures
+ );
+ });
+ }
+
+ void test_evict() {
+ // only support segmented backend currently
+ ASSERT_EQ(epm->get_main_backend_type(), backend_type_t::SEGMENTED);
+ ASSERT_TRUE(epm->background_process.has_cold_tier());
+ constexpr size_t device_size =
+ segment_manager::DEFAULT_TEST_EPHEMERAL.size;
+ constexpr size_t block_size =
+ segment_manager::DEFAULT_TEST_EPHEMERAL.block_size;
+ constexpr size_t segment_size =
+ segment_manager::DEFAULT_TEST_EPHEMERAL.segment_size;
+ ASSERT_GE(segment_size, block_size * 20);
+
+ run_async([this] {
+ // indicates there is no available segments to reclaim
+ double stop_ratio = (double)segment_size / (double)device_size / 2;
+ // 1 segment
+ double default_ratio = stop_ratio * 2;
+ // 1.25 segment
+ double fast_ratio = stop_ratio * 2.5;
+
+ epm->background_process
+ .eviction_state
+ .init(stop_ratio, default_ratio, fast_ratio);
+
+ // these variables are described in
+ // EPM::BackgroundProcess::eviction_state_t::maybe_update_eviction_mode
+ size_t ratio_A_size = segment_size / 2 - block_size * 10;
+ size_t ratio_B_size = segment_size / 2 + block_size * 10;
+ size_t ratio_C_size = segment_size + block_size;
+ size_t ratio_D_size = segment_size * 1.25 + block_size;
+
+ auto run_until = [this](size_t size) -> seastar::future<> {
+ return seastar::repeat([this, size] {
+ size_t current_size = epm->background_process
+ .main_cleaner->get_stat().data_stored;
+ if (current_size >= size) {
+ return seastar::futurize_invoke([] {
+ return seastar::stop_iteration::yes;
+ });
+ } else {
+ int num = (size - current_size) / block_size;
+ return seastar::do_for_each(
+ boost::make_counting_iterator(0),
+ boost::make_counting_iterator(num),
+ [this](auto) {
+ // don't start background process to test the behavior
+ // of generation changes during alloc new extents
+ return allocate_sequentially(block_size, 1, false);
+ }).then([] {
+ return seastar::stop_iteration::no;
+ });
+ }
+ });
+ };
+
+ std::vector<extent_types_t> all_extent_types{
+ extent_types_t::ROOT,
+ extent_types_t::LADDR_INTERNAL,
+ extent_types_t::LADDR_LEAF,
+ extent_types_t::OMAP_INNER,
+ extent_types_t::OMAP_LEAF,
+ extent_types_t::ONODE_BLOCK_STAGED,
+ extent_types_t::COLL_BLOCK,
+ extent_types_t::OBJECT_DATA_BLOCK,
+ extent_types_t::RETIRED_PLACEHOLDER,
+ extent_types_t::ALLOC_INFO,
+ extent_types_t::JOURNAL_TAIL,
+ extent_types_t::TEST_BLOCK,
+ extent_types_t::TEST_BLOCK_PHYSICAL,
+ extent_types_t::BACKREF_INTERNAL,
+ extent_types_t::BACKREF_LEAF
+ };
+
+ std::vector<rewrite_gen_t> all_generations;
+ for (auto i = INIT_GENERATION; i < REWRITE_GENERATIONS; i++) {
+ all_generations.push_back(i);
+ }
+
+ // input target-generation -> expected generation after the adjustment
+ using generation_mapping_t = std::map<rewrite_gen_t, rewrite_gen_t>;
+ std::map<extent_types_t, generation_mapping_t> expected_generations;
+
+ // this loop should be consistent with EPM::adjust_generation
+ for (auto t : all_extent_types) {
+ expected_generations[t] = {};
+ if (!is_logical_type(t)) {
+ for (auto gen : all_generations) {
+ expected_generations[t][gen] = INLINE_GENERATION;
+ }
+ } else {
+ if (get_extent_category(t) == data_category_t::METADATA) {
+ expected_generations[t][INIT_GENERATION] = INLINE_GENERATION;
+ } else {
+ expected_generations[t][INIT_GENERATION] = OOL_GENERATION;
+ }
+
+ for (auto i = INIT_GENERATION + 1; i < REWRITE_GENERATIONS; i++) {
+ expected_generations[t][i] = i;
+ }
+ }
+ }
+
+ auto update_data_gen_mapping = [&](std::function<rewrite_gen_t(rewrite_gen_t)> func) {
+ for (auto t : all_extent_types) {
+ if (!is_logical_type(t)) {
+ continue;
+ }
+ for (auto i = INIT_GENERATION + 1; i < REWRITE_GENERATIONS; i++) {
+ expected_generations[t][i] = func(i);
+ }
+ }
+ // since background process didn't start in allocate_sequentially
+ // we update eviction mode manually.
+ epm->background_process.maybe_update_eviction_mode();
+ };
+
+ auto test_gen = [&](const char *caller) {
+ for (auto t : all_extent_types) {
+ for (auto gen : all_generations) {
+ auto epm_gen = epm->adjust_generation(
+ get_extent_category(t),
+ t,
+ placement_hint_t::HOT,
+ gen);
+ if (expected_generations[t][gen] != epm_gen) {
+ logger().error("caller: {}, extent type: {}, input generation: {}, "
+ "expected generation : {}, adjust result from EPM: {}",
+ caller, t, gen, expected_generations[t][gen], epm_gen);
+ }
+ EXPECT_EQ(expected_generations[t][gen], epm_gen);
+ }
+ }
+ };
+
+ // verify that no data should go to the cold tier
+ update_data_gen_mapping([](rewrite_gen_t gen) -> rewrite_gen_t {
+ if (gen == MIN_COLD_GENERATION) {
+ return MIN_COLD_GENERATION - 1;
+ } else {
+ return gen;
+ }
+ });
+ test_gen("init");
+
+ run_until(ratio_A_size).get();
+ EXPECT_TRUE(epm->background_process.eviction_state.is_stop_mode());
+ test_gen("exceed ratio A");
+ epm->run_background_work_until_halt().get();
+
+ run_until(ratio_B_size).get();
+ EXPECT_TRUE(epm->background_process.eviction_state.is_stop_mode());
+ test_gen("exceed ratio B");
+ epm->run_background_work_until_halt().get();
+
+ // verify that data may go to the cold tier
+ run_until(ratio_C_size).get();
+ update_data_gen_mapping([](rewrite_gen_t gen) { return gen; });
+ EXPECT_TRUE(epm->background_process.eviction_state.is_default_mode());
+ test_gen("exceed ratio C");
+ epm->run_background_work_until_halt().get();
+
+ // verify that data must go to the cold tier
+ run_until(ratio_D_size).get();
+ update_data_gen_mapping([](rewrite_gen_t gen) {
+ if (gen >= MIN_REWRITE_GENERATION && gen < MIN_COLD_GENERATION) {
+ return MIN_COLD_GENERATION;
+ } else {
+ return gen;
+ }
+ });
+ EXPECT_TRUE(epm->background_process.eviction_state.is_fast_mode());
+ test_gen("exceed ratio D");
+
+ auto main_size = epm->background_process.main_cleaner->get_stat().data_stored;
+ auto cold_size = epm->background_process.cold_cleaner->get_stat().data_stored;
+ EXPECT_EQ(cold_size, 0);
+ epm->run_background_work_until_halt().get();
+ auto new_main_size = epm->background_process.main_cleaner->get_stat().data_stored;
+ auto new_cold_size = epm->background_process.cold_cleaner->get_stat().data_stored;
+ EXPECT_GE(main_size, new_main_size);
+ EXPECT_NE(new_cold_size, 0);
+
+ update_data_gen_mapping([](rewrite_gen_t gen) { return gen; });
+ EXPECT_TRUE(epm->background_process.eviction_state.is_default_mode());
+ test_gen("finish evict");
+ });
+ }
+
+ using remap_entry = TransactionManager::remap_entry;
+ LBAMappingRef remap_pin(
+ test_transaction_t &t,
+ LBAMappingRef &&opin,
+ extent_len_t new_offset,
+ extent_len_t new_len) {
+ if (t.t->is_conflicted()) {
+ return nullptr;
+ }
+ auto o_laddr = opin->get_key();
+ auto pin = with_trans_intr(*(t.t), [&](auto& trans) {
+ return tm->remap_pin<TestBlock>(
+ trans, std::move(opin), std::array{
+ remap_entry(new_offset, new_len)}
+ ).si_then([](auto ret) {
+ return std::move(ret[0]);
+ });
+ }).handle_error(crimson::ct_error::eagain::handle([] {
+ LBAMappingRef t = nullptr;
+ return t;
+ }), crimson::ct_error::pass_further_all{}).unsafe_get0();
+ if (t.t->is_conflicted()) {
+ return nullptr;
+ }
+ test_mappings.dec_ref(o_laddr, t.mapping_delta);
+ EXPECT_FALSE(test_mappings.contains(o_laddr, t.mapping_delta));
+ EXPECT_TRUE(pin);
+ EXPECT_EQ(pin->get_length(), new_len);
+ EXPECT_EQ(pin->get_key(), o_laddr + new_offset);
+
+ auto extent = try_read_pin(t, pin->duplicate());
+ if (extent) {
+ test_mappings.alloced(pin->get_key(), *extent, t.mapping_delta);
+ EXPECT_TRUE(extent->is_exist_clean());
+ } else {
+ ceph_assert(t.t->is_conflicted());
+ return nullptr;
+ }
+ return pin;
+ }
+
+ using _overwrite_pin_iertr = TransactionManager::get_pin_iertr;
+ using _overwrite_pin_ret = _overwrite_pin_iertr::future<
+ std::tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>>;
+ _overwrite_pin_ret _overwrite_pin(
+ Transaction &t,
+ LBAMappingRef &&opin,
+ extent_len_t new_offset,
+ extent_len_t new_len,
+ ceph::bufferlist &bl) {
+ auto o_laddr = opin->get_key();
+ auto o_len = opin->get_length();
+ if (new_offset != 0 && o_len != new_offset + new_len) {
+ return tm->remap_pin<TestBlock, 2>(
+ t,
+ std::move(opin),
+ std::array{
+ remap_entry(
+ 0,
+ new_offset),
+ remap_entry(
+ new_offset + new_len,
+ o_len - new_offset - new_len)
+ }
+ ).si_then([this, new_offset, new_len, o_laddr, &t, &bl](auto ret) {
+ return tm->alloc_extent<TestBlock>(t, o_laddr + new_offset, new_len
+ ).si_then([this, ret = std::move(ret), new_len,
+ new_offset, o_laddr, &t, &bl](auto ext) mutable {
+ ceph_assert(ret.size() == 2);
+ auto iter = bl.cbegin();
+ iter.copy(new_len, ext->get_bptr().c_str());
+ auto r_laddr = o_laddr + new_offset + new_len;
+ // old pins expired after alloc new extent, need to get it.
+ return tm->get_pin(t, o_laddr
+ ).si_then([this, &t, ext = std::move(ext), r_laddr](auto lpin) mutable {
+ return tm->get_pin(t, r_laddr
+ ).si_then([lpin = std::move(lpin), ext = std::move(ext)]
+ (auto rpin) mutable {
+ return _overwrite_pin_iertr::make_ready_future<
+ std::tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>>(
+ std::make_tuple(
+ std::move(lpin), std::move(ext), std::move(rpin)));
+ });
+ });
+ });
+ });
+ } else if (new_offset == 0 && o_len != new_offset + new_len) {
+ return tm->remap_pin<TestBlock, 1>(
+ t,
+ std::move(opin),
+ std::array{
+ remap_entry(
+ new_offset + new_len,
+ o_len - new_offset - new_len)
+ }
+ ).si_then([this, new_offset, new_len, o_laddr, &t, &bl](auto ret) {
+ return tm->alloc_extent<TestBlock>(t, o_laddr + new_offset, new_len
+ ).si_then([this, ret = std::move(ret), new_offset, new_len,
+ o_laddr, &t, &bl](auto ext) mutable {
+ ceph_assert(ret.size() == 1);
+ auto iter = bl.cbegin();
+ iter.copy(new_len, ext->get_bptr().c_str());
+ auto r_laddr = o_laddr + new_offset + new_len;
+ return tm->get_pin(t, r_laddr
+ ).si_then([ext = std::move(ext)](auto rpin) mutable {
+ return _overwrite_pin_iertr::make_ready_future<
+ std::tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>>(
+ std::make_tuple(
+ nullptr, std::move(ext), std::move(rpin)));
+ });
+ });
+ });
+ } else if (new_offset != 0 && o_len == new_offset + new_len) {
+ return tm->remap_pin<TestBlock, 1>(
+ t,
+ std::move(opin),
+ std::array{
+ remap_entry(
+ 0,
+ new_offset)
+ }
+ ).si_then([this, new_offset, new_len, o_laddr, &t, &bl](auto ret) {
+ return tm->alloc_extent<TestBlock>(t, o_laddr + new_offset, new_len
+ ).si_then([this, ret = std::move(ret), new_len, o_laddr, &t, &bl]
+ (auto ext) mutable {
+ ceph_assert(ret.size() == 1);
+ auto iter = bl.cbegin();
+ iter.copy(new_len, ext->get_bptr().c_str());
+ return tm->get_pin(t, o_laddr
+ ).si_then([ext = std::move(ext)](auto lpin) mutable {
+ return _overwrite_pin_iertr::make_ready_future<
+ std::tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>>(
+ std::make_tuple(
+ std::move(lpin), std::move(ext), nullptr));
+ });
+ });
+ });
+ } else {
+ ceph_abort("impossible");
+ return _overwrite_pin_iertr::make_ready_future<
+ std::tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>>(
+ std::make_tuple(nullptr, nullptr, nullptr));
+ }
+ }
+
+ using overwrite_pin_ret = std::tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>;
+ overwrite_pin_ret overwrite_pin(
+ test_transaction_t &t,
+ LBAMappingRef &&opin,
+ extent_len_t new_offset,
+ extent_len_t new_len,
+ ceph::bufferlist &bl) {
+ if (t.t->is_conflicted()) {
+ return std::make_tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>(
+ nullptr, nullptr, nullptr);
+ }
+ auto o_laddr = opin->get_key();
+ auto o_paddr = opin->get_val();
+ auto o_len = opin->get_length();
+ auto res = with_trans_intr(*(t.t), [&](auto& trans) {
+ return _overwrite_pin(
+ trans, std::move(opin), new_offset, new_len, bl);
+ }).handle_error(crimson::ct_error::eagain::handle([] {
+ return std::make_tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>(
+ nullptr, nullptr, nullptr);
+ }), crimson::ct_error::pass_further_all{}).unsafe_get0();
+ if (t.t->is_conflicted()) {
+ return std::make_tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>(
+ nullptr, nullptr, nullptr);
+ }
+ test_mappings.dec_ref(o_laddr, t.mapping_delta);
+ EXPECT_FALSE(test_mappings.contains(o_laddr, t.mapping_delta));
+ auto &[lpin, ext, rpin] = res;
+
+ EXPECT_TRUE(ext);
+ EXPECT_TRUE(lpin || rpin);
+ EXPECT_TRUE(o_len > ext->get_length());
+ if (lpin) {
+ EXPECT_EQ(lpin->get_key(), o_laddr);
+ EXPECT_EQ(lpin->get_val(), o_paddr);
+ EXPECT_EQ(lpin->get_length(), new_offset);
+ auto lext = try_read_pin(t, lpin->duplicate());
+ if (lext) {
+ test_mappings.alloced(lpin->get_key(), *lext, t.mapping_delta);
+ EXPECT_TRUE(lext->is_exist_clean());
+ } else {
+ ceph_assert(t.t->is_conflicted());
+ return std::make_tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>(
+ nullptr, nullptr, nullptr);
+ }
+ }
+ EXPECT_EQ(ext->get_laddr(), o_laddr + new_offset);
+ EXPECT_EQ(ext->get_length(), new_len);
+ test_mappings.alloced(ext->get_laddr(), *ext, t.mapping_delta);
+ if (rpin) {
+ EXPECT_EQ(rpin->get_key(), o_laddr + new_offset + new_len);
+ EXPECT_EQ(rpin->get_val(), o_paddr.add_offset(new_offset)
+ .add_offset(new_len));
+ EXPECT_EQ(rpin->get_length(), o_len - new_offset - new_len);
+ auto rext = try_read_pin(t, rpin->duplicate());
+ if (rext) {
+ test_mappings.alloced(rpin->get_key(), *rext, t.mapping_delta);
+ EXPECT_TRUE(rext->is_exist_clean());
+ } else {
+ ceph_assert(t.t->is_conflicted());
+ return std::make_tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>(
+ nullptr, nullptr, nullptr);
+ }
+ }
+ return std::make_tuple<LBAMappingRef, TestBlockRef, LBAMappingRef>(
+ std::move(lpin), std::move(ext), std::move(rpin));
+ }
+
+ void test_remap_pin() {
+ run_async([this] {
+ constexpr size_t l_offset = 32 << 10;
+ constexpr size_t l_len = 32 << 10;
+ constexpr size_t r_offset = 64 << 10;
+ constexpr size_t r_len = 32 << 10;
+ {
+ auto t = create_transaction();
+ auto lext = alloc_extent(t, l_offset, l_len);
+ lext->set_contents('l', 0, 16 << 10);
+ auto rext = alloc_extent(t, r_offset, r_len);
+ rext->set_contents('r', 16 << 10, 16 << 10);
+ submit_transaction(std::move(t));
+ }
+ {
+ auto t = create_transaction();
+ auto lpin = get_pin(t, l_offset);
+ auto rpin = get_pin(t, r_offset);
+ //split left
+ auto pin1 = remap_pin(t, std::move(lpin), 0, 16 << 10);
+ ASSERT_TRUE(pin1);
+ auto pin2 = remap_pin(t, std::move(pin1), 0, 8 << 10);
+ ASSERT_TRUE(pin2);
+ auto pin3 = remap_pin(t, std::move(pin2), 0, 4 << 10);
+ ASSERT_TRUE(pin3);
+ auto lext = get_extent(t, pin3->get_key(), pin3->get_length());
+ EXPECT_EQ('l', lext->get_bptr().c_str()[0]);
+ auto mlext = mutate_extent(t, lext);
+ ASSERT_TRUE(mlext->is_exist_mutation_pending());
+ ASSERT_TRUE(mlext.get() == lext.get());
+
+ //split right
+ auto pin4 = remap_pin(t, std::move(rpin), 16 << 10, 16 << 10);
+ ASSERT_TRUE(pin4);
+ auto pin5 = remap_pin(t, std::move(pin4), 8 << 10, 8 << 10);
+ ASSERT_TRUE(pin5);
+ auto pin6 = remap_pin(t, std::move(pin5), 4 << 10, 4 << 10);
+ ASSERT_TRUE(pin6);
+ auto rext = get_extent(t, pin6->get_key(), pin6->get_length());
+ EXPECT_EQ('r', rext->get_bptr().c_str()[0]);
+ auto mrext = mutate_extent(t, rext);
+ ASSERT_TRUE(mrext->is_exist_mutation_pending());
+ ASSERT_TRUE(mrext.get() == rext.get());
+
+ submit_transaction(std::move(t));
+ check();
+ }
+ replay();
+ check();
+ });
+ }
+
+ void test_overwrite_pin() {
+ run_async([this] {
+ constexpr size_t m_offset = 8 << 10;
+ constexpr size_t m_len = 56 << 10;
+ constexpr size_t l_offset = 64 << 10;
+ constexpr size_t l_len = 64 << 10;
+ constexpr size_t r_offset = 128 << 10;
+ constexpr size_t r_len = 64 << 10;
+ {
+ auto t = create_transaction();
+ auto m_ext = alloc_extent(t, m_offset, m_len);
+ m_ext->set_contents('a', 0 << 10, 8 << 10);
+ m_ext->set_contents('b', 16 << 10, 4 << 10);
+ m_ext->set_contents('c', 36 << 10, 4 << 10);
+ m_ext->set_contents('d', 52 << 10, 4 << 10);
+
+ auto l_ext = alloc_extent(t, l_offset, l_len);
+ auto r_ext = alloc_extent(t, r_offset, r_len);
+ submit_transaction(std::move(t));
+ }
+ {
+ auto t = create_transaction();
+ auto mpin = get_pin(t, m_offset);
+ auto lpin = get_pin(t, l_offset);
+ auto rpin = get_pin(t, r_offset);
+
+ bufferlist mbl1, mbl2, mbl3;
+ mbl1.append(ceph::bufferptr(ceph::buffer::create(8 << 10, 0)));
+ mbl2.append(ceph::bufferptr(ceph::buffer::create(16 << 10, 0)));
+ mbl3.append(ceph::bufferptr(ceph::buffer::create(12 << 10, 0)));
+ auto [mlp1, mext1, mrp1] = overwrite_pin(
+ t, std::move(mpin), 8 << 10 , 8 << 10, mbl1);
+ auto [mlp2, mext2, mrp2] = overwrite_pin(
+ t, std::move(mrp1), 4 << 10 , 16 << 10, mbl2);
+ auto [mlpin3, me3, mrpin3] = overwrite_pin(
+ t, std::move(mrp2), 4 << 10 , 12 << 10, mbl3);
+ auto mlext1 = get_extent(t, mlp1->get_key(), mlp1->get_length());
+ auto mlext2 = get_extent(t, mlp2->get_key(), mlp2->get_length());
+ auto mlext3 = get_extent(t, mlpin3->get_key(), mlpin3->get_length());
+ auto mrext3 = get_extent(t, mrpin3->get_key(), mrpin3->get_length());
+ EXPECT_EQ('a', mlext1->get_bptr().c_str()[0]);
+ EXPECT_EQ('b', mlext2->get_bptr().c_str()[0]);
+ EXPECT_EQ('c', mlext3->get_bptr().c_str()[0]);
+ EXPECT_EQ('d', mrext3->get_bptr().c_str()[0]);
+ auto mutate_mlext1 = mutate_extent(t, mlext1);
+ auto mutate_mlext2 = mutate_extent(t, mlext2);
+ auto mutate_mlext3 = mutate_extent(t, mlext3);
+ auto mutate_mrext3 = mutate_extent(t, mrext3);
+ ASSERT_TRUE(mutate_mlext1->is_exist_mutation_pending());
+ ASSERT_TRUE(mutate_mlext2->is_exist_mutation_pending());
+ ASSERT_TRUE(mutate_mlext3->is_exist_mutation_pending());
+ ASSERT_TRUE(mutate_mrext3->is_exist_mutation_pending());
+ ASSERT_TRUE(mutate_mlext1.get() == mlext1.get());
+ ASSERT_TRUE(mutate_mlext2.get() == mlext2.get());
+ ASSERT_TRUE(mutate_mlext3.get() == mlext3.get());
+ ASSERT_TRUE(mutate_mrext3.get() == mrext3.get());
+
+ bufferlist lbl1, rbl1;
+ lbl1.append(ceph::bufferptr(ceph::buffer::create(32 << 10, 0)));
+ auto [llp1, lext1, lrp1] = overwrite_pin(
+ t, std::move(lpin), 0 , 32 << 10, lbl1);
+ EXPECT_FALSE(llp1);
+ EXPECT_TRUE(lrp1);
+ EXPECT_TRUE(lext1);
+
+ rbl1.append(ceph::bufferptr(ceph::buffer::create(32 << 10, 0)));
+ auto [rlp1, rext1, rrp1] = overwrite_pin(
+ t, std::move(rpin), 32 << 10 , 32 << 10, rbl1);
+ EXPECT_TRUE(rlp1);
+ EXPECT_TRUE(rext1);
+ EXPECT_FALSE(rrp1);
+
+ submit_transaction(std::move(t));
+ check();
+ }
+ replay();
+ check();
+ });
+ }
+
+ void test_remap_pin_concurrent() {
+ run_async([this] {
+ constexpr unsigned REMAP_NUM = 32;
+ constexpr size_t offset = 0;
+ constexpr size_t length = 256 << 10;
+ {
+ auto t = create_transaction();
+ auto extent = alloc_extent(t, offset, length);
+ ASSERT_EQ(length, extent->get_length());
+ submit_transaction(std::move(t));
+ }
+ int success = 0;
+ int early_exit = 0;
+ int conflicted = 0;
+
+ seastar::parallel_for_each(
+ boost::make_counting_iterator(0u),
+ boost::make_counting_iterator(REMAP_NUM),
+ [&](auto) {
+ return seastar::async([&] {
+ uint32_t pieces = std::uniform_int_distribution<>(6, 31)(gen);
+ std::set<uint32_t> split_points;
+ for (uint32_t i = 0; i < pieces; i++) {
+ auto p = std::uniform_int_distribution<>(1, 256)(gen);
+ split_points.insert(p - p % 4);
+ }
+
+ auto t = create_transaction();
+ auto pin0 = try_get_pin(t, offset);
+ if (!pin0 || pin0->get_length() != length) {
+ early_exit++;
+ return;
+ }
+
+ auto last_pin = pin0->duplicate();
+ ASSERT_TRUE(!split_points.empty());
+ for (auto off : split_points) {
+ if (off == 0 || off >= 255) {
+ continue;
+ }
+ auto new_off = (off << 10) - last_pin->get_key();
+ auto new_len = last_pin->get_length() - new_off;
+ //always remap right extent at new split_point
+ auto pin = remap_pin(t, std::move(last_pin), new_off, new_len);
+ if (!pin) {
+ conflicted++;
+ return;
+ }
+ last_pin = pin->duplicate();
+ }
+ auto last_ext = try_get_extent(t, last_pin->get_key());
+ if (last_ext) {
+ auto last_ext1 = mutate_extent(t, last_ext);
+ ASSERT_TRUE(last_ext1->is_exist_mutation_pending());
+ } else {
+ conflicted++;
+ return;
+ }
+
+ if (try_submit_transaction(std::move(t))) {
+ success++;
+ logger().info("transaction {} submit the transction",
+ static_cast<void*>(t.t.get()));
+ } else {
+ conflicted++;
+ }
+ });
+ }).handle_exception([](std::exception_ptr e) {
+ logger().info("{}", e);
+ }).get0();
+ logger().info("test_remap_pin_concurrent: "
+ "early_exit {} conflicted {} success {}",
+ early_exit, conflicted, success);
+ ASSERT_TRUE(success == 1);
+ ASSERT_EQ(success + conflicted + early_exit, REMAP_NUM);
+ replay();
+ check();
+ });
+ }
+
+ void test_overwrite_pin_concurrent() {
+ run_async([this] {
+ constexpr unsigned REMAP_NUM = 32;
+ constexpr size_t offset = 0;
+ constexpr size_t length = 256 << 10;
+ {
+ auto t = create_transaction();
+ auto extent = alloc_extent(t, offset, length);
+ ASSERT_EQ(length, extent->get_length());
+ submit_transaction(std::move(t));
+ }
+ int success = 0;
+ int early_exit = 0;
+ int conflicted = 0;
+
+ seastar::parallel_for_each(
+ boost::make_counting_iterator(0u),
+ boost::make_counting_iterator(REMAP_NUM),
+ [&](auto) {
+ return seastar::async([&] {
+ uint32_t pieces = std::uniform_int_distribution<>(6, 31)(gen);
+ if (pieces % 2 == 1) {
+ pieces++;
+ }
+ std::list<uint32_t> split_points;
+ for (uint32_t i = 0; i < pieces; i++) {
+ auto p = std::uniform_int_distribution<>(1, 120)(gen);
+ split_points.push_back(p - p % 4);
+ }
+ split_points.sort();
+
+ auto t = create_transaction();
+ auto pin0 = try_get_pin(t, offset);
+ if (!pin0 || pin0->get_length() != length) {
+ early_exit++;
+ return;
+ }
+
+ auto empty_transaction = true;
+ auto last_rpin = pin0->duplicate();
+ ASSERT_TRUE(!split_points.empty());
+ while(!split_points.empty()) {
+ // new overwrite area: start_off ~ end_off
+ auto start_off = split_points.front();
+ split_points.pop_front();
+ auto end_off = split_points.front();
+ split_points.pop_front();
+ ASSERT_TRUE(start_off <= end_off);
+ if (((end_off << 10) == pin0->get_key() + pin0->get_length())
+ || (start_off == end_off)) {
+ if (split_points.empty() && empty_transaction) {
+ early_exit++;
+ return;
+ }
+ continue;
+ }
+ empty_transaction = false;
+ auto new_off = (start_off << 10) - last_rpin->get_key();
+ auto new_len = (end_off - start_off) << 10;
+ bufferlist bl;
+ bl.append(ceph::bufferptr(ceph::buffer::create(new_len, 0)));
+ auto [lpin, ext, rpin] = overwrite_pin(
+ t, last_rpin->duplicate(), new_off, new_len, bl);
+ if (!ext) {
+ conflicted++;
+ return;
+ }
+ // lpin is nullptr might not cause by confliction,
+ // it might just not exist.
+ if (lpin) {
+ auto lext = try_get_extent(t, lpin->get_key());
+ if (!lext) {
+ conflicted++;
+ return;
+ }
+ if (get_random_contents() % 2 == 0) {
+ auto lext1 = mutate_extent(t, lext);
+ ASSERT_TRUE(lext1->is_exist_mutation_pending());
+ }
+ }
+ ASSERT_TRUE(rpin);
+ last_rpin = rpin->duplicate();
+ }
+ auto last_rext = try_get_extent(t, last_rpin->get_key());
+ if (!last_rext) {
+ conflicted++;
+ return;
+ }
+ if (get_random_contents() % 2 == 0) {
+ auto last_rext1 = mutate_extent(t, last_rext);
+ ASSERT_TRUE(last_rext1->is_exist_mutation_pending());
+ }
+
+ if (try_submit_transaction(std::move(t))) {
+ success++;
+ logger().info("transaction {} submit the transction",
+ static_cast<void*>(t.t.get()));
+ } else {
+ conflicted++;
+ }
+ });
+ }).handle_exception([](std::exception_ptr e) {
+ logger().info("{}", e);
+ }).get0();
+ logger().info("test_overwrite_pin_concurrent: "
+ "early_exit {} conflicted {} success {}",
+ early_exit, conflicted, success);
+ ASSERT_TRUE(success == 1 || early_exit == REMAP_NUM);
+ ASSERT_EQ(success + conflicted + early_exit, REMAP_NUM);
+ replay();
+ check();
+ });
+ }
+};
+
+struct tm_single_device_test_t :
+ public transaction_manager_test_t {
+
+ tm_single_device_test_t() : transaction_manager_test_t(1, 0) {}
+};
+
+struct tm_multi_device_test_t :
+ public transaction_manager_test_t {
+
+ tm_multi_device_test_t() : transaction_manager_test_t(3, 0) {}
+};
+
+struct tm_multi_tier_device_test_t :
+ public transaction_manager_test_t {
+
+ tm_multi_tier_device_test_t() : transaction_manager_test_t(1, 2) {}
+};
+
+TEST_P(tm_single_device_test_t, basic)
+{
+ constexpr laddr_t SIZE = 4096;
+ run_async([this] {
+ constexpr laddr_t ADDR = 0xFF * SIZE;
+ {
+ auto t = create_transaction();
+ auto extent = alloc_extent(
+ t,
+ ADDR,
+ SIZE,
+ 'a');
+ ASSERT_EQ(ADDR, extent->get_laddr());
+ check_mappings(t);
+ check();
+ submit_transaction(std::move(t));
+ check();
+ }
+ });
+}
+
+TEST_P(tm_single_device_test_t, mutate)
+{
+ constexpr laddr_t SIZE = 4096;
+ run_async([this] {
+ constexpr laddr_t ADDR = 0xFF * SIZE;
+ {
+ auto t = create_transaction();
+ auto extent = alloc_extent(
+ t,
+ ADDR,
+ SIZE,
+ 'a');
+ ASSERT_EQ(ADDR, extent->get_laddr());
+ check_mappings(t);
+ check();
+ submit_transaction(std::move(t));
+ check();
+ }
+ ASSERT_TRUE(check_usage());
+ replay();
+ {
+ auto t = create_transaction();
+ auto ext = get_extent(
+ t,
+ ADDR,
+ SIZE);
+ auto mut = mutate_extent(t, ext);
+ check_mappings(t);
+ check();
+ submit_transaction(std::move(t));
+ check();
+ }
+ ASSERT_TRUE(check_usage());
+ replay();
+ check();
+ });
+}
+
+TEST_P(tm_single_device_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_P(tm_single_device_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_P(tm_single_device_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_P(tm_single_device_test_t, create_remove_same_transaction)
+{
+ constexpr laddr_t SIZE = 4096;
+ run_async([this] {
+ constexpr laddr_t ADDR = 0xFF * SIZE;
+ {
+ auto t = create_transaction();
+ auto extent = alloc_extent(
+ t,
+ ADDR,
+ SIZE,
+ 'a');
+ ASSERT_EQ(ADDR, extent->get_laddr());
+ check_mappings(t);
+ dec_ref(t, ADDR);
+ check_mappings(t);
+
+ extent = alloc_extent(
+ t,
+ ADDR,
+ SIZE,
+ 'a');
+
+ submit_transaction(std::move(t));
+ check();
+ }
+ replay();
+ check();
+ });
+}
+
+TEST_P(tm_single_device_test_t, split_merge_read_same_transaction)
+{
+ 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();
+ }
+ {
+ auto t = create_transaction();
+ for (unsigned i = 0; i < 240; ++i) {
+ dec_ref(
+ t,
+ laddr_t(i * SIZE));
+ }
+ check_mappings(t);
+ submit_transaction(std::move(t));
+ check();
+ }
+ });
+}
+
+TEST_P(tm_single_device_test_t, inc_dec_ref)
+{
+ constexpr laddr_t SIZE = 4096;
+ run_async([this] {
+ constexpr laddr_t ADDR = 0xFF * SIZE;
+ {
+ auto t = create_transaction();
+ auto extent = alloc_extent(
+ t,
+ ADDR,
+ SIZE,
+ 'a');
+ ASSERT_EQ(ADDR, extent->get_laddr());
+ check_mappings(t);
+ check();
+ submit_transaction(std::move(t));
+ check();
+ }
+ replay();
+ {
+ auto t = create_transaction();
+ inc_ref(t, ADDR);
+ check_mappings(t);
+ check();
+ submit_transaction(std::move(t));
+ check();
+ }
+ {
+ auto t = create_transaction();
+ dec_ref(t, ADDR);
+ check_mappings(t);
+ check();
+ submit_transaction(std::move(t));
+ check();
+ }
+ replay();
+ {
+ auto t = create_transaction();
+ dec_ref(t, ADDR);
+ check_mappings(t);
+ check();
+ submit_transaction(std::move(t));
+ check();
+ }
+ });
+}
+
+TEST_P(tm_single_device_test_t, cause_lba_split)
+{
+ constexpr laddr_t SIZE = 4096;
+ run_async([this] {
+ for (unsigned i = 0; i < 200; ++i) {
+ auto t = create_transaction();
+ auto extent = alloc_extent(
+ t,
+ i * SIZE,
+ SIZE,
+ (char)(i & 0xFF));
+ ASSERT_EQ(i * SIZE, extent->get_laddr());
+ submit_transaction(std::move(t));
+ }
+ check();
+ });
+}
+
+TEST_P(tm_single_device_test_t, random_writes)
+{
+ constexpr size_t TOTAL = 4<<20;
+ constexpr size_t BSIZE = 4<<10;
+ constexpr size_t PADDING_SIZE = 256<<10;
+ constexpr size_t BLOCKS = TOTAL / BSIZE;
+ run_async([this] {
+ for (unsigned i = 0; i < BLOCKS; ++i) {
+ auto t = create_transaction();
+ auto extent = alloc_extent(
+ t,
+ i * BSIZE,
+ BSIZE);
+ ASSERT_EQ(i * BSIZE, extent->get_laddr());
+ submit_transaction(std::move(t));
+ }
+
+ for (unsigned i = 0; i < 4; ++i) {
+ for (unsigned j = 0; j < 65; ++j) {
+ auto t = create_transaction();
+ for (unsigned k = 0; k < 2; ++k) {
+ auto ext = get_extent(
+ t,
+ get_random_laddr(BSIZE, TOTAL),
+ BSIZE);
+ auto mut = mutate_extent(t, ext);
+ // pad out transaction
+ auto padding = alloc_extent(
+ t,
+ TOTAL + (k * PADDING_SIZE),
+ PADDING_SIZE);
+ dec_ref(t, padding->get_laddr());
+ }
+ submit_transaction(std::move(t));
+ }
+ replay();
+ logger().info("random_writes: {} checking", i);
+ check();
+ logger().info("random_writes: {} done replaying/checking", i);
+ }
+ });
+}
+
+TEST_P(tm_single_device_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);
+ });
+ });
+}
+
+TEST_P(tm_single_device_test_t, remap_lazy_read)
+{
+ constexpr laddr_t offset = 0;
+ constexpr size_t length = 256 << 10;
+ run_async([this, offset] {
+ {
+ auto t = create_transaction();
+ auto extent = alloc_extent(
+ t,
+ offset,
+ length,
+ 'a');
+ ASSERT_EQ(offset, extent->get_laddr());
+ check_mappings(t);
+ submit_transaction(std::move(t));
+ check();
+ }
+ replay();
+ {
+ auto t = create_transaction();
+ auto pin = get_pin(t, offset);
+ auto rpin = remap_pin(t, std::move(pin), 0, 128 << 10);
+ check_mappings(t);
+ submit_transaction(std::move(t));
+ check();
+ }
+ replay();
+ {
+ auto t = create_transaction();
+ auto pin = get_pin(t, offset);
+ bufferlist bl;
+ bl.append(ceph::bufferptr(ceph::buffer::create(64 << 10, 0)));
+ auto [lpin, ext, rpin] = overwrite_pin(
+ t, std::move(pin), 4 << 10 , 64 << 10, bl);
+ check_mappings(t);
+ submit_transaction(std::move(t));
+ check();
+ }
+ replay();
+ });
+}
+
+TEST_P(tm_single_device_test_t, random_writes_concurrent)
+{
+ test_random_writes_concurrent();
+}
+
+TEST_P(tm_multi_device_test_t, random_writes_concurrent)
+{
+ test_random_writes_concurrent();
+}
+
+TEST_P(tm_multi_tier_device_test_t, evict)
+{
+ test_evict();
+}
+
+TEST_P(tm_single_device_test_t, parallel_extent_read)
+{
+ test_parallel_extent_read();
+}
+
+TEST_P(tm_single_device_test_t, test_remap_pin)
+{
+ test_remap_pin();
+}
+
+TEST_P(tm_single_device_test_t, test_overwrite_pin)
+{
+ test_overwrite_pin();
+}
+
+TEST_P(tm_single_device_test_t, test_remap_pin_concurrent)
+{
+ test_remap_pin_concurrent();
+}
+
+TEST_P(tm_single_device_test_t, test_overwrite_pin_concurrent)
+{
+ test_overwrite_pin_concurrent();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ transaction_manager_test,
+ tm_single_device_test_t,
+ ::testing::Values (
+ "segmented",
+ "circularbounded"
+ )
+);
+
+INSTANTIATE_TEST_SUITE_P(
+ transaction_manager_test,
+ tm_multi_device_test_t,
+ ::testing::Values (
+ "segmented"
+ )
+);
+
+INSTANTIATE_TEST_SUITE_P(
+ transaction_manager_test,
+ tm_multi_tier_device_test_t,
+ ::testing::Values (
+ "segmented"
+ )
+);