]>
Commit | Line | Data |
---|---|---|
20effc67 TL |
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 2021 ScyllaDB | |
20 | */ | |
21 | ||
22 | #include <exception> | |
23 | ||
24 | #include <boost/range/irange.hpp> | |
25 | ||
26 | #include <seastar/testing/test_case.hh> | |
27 | #include <seastar/testing/thread_test_case.hh> | |
28 | ||
29 | #include <seastar/core/gate.hh> | |
30 | #include <seastar/util/closeable.hh> | |
31 | #include <seastar/core/loop.hh> | |
32 | ||
33 | using namespace seastar; | |
34 | ||
35 | class expected_exception : public std::runtime_error { | |
36 | public: | |
37 | expected_exception() : runtime_error("expected") {} | |
38 | }; | |
39 | ||
40 | SEASTAR_TEST_CASE(deferred_close_test) { | |
41 | return do_with(gate(), 0, 42, [] (gate& g, int& count, int& expected) { | |
42 | return async([&] { | |
43 | auto close_gate = deferred_close(g); | |
44 | ||
45 | for (auto i = 0; i < expected; i++) { | |
46 | (void)with_gate(g, [&count] { | |
47 | ++count; | |
48 | }); | |
49 | } | |
50 | }).then([&] { | |
51 | // destroying close_gate should invoke g.close() | |
52 | // and wait for all background continuations to complete | |
53 | BOOST_REQUIRE(g.is_closed()); | |
54 | BOOST_REQUIRE_EQUAL(count, expected); | |
55 | }); | |
56 | }); | |
57 | } | |
58 | ||
1e59de90 TL |
59 | SEASTAR_TEST_CASE(move_deferred_close_test) { |
60 | return do_with(gate(), [] (gate& g) { | |
61 | return async([&] { | |
62 | auto close_gate = make_shared(deferred_close(g)); | |
63 | // g.close() should not be called when deferred_close is moved away | |
64 | BOOST_REQUIRE(!g.is_closed()); | |
65 | }).then([&] { | |
66 | // Before this test is exercised, gate::close() would run into a | |
67 | // assert failure when leaving previous continuation, if gate::close() | |
68 | // is called twice, so this test only verifies the behavior with the | |
69 | // release build. | |
70 | BOOST_REQUIRE(g.is_closed()); | |
71 | }); | |
72 | }); | |
73 | } | |
74 | ||
20effc67 TL |
75 | SEASTAR_TEST_CASE(close_now_test) { |
76 | return do_with(gate(), 0, 42, [] (gate& g, int& count, int& expected) { | |
77 | return async([&] { | |
78 | auto close_gate = deferred_close(g); | |
79 | ||
80 | for (auto i = 0; i < expected; i++) { | |
81 | (void)with_gate(g, [&count] { | |
82 | ++count; | |
83 | }); | |
84 | } | |
85 | ||
86 | close_gate.close_now(); | |
87 | BOOST_REQUIRE(g.is_closed()); | |
88 | BOOST_REQUIRE_EQUAL(count, expected); | |
89 | // gate must not be double-closed. | |
90 | }); | |
91 | }); | |
92 | } | |
93 | ||
1e59de90 TL |
94 | SEASTAR_TEST_CASE(cancel_deferred_close_test) { |
95 | gate g; | |
96 | { | |
97 | auto close_gate = deferred_close(g); | |
98 | close_gate.cancel(); | |
99 | } | |
100 | g.check(); // should not throw | |
101 | return make_ready_future<>(); | |
102 | } | |
103 | ||
20effc67 TL |
104 | SEASTAR_TEST_CASE(with_closeable_test) { |
105 | return do_with(0, 42, [] (int& count, int& expected) { | |
106 | return with_closeable(gate(), [&] (gate& g) { | |
107 | for (auto i = 0; i < expected; i++) { | |
108 | (void)with_gate(g, [&count] { | |
109 | ++count; | |
110 | }); | |
111 | } | |
112 | return 17; | |
113 | }).then([&] (int res) { | |
114 | // res should be returned by the function called | |
115 | // by with_closeable. | |
116 | BOOST_REQUIRE_EQUAL(res, 17); | |
117 | // closing the gate should wait for | |
118 | // all background continuations to complete | |
119 | BOOST_REQUIRE_EQUAL(count, expected); | |
120 | }); | |
121 | }); | |
122 | } | |
123 | ||
124 | SEASTAR_TEST_CASE(with_closeable_exception_test) { | |
125 | return do_with(0, 42, [] (int& count, int& expected) { | |
126 | return with_closeable(gate(), [&] (gate& g) { | |
127 | for (auto i = 0; i < expected; i++) { | |
128 | (void)with_gate(g, [&count] { | |
129 | ++count; | |
130 | }); | |
131 | } | |
132 | throw expected_exception(); | |
133 | }).handle_exception_type([&] (const expected_exception&) { | |
134 | // closing the gate should also happen when func throws, | |
135 | // waiting for all background continuations to complete | |
136 | BOOST_REQUIRE_EQUAL(count, expected); | |
137 | }); | |
138 | }); | |
139 | } | |
140 | ||
141 | namespace { | |
142 | ||
143 | class count_stops { | |
144 | int _count = -1; | |
145 | int* _ptr = nullptr; | |
146 | public: | |
147 | count_stops(int* ptr = nullptr) noexcept | |
148 | : _ptr(ptr ? ptr : &_count) | |
149 | { | |
150 | *_ptr = 0; | |
151 | } | |
152 | ||
153 | count_stops(count_stops&& o) noexcept { | |
154 | std::exchange(_count, o._count); | |
155 | if (o._ptr == &o._count) { | |
156 | _ptr = &_count; | |
157 | } else { | |
158 | std::exchange(_ptr, o._ptr); | |
159 | } | |
160 | } | |
161 | ||
162 | future<> stop() noexcept { | |
163 | ++*_ptr; | |
164 | return make_ready_future<>(); | |
165 | } | |
166 | ||
167 | int stopped() const noexcept { | |
168 | return *_ptr; | |
169 | } | |
170 | }; | |
171 | ||
172 | } // anonymous namespace | |
173 | ||
1e59de90 TL |
174 | SEASTAR_TEST_CASE(cancel_deferred_stop_test) { |
175 | count_stops cs; | |
176 | { | |
177 | auto stop = deferred_stop(cs); | |
178 | stop.cancel(); | |
179 | } | |
180 | BOOST_REQUIRE_EQUAL(cs.stopped(), 0); | |
181 | return make_ready_future<>(); | |
182 | } | |
183 | ||
20effc67 TL |
184 | SEASTAR_TEST_CASE(deferred_stop_test) { |
185 | return do_with(count_stops(), [] (count_stops& cs) { | |
186 | return async([&] { | |
187 | auto stop_counting = deferred_stop(cs); | |
188 | }).then([&] { | |
189 | // cs.stop() should be called when stop_counting is destroyed | |
190 | BOOST_REQUIRE_EQUAL(cs.stopped(), 1); | |
191 | }); | |
192 | }); | |
193 | } | |
194 | ||
1e59de90 TL |
195 | SEASTAR_TEST_CASE(move_deferred_stop_test) { |
196 | return do_with(count_stops(), [] (count_stops& cs) { | |
197 | return async([&] { | |
198 | auto stop = make_shared(deferred_stop(cs)); | |
199 | }).then([&] { | |
200 | // cs.stop() should be called once and only once | |
201 | // when stop is destroyed | |
202 | BOOST_REQUIRE_EQUAL(cs.stopped(), 1); | |
203 | }); | |
204 | }); | |
205 | } | |
206 | ||
20effc67 TL |
207 | SEASTAR_TEST_CASE(stop_now_test) { |
208 | return do_with(count_stops(), [] (count_stops& cs) { | |
209 | return async([&] { | |
210 | auto stop_counting = deferred_stop(cs); | |
211 | ||
212 | stop_counting.stop_now(); | |
213 | // cs.stop() should not be called again | |
214 | // when stop_counting is destroyed | |
215 | BOOST_REQUIRE_EQUAL(cs.stopped(), 1); | |
216 | }).then([&] { | |
217 | // cs.stop() should be called exactly once | |
218 | BOOST_REQUIRE_EQUAL(cs.stopped(), 1); | |
219 | }); | |
220 | }); | |
221 | } | |
222 | ||
223 | SEASTAR_TEST_CASE(with_stoppable_test) { | |
224 | return do_with(0, [] (int& stopped) { | |
225 | return with_stoppable(count_stops(&stopped), [] (count_stops& cs) { | |
226 | return 17; | |
227 | }).then([&] (int res) { | |
228 | // res should be returned by the function called | |
229 | // by with_closeable. | |
230 | BOOST_REQUIRE_EQUAL(res, 17); | |
231 | // cs.stop() should be called before count_stops is destroyed | |
232 | BOOST_REQUIRE_EQUAL(stopped, 1); | |
233 | }); | |
234 | }); | |
235 | } | |
236 | ||
237 | SEASTAR_TEST_CASE(with_stoppable_exception_test) { | |
238 | return do_with(0, [] (int& stopped) { | |
239 | return with_stoppable(count_stops(&stopped), [] (count_stops& cs) { | |
240 | throw expected_exception(); | |
241 | }).handle_exception_type([&] (const expected_exception&) { | |
242 | // cs.stop() should be called before count_stops is destroyed | |
243 | // also when func throws | |
244 | BOOST_REQUIRE_EQUAL(stopped, 1); | |
245 | }); | |
246 | }); | |
247 | } | |
248 | ||
1e59de90 TL |
249 | SEASTAR_THREAD_TEST_CASE(move_open_gate_test) { |
250 | gate g1; | |
251 | g1.enter(); | |
252 | // move an open gate | |
253 | gate g2 = std::move(g1); | |
254 | // the state in g1 should be moved into g2 | |
255 | BOOST_CHECK_EQUAL(g1.get_count(), 0); | |
256 | BOOST_REQUIRE_EQUAL(g2.get_count(), 1); | |
257 | g2.leave(); | |
258 | g2.close().get(); | |
259 | BOOST_CHECK(!g1.is_closed()); | |
260 | BOOST_CHECK(g2.is_closed()); | |
261 | } | |
262 | ||
263 | SEASTAR_THREAD_TEST_CASE(move_closing_gate_test) { | |
264 | gate g1; | |
265 | g1.enter(); | |
266 | auto fut = g1.close(); | |
267 | // move a closing gate | |
268 | gate g2 = std::move(g1); | |
269 | BOOST_CHECK_EQUAL(g1.get_count(), 0); | |
270 | BOOST_REQUIRE_EQUAL(g2.get_count(), 1); | |
271 | g2.leave(); | |
272 | fut.get(); | |
273 | BOOST_CHECK(!g1.is_closed()); | |
274 | BOOST_CHECK(g2.is_closed()); | |
275 | } | |
276 | ||
277 | SEASTAR_THREAD_TEST_CASE(move_closed_gate_test) { | |
278 | gate g1; | |
279 | g1.close().get(); | |
280 | // move a closed gate | |
281 | gate g2 = std::move(g1); | |
282 | BOOST_CHECK_EQUAL(g1.get_count(), 0); | |
283 | BOOST_CHECK_EQUAL(g2.get_count(), 0); | |
284 | BOOST_CHECK(!g1.is_closed()); | |
285 | BOOST_CHECK(g2.is_closed()); | |
286 | } | |
287 | ||
20effc67 TL |
288 | SEASTAR_THREAD_TEST_CASE(gate_holder_basic_test) { |
289 | gate g; | |
290 | auto gh = g.hold(); | |
291 | auto fut = g.close(); | |
292 | BOOST_CHECK(!fut.available()); | |
293 | gh.release(); | |
294 | fut.get(); | |
295 | } | |
296 | ||
297 | SEASTAR_THREAD_TEST_CASE(gate_holder_closed_test) { | |
298 | gate g; | |
299 | g.close().get(); | |
300 | BOOST_REQUIRE_THROW(g.hold(), gate_closed_exception); | |
301 | } | |
302 | ||
303 | SEASTAR_THREAD_TEST_CASE(gate_holder_move_test) { | |
304 | gate g; | |
305 | auto gh0 = g.hold(); | |
306 | auto fut = g.close(); | |
307 | BOOST_CHECK(!fut.available()); | |
308 | auto gh1 = std::move(gh0); | |
309 | BOOST_CHECK(!fut.available()); | |
310 | gh1.release(); | |
311 | fut.get(); | |
312 | } | |
313 | ||
314 | SEASTAR_THREAD_TEST_CASE(gate_holder_copy_test) { | |
315 | gate g; | |
316 | auto gh0 = g.hold(); | |
317 | auto gh1 = gh0; | |
318 | auto fut = g.close(); | |
319 | BOOST_CHECK(!fut.available()); | |
320 | gh0.release(); | |
321 | BOOST_CHECK(!fut.available()); | |
322 | gh1.release(); | |
323 | fut.get(); | |
324 | } | |
325 | ||
326 | SEASTAR_THREAD_TEST_CASE(gate_holder_copy_and_move_test) { | |
327 | gate g0; | |
328 | auto gh00 = g0.hold(); | |
329 | auto gh01 = gh00; | |
330 | auto fut0 = g0.close(); | |
331 | BOOST_CHECK(!fut0.available()); | |
332 | gate g1; | |
333 | auto gh1 = g1.hold(); | |
334 | auto fut1 = g1.close(); | |
335 | BOOST_CHECK(!fut1.available()); | |
336 | gh01.release(); | |
337 | BOOST_CHECK(!fut0.available()); | |
338 | BOOST_CHECK(!fut1.available()); | |
339 | gh00 = std::move(gh1); | |
340 | fut0.get(); | |
341 | BOOST_CHECK(!fut1.available()); | |
342 | gh00.release(); | |
343 | fut1.get(); | |
344 | } | |
345 | ||
346 | SEASTAR_THREAD_TEST_CASE(gate_holder_copy_after_close_test) { | |
347 | gate g; | |
348 | auto gh0 = g.hold(); | |
349 | auto fut = g.close(); | |
350 | BOOST_CHECK(g.is_closed()); | |
351 | gate::holder gh1 = gh0; | |
352 | BOOST_CHECK(!fut.available()); | |
353 | gh0.release(); | |
354 | BOOST_CHECK(!fut.available()); | |
355 | gh1.release(); | |
356 | fut.get(); | |
357 | } | |
358 | ||
359 | SEASTAR_TEST_CASE(gate_holder_parallel_copy_test) { | |
360 | constexpr int expected = 42; | |
361 | return do_with(0, [expected] (int& count) { | |
362 | return with_closeable(gate(), [&] (gate& g) { | |
363 | auto gh = g.hold(); | |
364 | // Copying the gate::holder in the lambda below should keep it open | |
365 | // until all instances complete | |
366 | (void)parallel_for_each(boost::irange(0, expected), [&count, gh = gh] (int) { | |
367 | count++; | |
368 | return make_ready_future<>(); | |
369 | }); | |
370 | return 17; | |
371 | }).then([&, expected] (int res) { | |
372 | // res should be returned by the function called | |
373 | // by with_closeable. | |
374 | BOOST_REQUIRE_EQUAL(res, 17); | |
375 | // closing the gate should wait for | |
376 | // all background continuations to complete | |
377 | BOOST_REQUIRE_EQUAL(count, expected); | |
378 | }); | |
379 | }); | |
380 | } |