2 * This file is open source software, licensed to you under the terms
3 * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4 * distributed with this work for additional information regarding copyright
5 * ownership. You may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing,
12 * software distributed under the License is distributed on an
13 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 * KIND, either express or implied. See the License for the
15 * specific language governing permissions and limitations
19 * Copyright (C) 2019 ScyllaDB Ltd.
22 #include <seastar/core/future-util.hh>
23 #include <seastar/testing/test_case.hh>
24 #include <seastar/core/sleep.hh>
26 using namespace seastar
;
27 using namespace std::chrono_literals
;
29 #ifndef SEASTAR_COROUTINES_ENABLED
31 SEASTAR_TEST_CASE(test_coroutines_not_compiled_in
) {
32 return make_ready_future
<>();
37 #include <seastar/core/coroutine.hh>
38 #include <seastar/coroutine/all.hh>
39 #include <seastar/coroutine/maybe_yield.hh>
43 future
<int> old_fashioned_continuations() {
44 return later().then([] {
49 future
<int> simple_coroutine() {
54 future
<int> ready_coroutine() {
58 future
<std::tuple
<int, double>> tuple_coroutine() {
59 co_return
std::tuple(1, 2.);
62 future
<int> failing_coroutine() {
67 [[gnu::noinline
]] int throw_exception(int x
) {
71 future
<int> failing_coroutine2() noexcept
{
73 co_return
throw_exception(17);
78 SEASTAR_TEST_CASE(test_simple_coroutines
) {
79 BOOST_REQUIRE_EQUAL(co_await
old_fashioned_continuations(), 42);
80 BOOST_REQUIRE_EQUAL(co_await
simple_coroutine(), 53);
81 BOOST_REQUIRE_EQUAL(ready_coroutine().get0(), 64);
82 BOOST_REQUIRE(co_await
tuple_coroutine() == std::tuple(1, 2.));
83 BOOST_REQUIRE_EXCEPTION((void)co_await
failing_coroutine(), int, [] (auto v
) { return v
== 42; });
84 BOOST_CHECK_EQUAL(co_await
failing_coroutine().then_wrapped([] (future
<int> f
) -> future
<int> {
85 BOOST_REQUIRE(f
.failed());
87 std::rethrow_exception(f
.get_exception());
92 BOOST_REQUIRE_EXCEPTION((void)co_await
failing_coroutine2(), int, [] (auto v
) { return v
== 17; });
93 BOOST_CHECK_EQUAL(co_await
failing_coroutine2().then_wrapped([] (future
<int> f
) -> future
<int> {
94 BOOST_REQUIRE(f
.failed());
96 std::rethrow_exception(f
.get_exception());
103 SEASTAR_TEST_CASE(test_abandond_coroutine
) {
104 std::optional
<future
<int>> f
;
106 auto p1
= promise
<>();
107 auto p2
= promise
<>();
108 auto p3
= promise
<>();
109 f
= p1
.get_future().then([&] () -> future
<int> {
111 BOOST_CHECK_THROW(co_await p3
.get_future(), broken_promise
);
115 co_await p2
.get_future();
117 BOOST_CHECK_EQUAL(co_await
std::move(*f
), 1);
120 SEASTAR_TEST_CASE(test_scheduling_group
) {
121 auto other_sg
= co_await
create_scheduling_group("the other group", 10.f
);
123 auto p1
= promise
<>();
124 auto p2
= promise
<>();
126 auto p1b
= promise
<>();
127 auto p2b
= promise
<>();
128 auto f1
= p1b
.get_future();
129 auto f2
= p2b
.get_future();
131 BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
132 auto f_ret
= with_scheduling_group(other_sg
,
133 [other_sg_cap
= other_sg
] (future
<> f1
, future
<> f2
, promise
<> p1
, promise
<> p2
) -> future
<int> {
134 // Make a copy in the coroutine before the lambda is destroyed.
135 auto other_sg
= other_sg_cap
;
136 BOOST_REQUIRE(current_scheduling_group() == other_sg
);
137 BOOST_REQUIRE(other_sg
== other_sg
);
139 co_await
std::move(f1
);
140 BOOST_REQUIRE(current_scheduling_group() == other_sg
);
142 co_await
std::move(f2
);
143 BOOST_REQUIRE(current_scheduling_group() == other_sg
);
145 }, p1
.get_future(), p2
.get_future(), std::move(p1b
), std::move(p2b
));
147 co_await
std::move(f1
);
148 BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
150 co_await
std::move(f2
);
151 BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
153 BOOST_REQUIRE_EQUAL(co_await
std::move(f_ret
), 42);
154 BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
157 SEASTAR_TEST_CASE(test_preemption
) {
159 unsigned preempted
= 0;
160 auto f
= later().then([&x
] {
164 // try to preempt 1000 times. 1 should be enough if not for
165 // task queue shaffling in debug mode which may cause co-routine
166 // continuation to run first.
167 while(preempted
< 1000 && !x
) {
168 preempted
+= need_preempt();
169 co_await make_ready_future
<>();
172 // wait for later() to complete
173 co_await
std::move(f
);
174 BOOST_REQUIRE(save_x
);
178 SEASTAR_TEST_CASE(test_all_simple
) {
179 auto [a
, b
] = co_await
coroutine::all(
180 [] { return make_ready_future
<int>(1); },
181 [] { return make_ready_future
<int>(2); }
183 BOOST_REQUIRE_EQUAL(a
, 1);
184 BOOST_REQUIRE_EQUAL(b
, 2);
187 SEASTAR_TEST_CASE(test_all_permutations
) {
188 std::vector
<std::chrono::milliseconds
> delays
= { 0ms
, 0ms
, 2ms
, 2ms
, 4ms
, 6ms
};
189 auto make_delayed_future_returning_nr
= [&] (int nr
) {
191 auto delay
= delays
[nr
];
192 return delay
== 0ms
? make_ready_future
<int>(nr
) : sleep(delay
).then([nr
] { return make_ready_future
<int>(nr
); });
196 auto [a
, b
, c
, d
, e
, f
] = co_await
coroutine::all(
197 make_delayed_future_returning_nr(0),
198 make_delayed_future_returning_nr(1),
199 make_delayed_future_returning_nr(2),
200 make_delayed_future_returning_nr(3),
201 make_delayed_future_returning_nr(4),
202 make_delayed_future_returning_nr(5)
204 BOOST_REQUIRE_EQUAL(a
, 0);
205 BOOST_REQUIRE_EQUAL(b
, 1);
206 BOOST_REQUIRE_EQUAL(c
, 2);
207 BOOST_REQUIRE_EQUAL(d
, 3);
208 BOOST_REQUIRE_EQUAL(e
, 4);
209 BOOST_REQUIRE_EQUAL(f
, 5);
210 } while (std::ranges::next_permutation(delays
).found
);
213 SEASTAR_TEST_CASE(test_all_ready_exceptions
) {
215 co_await
coroutine::all(
216 [] () -> future
<> { throw 1; },
217 [] () -> future
<> { throw 2; }
220 BOOST_REQUIRE(e
== 1 || e
== 2);
224 SEASTAR_TEST_CASE(test_all_nonready_exceptions
) {
226 co_await
coroutine::all(
237 BOOST_REQUIRE(e
== 1 || e
== 2);
241 SEASTAR_TEST_CASE(test_all_heterogeneous_types
) {
242 auto [a
, b
] = co_await
coroutine::all(
243 [] () -> future
<int> {
250 [] () -> future
<long> {
255 BOOST_REQUIRE_EQUAL(a
, 1);
256 BOOST_REQUIRE_EQUAL(b
, 2L);
259 SEASTAR_TEST_CASE(test_all_noncopyable_types
) {
260 auto [a
] = co_await
coroutine::all(
261 [] () -> future
<std::unique_ptr
<int>> {
262 co_return
std::make_unique
<int>(6);
265 BOOST_REQUIRE_EQUAL(*a
, 6);
268 SEASTAR_TEST_CASE(test_all_throw_in_input_func
) {
269 int nr_completed
= 0;
270 bool exception_seen
= false;
272 co_await
coroutine::all(
273 [&] () -> future
<int> {
278 [&] () -> future
<int> {
281 [&] () -> future
<int> {
288 BOOST_REQUIRE_EQUAL(n
, 9);
289 exception_seen
= true;
291 BOOST_REQUIRE_EQUAL(nr_completed
, 2);
292 BOOST_REQUIRE(exception_seen
);
295 SEASTAR_TEST_CASE(test_coroutine_exception
) {
296 auto i_am_exceptional
= [] () -> future
<int> {
297 co_return
coroutine::exception(std::make_exception_ptr(std::runtime_error("threw")));
299 BOOST_REQUIRE_THROW(co_await
i_am_exceptional(), std::runtime_error
);
300 co_await
i_am_exceptional().then_wrapped([] (future
<int> f
) {
301 BOOST_REQUIRE(f
.failed());
302 BOOST_REQUIRE_THROW(std::rethrow_exception(f
.get_exception()), std::runtime_error
);
305 auto i_am_exceptional_as_well
= [] () -> future
<bool> {
306 co_return
coroutine::make_exception(std::logic_error("threw"));
308 BOOST_REQUIRE_THROW(co_await
i_am_exceptional_as_well(), std::logic_error
);
309 co_await
i_am_exceptional_as_well().then_wrapped([] (future
<bool> f
) {
310 BOOST_REQUIRE(f
.failed());
311 BOOST_REQUIRE_THROW(std::rethrow_exception(f
.get_exception()), std::logic_error
);
315 SEASTAR_TEST_CASE(test_maybe_yield
) {
318 auto spinner
= [&] () -> future
<> {
319 // increment a variable continuously, but yield so an observer can see it.
322 co_await
coroutine::maybe_yield();
325 auto spinner_fut
= spinner();
327 for (int nr_changes
= 0; nr_changes
< 10; ++nr_changes
) {
328 // Try to observe the value changing in time, yield to
329 // allow the spinner to advance it.
330 while (snapshot
== var
) {
331 co_await
coroutine::maybe_yield();
336 co_await
std::move(spinner_fut
);
337 BOOST_REQUIRE(true); // the test will hang if it doesn't work.