// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include #include "test/crimson/gtest_seastar.h" #include "crimson/common/interruptible_future.h" #include "crimson/common/log.h" using namespace crimson; class test_interruption : public std::exception {}; class TestInterruptCondition { public: TestInterruptCondition(bool interrupt) : interrupt(interrupt) {} template std::optional may_interrupt() { if (interrupt) { return seastar::futurize::make_exception_future(test_interruption()); } else { return std::optional(); } } template static constexpr bool is_interruption_v = std::is_same_v; static bool is_interruption(std::exception_ptr& eptr) { if (*eptr.__cxa_exception_type() == typeid(test_interruption)) return true; return false; } private: bool interrupt = false; }; namespace crimson::interruptible { template thread_local interrupt_cond_t interrupt_cond; } TEST_F(seastar_test_suite_t, basic) { using interruptor = interruptible::interruptor; run_async([] { interruptor::with_interruption( [] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return interruptor::make_interruptible(seastar::now()) .then_interruptible([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); }).then_interruptible([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return errorator::make_ready_future<>(); }).safe_then_interruptible([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return seastar::now(); }, errorator::all_same_way([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); }) ); }, [](std::exception_ptr) {}, false).get0(); interruptor::with_interruption( [] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return interruptor::make_interruptible(seastar::now()) .then_interruptible([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); }); }, [](std::exception_ptr) { ceph_assert(!interruptible::interrupt_cond.interrupt_cond); return seastar::now(); }, true).get0(); }); } TEST_F(seastar_test_suite_t, loops) { using interruptor = interruptible::interruptor; std::cout << "testing interruptible loops" << std::endl; run_async([] { std::cout << "beginning" << std::endl; interruptor::with_interruption( [] { std::cout << "interruptiion enabled" << std::endl; ceph_assert(interruptible::interrupt_cond.interrupt_cond); return interruptor::make_interruptible(seastar::now()) .then_interruptible([] { std::cout << "test seastar future do_for_each" << std::endl; std::vector vec = {1, 2}; return seastar::do_with(std::move(vec), [](auto& vec) { return interruptor::do_for_each(std::begin(vec), std::end(vec), [](int) { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return seastar::now(); }); }); }).then_interruptible([] { std::cout << "test interruptible seastar future do_for_each" << std::endl; std::vector vec = {1, 2}; return seastar::do_with(std::move(vec), [](auto& vec) { return interruptor::do_for_each(std::begin(vec), std::end(vec), [](int) { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return interruptor::make_interruptible(seastar::now()); }); }); }).then_interruptible([] { std::cout << "test seastar future repeat" << std::endl; return interruptor::repeat([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return interruptor::make_interruptible( seastar::make_ready_future< seastar::stop_iteration>( seastar::stop_iteration::yes)); }); }).then_interruptible([] { std::cout << "test interruptible seastar future repeat" << std::endl; return interruptor::repeat([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return seastar::make_ready_future< seastar::stop_iteration>( seastar::stop_iteration::yes); }); }).then_interruptible([] { std::cout << "test interruptible errorated future do_for_each" << std::endl; std::vector vec = {1, 2}; return seastar::do_with(std::move(vec), [](auto& vec) { using namespace std::chrono_literals; return interruptor::make_interruptible(seastar::now()).then_interruptible([&vec] { return interruptor::do_for_each(std::begin(vec), std::end(vec), [](int) { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return interruptor::make_interruptible( errorator::make_ready_future<>()); }).safe_then_interruptible([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return seastar::now(); }, errorator::all_same_way([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); })); }); }); }).then_interruptible([] { std::cout << "test errorated future do_for_each" << std::endl; std::vector vec; // set a big enough iteration times to test if there is stack overflow in do_for_each for (int i = 0; i < 1000000; i++) { vec.push_back(i); } return seastar::do_with(std::move(vec), [](auto& vec) { using namespace std::chrono_literals; return interruptor::make_interruptible(seastar::now()).then_interruptible([&vec] { return interruptor::do_for_each(std::begin(vec), std::end(vec), [](int) { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return errorator::make_ready_future<>(); }).safe_then_interruptible([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return seastar::now(); }, errorator::all_same_way([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); })); }); }); }).then_interruptible([] { ceph_assert(interruptible::interrupt_cond.interrupt_cond); return seastar::now(); }); }, [](std::exception_ptr) {}, false).get0(); }); } using base_intr = interruptible::interruptor; using base_ertr = errorator; using base_iertr = interruptible::interruptible_errorator< TestInterruptCondition, base_ertr>; using base2_ertr = base_ertr::extend; using base2_iertr = interruptible::interruptible_errorator< TestInterruptCondition, base2_ertr>; template auto with_intr(F &&f) { return base_intr::with_interruption_to_error( std::forward(f), TestInterruptCondition(false)); } TEST_F(seastar_test_suite_t, errorated) { run_async([] { base_ertr::future<> ret = with_intr( []() { return base_iertr::now(); } ); ret.unsafe_get0(); }); } TEST_F(seastar_test_suite_t, errorated_value) { run_async([] { base_ertr::future ret = with_intr( []() { return base_iertr::make_ready_future( 1 ); }); EXPECT_EQ(ret.unsafe_get0(), 1); }); } TEST_F(seastar_test_suite_t, expand_errorated_value) { run_async([] { base2_ertr::future<> ret = with_intr( []() { return base_iertr::make_ready_future( 1 ).si_then([](auto) { return base2_iertr::make_ready_future<>(); }); }); ret.unsafe_get0(); }); } TEST_F(seastar_test_suite_t, interruptible_async) { using interruptor = interruptible::interruptor; run_async([] { interruptor::with_interruption([] { auto fut = interruptor::async([] { interruptor::make_interruptible( seastar::sleep(std::chrono::milliseconds(10))).get(); ceph_assert(interruptible::interrupt_cond< TestInterruptCondition>.interrupt_cond); ceph_assert(interruptible::interrupt_cond< TestInterruptCondition>.ref_count == 1); }); ceph_assert(interruptible::interrupt_cond< TestInterruptCondition>.interrupt_cond); ceph_assert(interruptible::interrupt_cond< TestInterruptCondition>.ref_count == 1); return fut; }, [](std::exception_ptr) {}, false).get0(); }); } TEST_F(seastar_test_suite_t, DISABLED_nested_interruptors) { run_async([] { base_ertr::future<> ret = with_intr( []() { return base_iertr::now().safe_then_interruptible([]() { return with_intr( []() { return base_iertr::now(); } ); }); } ); ret.unsafe_get0(); }); } #if 0 // This seems to cause a hang in the gcc-9 linker on bionic TEST_F(seastar_test_suite_t, handle_error) { run_async([] { base_ertr::future<> ret = with_intr( []() { return base2_iertr::make_ready_future( 1 ).handle_error_interruptible( base_iertr::pass_further{}, ct_error::assert_all{"crash on eio"} ).si_then([](auto) { return base_iertr::now(); }); }); ret.unsafe_get0(); }); } #endif