]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/seastar/tests/unit/coroutines_test.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / seastar / tests / unit / coroutines_test.cc
index b4b3bb2c20c0dad09788754dd2e7a86a13799491..733a4670938f114c0f77beff4a784d0c053e9506 100644 (file)
 
 #include <seastar/core/future-util.hh>
 #include <seastar/testing/test_case.hh>
+#include <seastar/core/sleep.hh>
 
 using namespace seastar;
+using namespace std::chrono_literals;
 
 #ifndef SEASTAR_COROUTINES_ENABLED
 
@@ -33,6 +35,8 @@ SEASTAR_TEST_CASE(test_coroutines_not_compiled_in) {
 #else
 
 #include <seastar/core/coroutine.hh>
+#include <seastar/coroutine/all.hh>
+#include <seastar/coroutine/maybe_yield.hh>
 
 namespace {
 
@@ -60,6 +64,15 @@ future<int> failing_coroutine() {
     throw 42;
 }
 
+[[gnu::noinline]] int throw_exception(int x) {
+    throw x;
+}
+
+future<int> failing_coroutine2() noexcept {
+    co_await later();
+    co_return throw_exception(17);
+}
+
 }
 
 SEASTAR_TEST_CASE(test_simple_coroutines) {
@@ -68,32 +81,23 @@ SEASTAR_TEST_CASE(test_simple_coroutines) {
     BOOST_REQUIRE_EQUAL(ready_coroutine().get0(), 64);
     BOOST_REQUIRE(co_await tuple_coroutine() == std::tuple(1, 2.));
     BOOST_REQUIRE_EXCEPTION((void)co_await failing_coroutine(), int, [] (auto v) { return v == 42; });
-}
-
-
-future<> forwarding_return_coroutine_1(bool& x) {
-    co_return
-// Clang complains if both return_value and return_void are defined
-#if defined(__clang__)
-    co_await
-#endif
-      later().then([&x] {
-        x = true;
-    });
-}
-
-future<int> forwarding_return_coroutine_2() {
-    co_return later().then([] {
-        return 3;
-    });
-}
-
-SEASTAR_TEST_CASE(test_forwarding_return) {
-    bool x = false;
-    co_await forwarding_return_coroutine_1(x);
-    BOOST_REQUIRE(x);
-    auto y = co_await forwarding_return_coroutine_2();
-    BOOST_REQUIRE_EQUAL(y, 3);
+    BOOST_CHECK_EQUAL(co_await failing_coroutine().then_wrapped([] (future<int> f) -> future<int> {
+        BOOST_REQUIRE(f.failed());
+        try {
+            std::rethrow_exception(f.get_exception());
+        } catch (int v) {
+           co_return v;
+        }
+    }), 42);
+    BOOST_REQUIRE_EXCEPTION((void)co_await failing_coroutine2(), int, [] (auto v) { return v == 17; });
+    BOOST_CHECK_EQUAL(co_await failing_coroutine2().then_wrapped([] (future<int> f) -> future<int> {
+        BOOST_REQUIRE(f.failed());
+        try {
+            std::rethrow_exception(f.get_exception());
+        } catch (int v) {
+           co_return v;
+        }
+    }), 17);
 }
 
 SEASTAR_TEST_CASE(test_abandond_coroutine) {
@@ -150,4 +154,187 @@ SEASTAR_TEST_CASE(test_scheduling_group) {
     BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
 }
 
+SEASTAR_TEST_CASE(test_preemption) {
+    bool x = false;
+    unsigned preempted = 0;
+    auto f = later().then([&x] {
+            x = true;
+        });
+
+    // try to preempt 1000 times. 1 should be enough if not for
+    // task queue shaffling in debug mode which may cause co-routine
+    // continuation to run first.
+    while(preempted < 1000 && !x) {
+        preempted += need_preempt(); 
+        co_await make_ready_future<>();
+    }
+    auto save_x = x;
+    // wait for later() to complete
+    co_await std::move(f);
+    BOOST_REQUIRE(save_x);
+    co_return;
+}
+
+SEASTAR_TEST_CASE(test_all_simple) {
+    auto [a, b] = co_await coroutine::all(
+        [] { return make_ready_future<int>(1); },
+        [] { return make_ready_future<int>(2); }
+    );
+    BOOST_REQUIRE_EQUAL(a, 1);
+    BOOST_REQUIRE_EQUAL(b, 2);
+}
+
+SEASTAR_TEST_CASE(test_all_permutations) {
+    std::vector<std::chrono::milliseconds> delays = { 0ms, 0ms, 2ms, 2ms, 4ms, 6ms };
+    auto make_delayed_future_returning_nr = [&] (int nr) {
+        return [=] {
+            auto delay = delays[nr];
+            return delay == 0ms ? make_ready_future<int>(nr) : sleep(delay).then([nr] { return make_ready_future<int>(nr); });
+        };
+    };
+    do {
+        auto [a, b, c, d, e, f] = co_await coroutine::all(
+            make_delayed_future_returning_nr(0),
+            make_delayed_future_returning_nr(1),
+            make_delayed_future_returning_nr(2),
+            make_delayed_future_returning_nr(3),
+            make_delayed_future_returning_nr(4),
+            make_delayed_future_returning_nr(5)
+        );
+        BOOST_REQUIRE_EQUAL(a, 0);
+        BOOST_REQUIRE_EQUAL(b, 1);
+        BOOST_REQUIRE_EQUAL(c, 2);
+        BOOST_REQUIRE_EQUAL(d, 3);
+        BOOST_REQUIRE_EQUAL(e, 4);
+        BOOST_REQUIRE_EQUAL(f, 5);
+    } while (std::ranges::next_permutation(delays).found);
+}
+
+SEASTAR_TEST_CASE(test_all_ready_exceptions) {
+    try {
+        co_await coroutine::all(
+            [] () -> future<> { throw 1; },
+            [] () -> future<> { throw 2; }
+        );
+    } catch (int e) {
+        BOOST_REQUIRE(e == 1 || e == 2);
+    }
+}
+
+SEASTAR_TEST_CASE(test_all_nonready_exceptions) {
+    try {
+        co_await coroutine::all(
+            [] () -> future<> { 
+                co_await sleep(1ms);
+                throw 1;
+            },
+            [] () -> future<> { 
+                co_await sleep(1ms);
+                throw 2;
+            }
+        );
+    } catch (int e) {
+        BOOST_REQUIRE(e == 1 || e == 2);
+    }
+}
+
+SEASTAR_TEST_CASE(test_all_heterogeneous_types) {
+    auto [a, b] = co_await coroutine::all(
+        [] () -> future<int> { 
+            co_await sleep(1ms);
+            co_return 1;
+        },
+        [] () -> future<> { 
+            co_await sleep(1ms);
+        },
+        [] () -> future<long> { 
+            co_await sleep(1ms);
+            co_return 2L;
+        }
+    );
+    BOOST_REQUIRE_EQUAL(a, 1);
+    BOOST_REQUIRE_EQUAL(b, 2L);
+}
+
+SEASTAR_TEST_CASE(test_all_noncopyable_types) {
+    auto [a] = co_await coroutine::all(
+        [] () -> future<std::unique_ptr<int>> {
+            co_return std::make_unique<int>(6);
+        }
+    );
+    BOOST_REQUIRE_EQUAL(*a, 6);
+}
+
+SEASTAR_TEST_CASE(test_all_throw_in_input_func) {
+    int nr_completed = 0;
+    bool exception_seen = false;
+    try {
+        co_await coroutine::all(
+            [&] () -> future<int> {
+                co_await sleep(1ms);
+                ++nr_completed;
+                co_return 7;
+            },
+            [&] () -> future<int> {
+                throw 9;
+            },
+            [&] () -> future<int> {
+                co_await sleep(1ms);
+                ++nr_completed;
+                co_return 7;
+            }
+        );
+    } catch (int n) {
+        BOOST_REQUIRE_EQUAL(n, 9);
+        exception_seen = true;
+    }
+    BOOST_REQUIRE_EQUAL(nr_completed, 2);
+    BOOST_REQUIRE(exception_seen);
+}
+
+SEASTAR_TEST_CASE(test_coroutine_exception) {
+    auto i_am_exceptional = [] () -> future<int> {
+        co_return coroutine::exception(std::make_exception_ptr(std::runtime_error("threw")));
+    };
+    BOOST_REQUIRE_THROW(co_await i_am_exceptional(), std::runtime_error);
+    co_await i_am_exceptional().then_wrapped([] (future<int> f) {
+        BOOST_REQUIRE(f.failed());
+        BOOST_REQUIRE_THROW(std::rethrow_exception(f.get_exception()), std::runtime_error);
+    });
+
+    auto i_am_exceptional_as_well = [] () -> future<bool> {
+        co_return coroutine::make_exception(std::logic_error("threw"));
+    };
+    BOOST_REQUIRE_THROW(co_await i_am_exceptional_as_well(), std::logic_error);
+    co_await i_am_exceptional_as_well().then_wrapped([] (future<bool> f) {
+        BOOST_REQUIRE(f.failed());
+        BOOST_REQUIRE_THROW(std::rethrow_exception(f.get_exception()), std::logic_error);
+    });
+}
+
+SEASTAR_TEST_CASE(test_maybe_yield) {
+    int var = 0;
+    bool done = false;
+    auto spinner = [&] () -> future<> {
+        // increment a variable continuously, but yield so an observer can see it.
+        while (!done) {
+            ++var;
+            co_await coroutine::maybe_yield();
+        }
+    };
+    auto spinner_fut = spinner();
+    int snapshot = var;
+    for (int nr_changes = 0; nr_changes < 10; ++nr_changes) {
+        // Try to observe the value changing in time, yield to
+        // allow the spinner to advance it.
+        while (snapshot == var) {
+            co_await coroutine::maybe_yield();
+        }
+        snapshot = var;
+    }
+    done = true;
+    co_await std::move(spinner_fut);
+    BOOST_REQUIRE(true); // the test will hang if it doesn't work.
+}
+
 #endif