]> git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/tests/unit/coroutines_test.cc
733a4670938f114c0f77beff4a784d0c053e9506
[ceph.git] / ceph / src / seastar / tests / unit / coroutines_test.cc
1 /*
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.
6 *
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
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
16 * under the License.
17 */
18 /*
19 * Copyright (C) 2019 ScyllaDB Ltd.
20 */
21
22 #include <seastar/core/future-util.hh>
23 #include <seastar/testing/test_case.hh>
24 #include <seastar/core/sleep.hh>
25
26 using namespace seastar;
27 using namespace std::chrono_literals;
28
29 #ifndef SEASTAR_COROUTINES_ENABLED
30
31 SEASTAR_TEST_CASE(test_coroutines_not_compiled_in) {
32 return make_ready_future<>();
33 }
34
35 #else
36
37 #include <seastar/core/coroutine.hh>
38 #include <seastar/coroutine/all.hh>
39 #include <seastar/coroutine/maybe_yield.hh>
40
41 namespace {
42
43 future<int> old_fashioned_continuations() {
44 return later().then([] {
45 return 42;
46 });
47 }
48
49 future<int> simple_coroutine() {
50 co_await later();
51 co_return 53;
52 }
53
54 future<int> ready_coroutine() {
55 co_return 64;
56 }
57
58 future<std::tuple<int, double>> tuple_coroutine() {
59 co_return std::tuple(1, 2.);
60 }
61
62 future<int> failing_coroutine() {
63 co_await later();
64 throw 42;
65 }
66
67 [[gnu::noinline]] int throw_exception(int x) {
68 throw x;
69 }
70
71 future<int> failing_coroutine2() noexcept {
72 co_await later();
73 co_return throw_exception(17);
74 }
75
76 }
77
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());
86 try {
87 std::rethrow_exception(f.get_exception());
88 } catch (int v) {
89 co_return v;
90 }
91 }), 42);
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());
95 try {
96 std::rethrow_exception(f.get_exception());
97 } catch (int v) {
98 co_return v;
99 }
100 }), 17);
101 }
102
103 SEASTAR_TEST_CASE(test_abandond_coroutine) {
104 std::optional<future<int>> f;
105 {
106 auto p1 = promise<>();
107 auto p2 = promise<>();
108 auto p3 = promise<>();
109 f = p1.get_future().then([&] () -> future<int> {
110 p2.set_value();
111 BOOST_CHECK_THROW(co_await p3.get_future(), broken_promise);
112 co_return 1;
113 });
114 p1.set_value();
115 co_await p2.get_future();
116 }
117 BOOST_CHECK_EQUAL(co_await std::move(*f), 1);
118 }
119
120 SEASTAR_TEST_CASE(test_scheduling_group) {
121 auto other_sg = co_await create_scheduling_group("the other group", 10.f);
122
123 auto p1 = promise<>();
124 auto p2 = promise<>();
125
126 auto p1b = promise<>();
127 auto p2b = promise<>();
128 auto f1 = p1b.get_future();
129 auto f2 = p2b.get_future();
130
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);
138 p1.set_value();
139 co_await std::move(f1);
140 BOOST_REQUIRE(current_scheduling_group() == other_sg);
141 p2.set_value();
142 co_await std::move(f2);
143 BOOST_REQUIRE(current_scheduling_group() == other_sg);
144 co_return 42;
145 }, p1.get_future(), p2.get_future(), std::move(p1b), std::move(p2b));
146
147 co_await std::move(f1);
148 BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
149 p1.set_value();
150 co_await std::move(f2);
151 BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
152 p2.set_value();
153 BOOST_REQUIRE_EQUAL(co_await std::move(f_ret), 42);
154 BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
155 }
156
157 SEASTAR_TEST_CASE(test_preemption) {
158 bool x = false;
159 unsigned preempted = 0;
160 auto f = later().then([&x] {
161 x = true;
162 });
163
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<>();
170 }
171 auto save_x = x;
172 // wait for later() to complete
173 co_await std::move(f);
174 BOOST_REQUIRE(save_x);
175 co_return;
176 }
177
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); }
182 );
183 BOOST_REQUIRE_EQUAL(a, 1);
184 BOOST_REQUIRE_EQUAL(b, 2);
185 }
186
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) {
190 return [=] {
191 auto delay = delays[nr];
192 return delay == 0ms ? make_ready_future<int>(nr) : sleep(delay).then([nr] { return make_ready_future<int>(nr); });
193 };
194 };
195 do {
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)
203 );
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);
211 }
212
213 SEASTAR_TEST_CASE(test_all_ready_exceptions) {
214 try {
215 co_await coroutine::all(
216 [] () -> future<> { throw 1; },
217 [] () -> future<> { throw 2; }
218 );
219 } catch (int e) {
220 BOOST_REQUIRE(e == 1 || e == 2);
221 }
222 }
223
224 SEASTAR_TEST_CASE(test_all_nonready_exceptions) {
225 try {
226 co_await coroutine::all(
227 [] () -> future<> {
228 co_await sleep(1ms);
229 throw 1;
230 },
231 [] () -> future<> {
232 co_await sleep(1ms);
233 throw 2;
234 }
235 );
236 } catch (int e) {
237 BOOST_REQUIRE(e == 1 || e == 2);
238 }
239 }
240
241 SEASTAR_TEST_CASE(test_all_heterogeneous_types) {
242 auto [a, b] = co_await coroutine::all(
243 [] () -> future<int> {
244 co_await sleep(1ms);
245 co_return 1;
246 },
247 [] () -> future<> {
248 co_await sleep(1ms);
249 },
250 [] () -> future<long> {
251 co_await sleep(1ms);
252 co_return 2L;
253 }
254 );
255 BOOST_REQUIRE_EQUAL(a, 1);
256 BOOST_REQUIRE_EQUAL(b, 2L);
257 }
258
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);
263 }
264 );
265 BOOST_REQUIRE_EQUAL(*a, 6);
266 }
267
268 SEASTAR_TEST_CASE(test_all_throw_in_input_func) {
269 int nr_completed = 0;
270 bool exception_seen = false;
271 try {
272 co_await coroutine::all(
273 [&] () -> future<int> {
274 co_await sleep(1ms);
275 ++nr_completed;
276 co_return 7;
277 },
278 [&] () -> future<int> {
279 throw 9;
280 },
281 [&] () -> future<int> {
282 co_await sleep(1ms);
283 ++nr_completed;
284 co_return 7;
285 }
286 );
287 } catch (int n) {
288 BOOST_REQUIRE_EQUAL(n, 9);
289 exception_seen = true;
290 }
291 BOOST_REQUIRE_EQUAL(nr_completed, 2);
292 BOOST_REQUIRE(exception_seen);
293 }
294
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")));
298 };
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);
303 });
304
305 auto i_am_exceptional_as_well = [] () -> future<bool> {
306 co_return coroutine::make_exception(std::logic_error("threw"));
307 };
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);
312 });
313 }
314
315 SEASTAR_TEST_CASE(test_maybe_yield) {
316 int var = 0;
317 bool done = false;
318 auto spinner = [&] () -> future<> {
319 // increment a variable continuously, but yield so an observer can see it.
320 while (!done) {
321 ++var;
322 co_await coroutine::maybe_yield();
323 }
324 };
325 auto spinner_fut = spinner();
326 int snapshot = var;
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();
332 }
333 snapshot = var;
334 }
335 done = true;
336 co_await std::move(spinner_fut);
337 BOOST_REQUIRE(true); // the test will hang if it doesn't work.
338 }
339
340 #endif