]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/seastar/tests/unit/futures_test.cc
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / seastar / tests / unit / futures_test.cc
index 85db6984025c55fb8ba8ffc9f77da3ac3ffdab07..200e33ac90d58c576f525b66a38626de52f33291 100644 (file)
 
 #include <seastar/testing/test_case.hh>
 
+#include <seastar/core/reactor.hh>
 #include <seastar/core/shared_ptr.hh>
 #include <seastar/core/future-util.hh>
 #include <seastar/core/sleep.hh>
+#include <seastar/core/stream.hh>
+#include <seastar/util/backtrace.hh>
 #include <seastar/core/do_with.hh>
 #include <seastar/core/shared_future.hh>
+#include <seastar/core/manual_clock.hh>
 #include <seastar/core/thread.hh>
 #include <seastar/core/print.hh>
+#include <seastar/core/gate.hh>
+#include <seastar/util/log.hh>
 #include <boost/iterator/counting_iterator.hpp>
 #include <seastar/testing/thread_test_case.hh>
 
+#include <boost/range/iterator_range.hpp>
+#include <boost/range/irange.hpp>
+
+#include <seastar/core/internal/api-level.hh>
+
 using namespace seastar;
 using namespace std::chrono_literals;
 
-class expected_exception : std::runtime_error {
+static_assert(std::is_nothrow_default_constructible_v<gate>,
+    "seastar::gate constructor must not throw");
+static_assert(std::is_nothrow_move_constructible_v<gate>,
+    "seastar::gate move constructor must not throw");
+
+static_assert(std::is_nothrow_default_constructible_v<shared_future<>>);
+static_assert(std::is_nothrow_copy_constructible_v<shared_future<>>);
+static_assert(std::is_nothrow_move_constructible_v<shared_future<>>);
+
+static_assert(std::is_nothrow_move_constructible_v<shared_promise<>>);
+
+class expected_exception : public std::runtime_error {
 public:
     expected_exception() : runtime_error("expected") {}
 };
@@ -44,14 +66,18 @@ public:
 #pragma clang diagnostic ignored "-Wself-move"
 #endif
 SEASTAR_TEST_CASE(test_self_move) {
-    future_state<std::unique_ptr<int>> s1;
+    future_state<std::tuple<std::unique_ptr<int>>> s1;
     s1.set(std::make_unique<int>(42));
     s1 = std::move(s1); // no crash, but the value of s1 is not defined.
 
+#if SEASTAR_API_LEVEL < 5
+    future_state<std::tuple<std::unique_ptr<int>>> s2;
+#else
     future_state<std::unique_ptr<int>> s2;
+#endif
     s2.set(std::make_unique<int>(42));
     std::swap(s2, s2);
-    BOOST_REQUIRE_EQUAL(*std::get<0>(std::move(s2).get()), 42);
+    BOOST_REQUIRE_EQUAL(*std::move(s2).get0(), 42);
 
     promise<std::unique_ptr<int>> p1;
     p1.set_value(std::make_unique<int>(42));
@@ -91,12 +117,12 @@ SEASTAR_TEST_CASE(test_stream) {
 
 SEASTAR_TEST_CASE(test_stream_drop_sub) {
     auto s = make_lw_shared<stream<int>>();
-    compat::optional<future<>> ret;
+    std::optional<future<>> ret;
     {
         auto sub = s->listen([](int x) {
             return make_ready_future<>();
         });
-        *ret = sub.done();
+        ret = sub.done();
         // It is ok to drop the subscription when we only want the competition future.
     }
     return s->produce(42).then([ret = std::move(*ret), s] () mutable {
@@ -105,23 +131,27 @@ SEASTAR_TEST_CASE(test_stream_drop_sub) {
     });
 }
 
+SEASTAR_TEST_CASE(test_reference) {
+    int a = 42;
+    future<int&> orig = make_ready_future<int&>(a);
+    future<int&> fut = std::move(orig);
+    int& r = fut.get0();
+    r = 43;
+    BOOST_REQUIRE_EQUAL(a, 43);
+    return make_ready_future<>();
+}
+
 SEASTAR_TEST_CASE(test_set_future_state_with_tuple) {
-    future_state<int> s1;
+    future_state<std::tuple<int>> s1;
     promise<int> p1;
     const std::tuple<int> v1(42);
     s1.set(v1);
     p1.set_value(v1);
 
-    future_state<int, int> s2;
-    promise<int, int> p2;
-    const std::tuple<int, int> v2(41, 42);
-    s2.set(v2);
-    p2.set_value(v2);
-
     return make_ready_future<>();
 }
 
-SEASTAR_TEST_CASE(test_set_value_throw_in_copy) {
+SEASTAR_THREAD_TEST_CASE(test_set_value_make_exception_in_copy) {
     struct throw_in_copy {
         throw_in_copy() noexcept = default;
         throw_in_copy(throw_in_copy&& x) noexcept {
@@ -132,8 +162,19 @@ SEASTAR_TEST_CASE(test_set_value_throw_in_copy) {
     };
     promise<throw_in_copy> p1;
     throw_in_copy v;
-    BOOST_REQUIRE_THROW(p1.set_value(v), int);
-    return make_ready_future<>();
+    p1.set_value(v);
+    BOOST_REQUIRE_THROW(p1.get_future().get(), int);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_set_exception_in_constructor) {
+    struct throw_in_constructor {
+        throw_in_constructor() {
+            throw 42;
+        }
+    };
+    future<throw_in_constructor> f = make_ready_future<throw_in_constructor>();
+    BOOST_REQUIRE(f.failed());
+    BOOST_REQUIRE_THROW(f.get(), int);
 }
 
 SEASTAR_TEST_CASE(test_finally_is_called_on_success_and_failure) {
@@ -166,6 +207,52 @@ SEASTAR_TEST_CASE(test_get_on_promise) {
     return make_ready_future();
 }
 
+// An exception class with a controlled what() overload
+class test_exception : public std::exception {
+    sstring _what;
+public:
+    explicit test_exception(sstring what) : _what(std::move(what)) {}
+    virtual const char* what() const noexcept override {
+        return _what.c_str();
+    }
+};
+
+static void check_finally_exception(std::exception_ptr ex) {
+  BOOST_REQUIRE_EQUAL(fmt::format("{}", ex),
+        "seastar::nested_exception: test_exception (bar) (while cleaning up after test_exception (foo))");
+  try {
+      // convert to the concrete type nested_exception
+      std::rethrow_exception(ex);
+  } catch (seastar::nested_exception& ex) {
+    try {
+        std::rethrow_exception(ex.inner);
+    } catch (test_exception& inner) {
+        BOOST_REQUIRE_EQUAL(inner.what(), "bar");
+    }
+    try {
+        ex.rethrow_nested();
+    } catch (test_exception& outer) {
+        BOOST_REQUIRE_EQUAL(outer.what(), "foo");
+    }
+  }
+}
+
+SEASTAR_TEST_CASE(test_finally_exception) {
+    return make_ready_future<>().then([] {
+        throw test_exception("foo");
+    }).finally([] {
+        throw test_exception("bar");
+    }).handle_exception(check_finally_exception);
+}
+
+SEASTAR_TEST_CASE(test_finally_exceptional_future) {
+    return make_ready_future<>().then([] {
+        throw test_exception("foo");
+    }).finally([] {
+       return make_exception_future<>(test_exception("bar"));
+    }).handle_exception(check_finally_exception);
+}
+
 SEASTAR_TEST_CASE(test_finally_waits_for_inner) {
     auto finally = make_shared<bool>();
     auto p = make_shared<promise<>>();
@@ -383,10 +470,10 @@ SEASTAR_TEST_CASE(test_bare_value_can_be_returned_from_callback) {
 SEASTAR_TEST_CASE(test_when_all_iterator_range) {
     std::vector<future<size_t>> futures;
     for (size_t i = 0; i != 1000000; ++i) {
-        // .then() usually returns a ready future, but sometimes it
-        // doesn't, so call it a million times.  This exercises both
-        // available and unavailable paths in when_all().
-        futures.push_back(make_ready_future<>().then([i] { return i; }));
+        // Use a mix of available and unavailable futures to exercise
+        // both paths in when_all().
+        auto fut = (i % 2) == 0 ? make_ready_future<>() : later();
+        futures.push_back(fut.then([i] { return i; }));
     }
     // Verify the above statement is correct
     BOOST_REQUIRE(!std::all_of(futures.begin(), futures.end(),
@@ -394,7 +481,7 @@ SEASTAR_TEST_CASE(test_when_all_iterator_range) {
     auto p = make_shared(std::move(futures));
     return when_all(p->begin(), p->end()).then([p] (std::vector<future<size_t>> ret) {
         BOOST_REQUIRE(std::all_of(ret.begin(), ret.end(), [] (auto& f) { return f.available(); }));
-        BOOST_REQUIRE(std::all_of(ret.begin(), ret.end(), [&ret] (auto& f) { return std::get<0>(f.get()) == size_t(&f - ret.data()); }));
+        BOOST_REQUIRE(std::all_of(ret.begin(), ret.end(), [&ret] (auto& f) { return f.get0() == size_t(&f - ret.data()); }));
     });
 }
 
@@ -408,6 +495,31 @@ SEASTAR_TEST_CASE(test_map_reduce) {
     });
 }
 
+SEASTAR_TEST_CASE(test_map_reduce_simple) {
+    return do_with(0L, [] (auto& res) {
+        long n = 10;
+        return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n),
+                [] (long x) { return x; },
+                [&res] (long x) { res += x; }).then([n, &res] {
+            long expected = (n * (n - 1)) / 2;
+            BOOST_REQUIRE_EQUAL(res, expected);
+        });
+    });
+}
+
+SEASTAR_TEST_CASE(test_map_reduce_tuple) {
+    return do_with(0L, 0L, [] (auto& res0, auto& res1) {
+        long n = 10;
+        return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n),
+                [] (long x) { return std::tuple<long, long>(x, -x); },
+                [&res0, &res1] (std::tuple<long, long> t) { res0 += std::get<0>(t); res1 += std::get<1>(t); }).then([n, &res0, &res1] {
+            long expected = (n * (n - 1)) / 2;
+            BOOST_REQUIRE_EQUAL(res0, expected);
+            BOOST_REQUIRE_EQUAL(res1, -expected);
+        });
+    });
+}
+
 // This test doesn't actually test anything - it just waits for the future
 // returned by sleep to complete. However, a bug we had in sleep() caused
 // this test to fail the sanitizer in the debug build, so this is a useful
@@ -450,6 +562,27 @@ SEASTAR_TEST_CASE(test_do_with_4) {
     });
 }
 
+SEASTAR_TEST_CASE(test_do_with_5) {
+    using func = noncopyable_function<void()>;
+    return do_with(func([] {}), [] (func&) {
+        return make_ready_future<>();
+    });
+}
+
+SEASTAR_TEST_CASE(test_do_with_6) {
+    const int x = 42;
+    return do_with(int(42), x, [](int&, int&) {
+        return make_ready_future<>();
+    });
+}
+
+SEASTAR_TEST_CASE(test_do_with_7) {
+    const int x = 42;
+    return do_with(x, [](int&) {
+        return make_ready_future<>();
+    });
+}
+
 SEASTAR_TEST_CASE(test_do_while_stopping_immediately) {
     return do_with(int(0), [] (int& count) {
         return repeat([&count] {
@@ -595,19 +728,30 @@ SEASTAR_TEST_CASE(test_parallel_for_each_waits_for_all_fibers_even_if_one_of_the
     });
 }
 
-#ifndef SEASTAR_SHUFFLE_TASK_QUEUE
-SEASTAR_TEST_CASE(test_high_priority_task_runs_before_ready_continuations) {
-    return now().then([] {
-        auto flag = make_lw_shared<bool>(false);
-        engine().add_high_priority_task(make_task([flag] {
-            *flag = true;
-        }));
-        return make_ready_future().then([flag] {
-            BOOST_REQUIRE(*flag);
+SEASTAR_THREAD_TEST_CASE(test_parallel_for_each_broken_promise) {
+    auto fut = [] {
+        std::vector<promise<>> v(2);
+        return parallel_for_each(v, [] (promise<>& p) {
+            return p.get_future();
         });
+    }();
+    BOOST_CHECK_THROW(fut.get(), broken_promise);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_repeat_broken_promise) {
+    auto get_fut = [] {
+        promise<stop_iteration> pr;
+        return pr.get_future();
+    };
+
+    future<> r = repeat([fut = get_fut()] () mutable {
+        return std::move(fut);
     });
+
+    BOOST_CHECK_THROW(r.get(), broken_promise);
 }
 
+#ifndef SEASTAR_SHUFFLE_TASK_QUEUE
 SEASTAR_TEST_CASE(test_high_priority_task_runs_in_the_middle_of_loops) {
     auto counter = make_lw_shared<int>(0);
     auto flag = make_lw_shared<bool>(false);
@@ -625,8 +769,8 @@ SEASTAR_TEST_CASE(test_high_priority_task_runs_in_the_middle_of_loops) {
 }
 #endif
 
-SEASTAR_TEST_CASE(futurize_apply_val_exception) {
-    return futurize<int>::apply([] (int arg) { throw expected_exception(); return arg; }, 1).then_wrapped([] (future<int> f) {
+SEASTAR_TEST_CASE(futurize_invoke_val_exception) {
+    return futurize_invoke([] (int arg) { throw expected_exception(); return arg; }, 1).then_wrapped([] (future<int> f) {
         try {
             f.get();
             BOOST_FAIL("should have thrown");
@@ -634,8 +778,8 @@ SEASTAR_TEST_CASE(futurize_apply_val_exception) {
     });
 }
 
-SEASTAR_TEST_CASE(futurize_apply_val_ok) {
-    return futurize<int>::apply([] (int arg) { return arg * 2; }, 2).then_wrapped([] (future<int> f) {
+SEASTAR_TEST_CASE(futurize_invoke_val_ok) {
+    return futurize_invoke([] (int arg) { return arg * 2; }, 2).then_wrapped([] (future<int> f) {
         try {
             auto x = f.get0();
             BOOST_REQUIRE_EQUAL(x, 4);
@@ -645,8 +789,8 @@ SEASTAR_TEST_CASE(futurize_apply_val_ok) {
     });
 }
 
-SEASTAR_TEST_CASE(futurize_apply_val_future_exception) {
-    return futurize<int>::apply([] (int a) {
+SEASTAR_TEST_CASE(futurize_invoke_val_future_exception) {
+    return futurize_invoke([] (int a) {
         return sleep(std::chrono::milliseconds(100)).then([] {
             throw expected_exception();
             return make_ready_future<int>(0);
@@ -659,8 +803,8 @@ SEASTAR_TEST_CASE(futurize_apply_val_future_exception) {
     });
 }
 
-SEASTAR_TEST_CASE(futurize_apply_val_future_ok) {
-    return futurize<int>::apply([] (int a) {
+SEASTAR_TEST_CASE(futurize_invoke_val_future_ok) {
+    return futurize_invoke([] (int a) {
         return sleep(std::chrono::milliseconds(100)).then([a] {
             return make_ready_future<int>(a * 100);
         });
@@ -673,8 +817,8 @@ SEASTAR_TEST_CASE(futurize_apply_val_future_ok) {
         }
     });
 }
-SEASTAR_TEST_CASE(futurize_apply_void_exception) {
-    return futurize<void>::apply([] (auto arg) { throw expected_exception(); }, 0).then_wrapped([] (future<> f) {
+SEASTAR_TEST_CASE(futurize_invoke_void_exception) {
+    return futurize_invoke([] (auto arg) { throw expected_exception(); }, 0).then_wrapped([] (future<> f) {
         try {
             f.get();
             BOOST_FAIL("should have thrown");
@@ -682,8 +826,8 @@ SEASTAR_TEST_CASE(futurize_apply_void_exception) {
     });
 }
 
-SEASTAR_TEST_CASE(futurize_apply_void_ok) {
-    return futurize<void>::apply([] (auto arg) { }, 0).then_wrapped([] (future<> f) {
+SEASTAR_TEST_CASE(futurize_invoke_void_ok) {
+    return futurize_invoke([] (auto arg) { }, 0).then_wrapped([] (future<> f) {
         try {
             f.get();
         } catch (expected_exception& e) {
@@ -692,8 +836,8 @@ SEASTAR_TEST_CASE(futurize_apply_void_ok) {
     });
 }
 
-SEASTAR_TEST_CASE(futurize_apply_void_future_exception) {
-    return futurize<void>::apply([] (auto a) {
+SEASTAR_TEST_CASE(futurize_invoke_void_future_exception) {
+    return futurize_invoke([] (auto a) {
         return sleep(std::chrono::milliseconds(100)).then([] {
             throw expected_exception();
         });
@@ -705,9 +849,9 @@ SEASTAR_TEST_CASE(futurize_apply_void_future_exception) {
     });
 }
 
-SEASTAR_TEST_CASE(futurize_apply_void_future_ok) {
+SEASTAR_TEST_CASE(futurize_invoke_void_future_ok) {
     auto a = make_lw_shared<int>(1);
-    return futurize<void>::apply([] (int& a) {
+    return futurize_invoke([] (int& a) {
         return sleep(std::chrono::milliseconds(100)).then([&a] {
             a *= 100;
         });
@@ -832,19 +976,23 @@ SEASTAR_TEST_CASE(test_ignored_future_warning) {
 SEASTAR_TEST_CASE(test_futurize_from_tuple) {
     std::tuple<int> v1 = std::make_tuple(3);
     std::tuple<> v2 = {};
-    BOOST_REQUIRE(futurize<int>::from_tuple(v1).get() == v1);
-    BOOST_REQUIRE(futurize<void>::from_tuple(v2).get() == v2);
+    future<int> fut1 = futurize<int>::from_tuple(v1);
+    future<> fut2 = futurize<void>::from_tuple(v2);
+    BOOST_REQUIRE(fut1.get0() == std::get<0>(v1));
+#if SEASTAR_API_LEVEL < 5
+    BOOST_REQUIRE(fut2.get() == v2);
+#endif
     return make_ready_future<>();
 }
 
 SEASTAR_TEST_CASE(test_repeat_until_value) {
     return do_with(int(), [] (int& counter) {
-        return repeat_until_value([&counter] () -> future<compat::optional<int>> {
+        return repeat_until_value([&counter] () -> future<std::optional<int>> {
             if (counter == 10000) {
-                return make_ready_future<compat::optional<int>>(counter);
+                return make_ready_future<std::optional<int>>(counter);
             } else {
                 ++counter;
-                return make_ready_future<compat::optional<int>>(compat::nullopt);
+                return make_ready_future<std::optional<int>>(std::nullopt);
             }
         }).then([&counter] (int result) {
             BOOST_REQUIRE(counter == 10000);
@@ -854,14 +1002,14 @@ SEASTAR_TEST_CASE(test_repeat_until_value) {
 }
 
 SEASTAR_TEST_CASE(test_repeat_until_value_implicit_future) {
-    // Same as above, but returning compat::optional<int> instead of future<compat::optional<int>>
+    // Same as above, but returning std::optional<int> instead of future<std::optional<int>>
     return do_with(int(), [] (int& counter) {
         return repeat_until_value([&counter] {
             if (counter == 10000) {
-                return compat::optional<int>(counter);
+                return std::optional<int>(counter);
             } else {
                 ++counter;
-                return compat::optional<int>(compat::nullopt);
+                return std::optional<int>(std::nullopt);
             }
         }).then([&counter] (int result) {
             BOOST_REQUIRE(counter == 10000);
@@ -873,7 +1021,7 @@ SEASTAR_TEST_CASE(test_repeat_until_value_implicit_future) {
 SEASTAR_TEST_CASE(test_repeat_until_value_exception) {
     return repeat_until_value([] {
         throw expected_exception();
-        return compat::optional<int>(43);
+        return std::optional<int>(43);
     }).then_wrapped([] (future<int> f) {
         check_fails_with_expected(std::move(f));
     });
@@ -883,16 +1031,15 @@ SEASTAR_TEST_CASE(test_when_allx) {
     return when_all(later(), later(), make_ready_future()).discard_result();
 }
 
-#if __cplusplus >= 201703L
-
 // A noncopyable and nonmovable struct
 struct non_copy_non_move {
+    non_copy_non_move() = default;
     non_copy_non_move(non_copy_non_move&&) = delete;
     non_copy_non_move(const non_copy_non_move&) = delete;
 };
 
 SEASTAR_TEST_CASE(test_when_all_functions) {
-    auto f = [x = non_copy_non_move{}] {
+    auto f = [x = non_copy_non_move()] {
         (void)x;
         return make_ready_future<int>(42);
     };
@@ -913,14 +1060,14 @@ SEASTAR_TEST_CASE(test_when_all_functions) {
 }
 
 SEASTAR_TEST_CASE(test_when_all_succeed_functions) {
-    auto f = [x = non_copy_non_move{}] {
+    auto f = [x = non_copy_non_move()] {
         (void)x;
         return make_ready_future<int>(42);
     };
     return when_all_succeed(f, [] {
         throw 42;
         return make_ready_future<>();
-    }, later()).then_wrapped([] (future<int> res) {
+    }, later()).then_wrapped([] (auto res) { // type of `res` changes when SESTAR_API_LEVEL < 3
         BOOST_REQUIRE(res.available());
         BOOST_REQUIRE(res.failed());
         res.ignore_ready_future();
@@ -928,8 +1075,6 @@ SEASTAR_TEST_CASE(test_when_all_succeed_functions) {
     });
 }
 
-#endif
-
 template<typename E, typename... T>
 static void check_failed_with(future<T...>&& f) {
     BOOST_REQUIRE(f.failed());
@@ -988,6 +1133,7 @@ SEASTAR_THREAD_TEST_CASE(test_shared_future_get_future_after_timeout) {
 
     future<> fut3 = sfut.get_future(manual_clock::now() + 1s);
     pr.set_value();
+    fut3.get();
 }
 
 SEASTAR_TEST_CASE(test_custom_exception_factory_in_with_timeout) {
@@ -1060,15 +1206,21 @@ SEASTAR_TEST_CASE(test_shared_future_with_timeout) {
     });
 }
 
+#if SEASTAR_API_LEVEL < 4
+#define THEN_UNPACK then
+#else
+#define THEN_UNPACK then_unpack
+#endif
+
 SEASTAR_TEST_CASE(test_when_all_succeed_tuples) {
     return seastar::when_all_succeed(
         make_ready_future<>(),
         make_ready_future<sstring>("hello world"),
         make_ready_future<int>(42),
         make_ready_future<>(),
-        make_ready_future<int, sstring>(84, "hi"),
+        make_ready_future<std::tuple<int, sstring>>(std::tuple(84, "hi")),
         make_ready_future<bool>(true)
-    ).then([] (sstring msg, int v, std::tuple<int, sstring> t, bool b) {
+    ).THEN_UNPACK([] (sstring msg, int v, std::tuple<int, sstring> t, bool b) {
         BOOST_REQUIRE_EQUAL(msg, "hello world");
         BOOST_REQUIRE_EQUAL(v, 42);
         BOOST_REQUIRE_EQUAL(std::get<0>(t), 84);
@@ -1080,7 +1232,7 @@ SEASTAR_TEST_CASE(test_when_all_succeed_tuples) {
                 make_ready_future<sstring>("hello world"),
                 make_exception_future<int>(43),
                 make_ready_future<>()
-        ).then([] (sstring, int) {
+        ).THEN_UNPACK([] (sstring, int) {
             BOOST_FAIL("shouldn't reach");
             return false;
         }).handle_exception([] (auto excp) {
@@ -1169,8 +1321,8 @@ SEASTAR_TEST_CASE(test_futurize_mutable) {
 }
 
 SEASTAR_THREAD_TEST_CASE(test_broken_promises) {
-    compat::optional<future<>> f;
-    compat::optional<future<>> f2;
+    std::optional<future<>> f;
+    std::optional<future<>> f2;
     { // Broken after attaching a continuation
         auto p = promise<>();
         f = p.get_future();
@@ -1203,11 +1355,263 @@ SEASTAR_THREAD_TEST_CASE(test_broken_promises) {
 
 SEASTAR_TEST_CASE(test_warn_on_broken_promise_with_no_future) {
     // Example code where we expect a "Exceptional future ignored"
-    // warning. We can't directly test that the warning is issued, but
-    // this example functions as documentation.
+    // warning.
     promise<> p;
     // Intentionally destroy the future
     (void)p.get_future();
-    p.set_exception(std::runtime_error("foo"));
+
+    with_allow_abandoned_failed_futures(1, [&] {
+        p.set_exception(std::runtime_error("foo"));
+    });
+
+    return make_ready_future<>();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_exception_future_with_backtrace) {
+    int counter = 0;
+    auto inner = [&] (bool return_exception) mutable {
+        if (!return_exception) {
+            return make_ready_future<int>(++counter);
+        } else {
+            return make_exception_future_with_backtrace<int>(expected_exception());
+        }
+    };
+    auto outer = [&] (bool return_exception) {
+        return inner(return_exception).then([] (int i) {
+            return make_ready_future<int>(-i);
+        });
+    };
+
+    BOOST_REQUIRE_EQUAL(outer(false).get0(), -1);
+    BOOST_REQUIRE_EQUAL(counter, 1);
+
+    BOOST_CHECK_THROW(outer(true).get0(), expected_exception);
+    BOOST_REQUIRE_EQUAL(counter, 1);
+
+    // Example code where we expect a "Exceptional future ignored"
+    // warning.
+    (void)outer(true).then_wrapped([](future<int> fut) {
+        with_allow_abandoned_failed_futures(1, [fut = std::move(fut)]() mutable {
+            auto foo = std::move(fut);
+        });
+    });
+}
+
+class throw_on_move {
+    int _i;
+public:
+    throw_on_move(int i = 0) noexcept {
+        _i = i;
+    }
+    throw_on_move(const throw_on_move&) = delete;
+    throw_on_move(throw_on_move&&) {
+        _i = -1;
+        throw expected_exception();
+    }
+
+    int value() const {
+        return _i;
+    }
+};
+
+SEASTAR_TEST_CASE(test_async_throw_on_move) {
+    return async([] (throw_on_move t) {
+        BOOST_CHECK(false);
+    }, throw_on_move()).handle_exception_type([] (const expected_exception&) {
+        return make_ready_future<>();
+    });
+}
+
+future<> func4() {
+    return later().then([] {
+        seastar_logger.info("backtrace: {}", current_backtrace());
+    });
+}
+
+void func3() {
+    seastar::async([] {
+        func4().get();
+    }).get();
+}
+
+future<> func2() {
+    return seastar::async([] {
+        func3();
+    });
+}
+
+future<> func1() {
+    return later().then([] {
+        return func2();
+    });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_backtracing) {
+    func1().get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_then_unpack) {
+    make_ready_future<std::tuple<>>().then_unpack([] () {
+        BOOST_REQUIRE(true);
+    }).get();
+    make_ready_future<std::tuple<int>>(std::tuple<int>(1)).then_unpack([] (int x) {
+        BOOST_REQUIRE(x == 1);
+    }).get();
+    make_ready_future<std::tuple<int, long>>(std::tuple<int, long>(1, 2)).then_unpack([] (int x, long y) {
+        BOOST_REQUIRE(x == 1 && y == 2);
+    }).get();
+    make_ready_future<std::tuple<std::unique_ptr<int>>>(std::tuple(std::make_unique<int>(42))).then_unpack([] (std::unique_ptr<int> p1) {
+        BOOST_REQUIRE(*p1 == 42);
+    }).get();
+}
+
+future<> test_then_function_f() {
     return make_ready_future<>();
 }
+
+SEASTAR_TEST_CASE(test_then_function) {
+    return make_ready_future<>().then(test_then_function_f);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_with_gate) {
+    gate g;
+    int counter = 0;
+    int gate_closed_errors = 0;
+    int other_errors = 0;
+
+    // test normal operation when gate is opened
+    BOOST_CHECK_NO_THROW(with_gate(g, [&] { counter++; }).get());
+    BOOST_REQUIRE_EQUAL(counter, 1);
+
+    // test that an exception returned by the calling func
+    // is propagated to with_gate future
+    counter = gate_closed_errors = other_errors = 0;
+    BOOST_CHECK_NO_THROW(with_gate(g, [&] {
+            counter++;
+            return make_exception_future<>(expected_exception());
+        }).handle_exception_type([&] (gate_closed_exception& e) {
+            gate_closed_errors++;
+        }).handle_exception([&] (std::exception_ptr) {
+            other_errors++;
+        }).get());
+    BOOST_REQUIRE(counter);
+    BOOST_REQUIRE(!gate_closed_errors);
+    BOOST_REQUIRE(other_errors);
+
+    g.close().get();
+
+    // test that with_gate.get() throws when the gate is closed
+    counter = gate_closed_errors = other_errors = 0;
+    BOOST_CHECK_THROW(with_gate(g, [&] { counter++; }).get(), gate_closed_exception);
+    BOOST_REQUIRE(!counter);
+
+    // test that with_gate throws when the gate is closed
+    counter = gate_closed_errors = other_errors = 0;
+    BOOST_CHECK_THROW(with_gate(g, [&] {
+            counter++;
+        }).then_wrapped([&] (future<> f) {
+            auto eptr = f.get_exception();
+            try {
+                std::rethrow_exception(eptr);
+            } catch (gate_closed_exception& e) {
+                gate_closed_errors++;
+            } catch (...) {
+                other_errors++;
+            }
+        }).get(), gate_closed_exception);
+    BOOST_REQUIRE(!counter);
+    BOOST_REQUIRE(!gate_closed_errors);
+    BOOST_REQUIRE(!other_errors);
+
+    // test that try_with_gate returns gate_closed_exception when the gate is closed
+    counter = gate_closed_errors = other_errors = 0;
+    try_with_gate(g, [&] { counter++; }).handle_exception_type([&] (gate_closed_exception& e) {
+        gate_closed_errors++;
+    }).handle_exception([&] (std::exception_ptr) {
+        other_errors++;
+    }).get();
+    BOOST_REQUIRE(!counter);
+    BOOST_REQUIRE(gate_closed_errors);
+    BOOST_REQUIRE(!other_errors);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_max_concurrent_for_each) {
+    BOOST_TEST_MESSAGE("empty range");
+    max_concurrent_for_each(std::vector<int>(), 3, [] (int) {
+        BOOST_FAIL("should not reach");
+        return make_exception_future<>(std::bad_function_call());
+    }).get();
+
+    auto range = boost::copy_range<std::vector<int>>(boost::irange(1, 8));
+
+    BOOST_TEST_MESSAGE("iterator");
+    auto sum = 0;
+    max_concurrent_for_each(range.begin(), range.end(), 3,  [&sum] (int v) {
+        sum += v;
+        return make_ready_future<>();
+    }).get();
+    BOOST_REQUIRE_EQUAL(sum, 28);
+
+    BOOST_TEST_MESSAGE("const iterator");
+    sum = 0;
+    max_concurrent_for_each(range.cbegin(), range.cend(), 3,  [&sum] (int v) {
+        sum += v;
+        return make_ready_future<>();
+    }).get();
+    BOOST_REQUIRE_EQUAL(sum, 28);
+
+    BOOST_TEST_MESSAGE("reverse iterator");
+    sum = 0;
+    max_concurrent_for_each(range.rbegin(), range.rend(), 3,  [&sum] (int v) {
+        sum += v;
+        return make_ready_future<>();
+    }).get();
+    BOOST_REQUIRE_EQUAL(sum, 28);
+
+    BOOST_TEST_MESSAGE("immediate result");
+    sum = 0;
+    max_concurrent_for_each(range, 3,  [&sum] (int v) {
+        sum += v;
+        return make_ready_future<>();
+    }).get();
+    BOOST_REQUIRE_EQUAL(sum, 28);
+
+    BOOST_TEST_MESSAGE("suspend");
+    sum = 0;
+    max_concurrent_for_each(range, 3, [&sum] (int v) {
+        return later().then([&sum, v] {
+            sum += v;
+        });
+    }).get();
+    BOOST_REQUIRE_EQUAL(sum, 28);
+
+    BOOST_TEST_MESSAGE("throw immediately");
+    sum = 0;
+    BOOST_CHECK_EXCEPTION(max_concurrent_for_each(range, 3, [&sum] (int v) {
+        sum += v;
+        if (v == 1) {
+            throw 5;
+        }
+        return make_ready_future<>();
+    }).get(), int, [] (int v) { return v == 5; });
+    BOOST_REQUIRE_EQUAL(sum, 28);
+
+    BOOST_TEST_MESSAGE("throw after suspension");
+    sum = 0;
+    BOOST_CHECK_EXCEPTION(max_concurrent_for_each(range, 3, [&sum] (int v) {
+        return later().then([&sum, v] {
+            sum += v;
+            if (v == 2) {
+                throw 5;
+            }
+        });
+    }).get(), int, [] (int v) { return v == 5; });
+
+    BOOST_TEST_MESSAGE("concurrency higher than vector length");
+    sum = 0;
+    max_concurrent_for_each(range, range.size() + 3,  [&sum] (int v) {
+        sum += v;
+        return make_ready_future<>();
+    }).get();
+    BOOST_REQUIRE_EQUAL(sum, 28);
+}