]> git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/tests/unit/closeable_test.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / seastar / tests / unit / closeable_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 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
59 SEASTAR_TEST_CASE(close_now_test) {
60 return do_with(gate(), 0, 42, [] (gate& g, int& count, int& expected) {
61 return async([&] {
62 auto close_gate = deferred_close(g);
63
64 for (auto i = 0; i < expected; i++) {
65 (void)with_gate(g, [&count] {
66 ++count;
67 });
68 }
69
70 close_gate.close_now();
71 BOOST_REQUIRE(g.is_closed());
72 BOOST_REQUIRE_EQUAL(count, expected);
73 // gate must not be double-closed.
74 });
75 });
76 }
77
78 SEASTAR_TEST_CASE(with_closeable_test) {
79 return do_with(0, 42, [] (int& count, int& expected) {
80 return with_closeable(gate(), [&] (gate& g) {
81 for (auto i = 0; i < expected; i++) {
82 (void)with_gate(g, [&count] {
83 ++count;
84 });
85 }
86 return 17;
87 }).then([&] (int res) {
88 // res should be returned by the function called
89 // by with_closeable.
90 BOOST_REQUIRE_EQUAL(res, 17);
91 // closing the gate should wait for
92 // all background continuations to complete
93 BOOST_REQUIRE_EQUAL(count, expected);
94 });
95 });
96 }
97
98 SEASTAR_TEST_CASE(with_closeable_exception_test) {
99 return do_with(0, 42, [] (int& count, int& expected) {
100 return with_closeable(gate(), [&] (gate& g) {
101 for (auto i = 0; i < expected; i++) {
102 (void)with_gate(g, [&count] {
103 ++count;
104 });
105 }
106 throw expected_exception();
107 }).handle_exception_type([&] (const expected_exception&) {
108 // closing the gate should also happen when func throws,
109 // waiting for all background continuations to complete
110 BOOST_REQUIRE_EQUAL(count, expected);
111 });
112 });
113 }
114
115 namespace {
116
117 class count_stops {
118 int _count = -1;
119 int* _ptr = nullptr;
120 public:
121 count_stops(int* ptr = nullptr) noexcept
122 : _ptr(ptr ? ptr : &_count)
123 {
124 *_ptr = 0;
125 }
126
127 count_stops(count_stops&& o) noexcept {
128 std::exchange(_count, o._count);
129 if (o._ptr == &o._count) {
130 _ptr = &_count;
131 } else {
132 std::exchange(_ptr, o._ptr);
133 }
134 }
135
136 future<> stop() noexcept {
137 ++*_ptr;
138 return make_ready_future<>();
139 }
140
141 int stopped() const noexcept {
142 return *_ptr;
143 }
144 };
145
146 } // anonymous namespace
147
148 SEASTAR_TEST_CASE(deferred_stop_test) {
149 return do_with(count_stops(), [] (count_stops& cs) {
150 return async([&] {
151 auto stop_counting = deferred_stop(cs);
152 }).then([&] {
153 // cs.stop() should be called when stop_counting is destroyed
154 BOOST_REQUIRE_EQUAL(cs.stopped(), 1);
155 });
156 });
157 }
158
159 SEASTAR_TEST_CASE(stop_now_test) {
160 return do_with(count_stops(), [] (count_stops& cs) {
161 return async([&] {
162 auto stop_counting = deferred_stop(cs);
163
164 stop_counting.stop_now();
165 // cs.stop() should not be called again
166 // when stop_counting is destroyed
167 BOOST_REQUIRE_EQUAL(cs.stopped(), 1);
168 }).then([&] {
169 // cs.stop() should be called exactly once
170 BOOST_REQUIRE_EQUAL(cs.stopped(), 1);
171 });
172 });
173 }
174
175 SEASTAR_TEST_CASE(with_stoppable_test) {
176 return do_with(0, [] (int& stopped) {
177 return with_stoppable(count_stops(&stopped), [] (count_stops& cs) {
178 return 17;
179 }).then([&] (int res) {
180 // res should be returned by the function called
181 // by with_closeable.
182 BOOST_REQUIRE_EQUAL(res, 17);
183 // cs.stop() should be called before count_stops is destroyed
184 BOOST_REQUIRE_EQUAL(stopped, 1);
185 });
186 });
187 }
188
189 SEASTAR_TEST_CASE(with_stoppable_exception_test) {
190 return do_with(0, [] (int& stopped) {
191 return with_stoppable(count_stops(&stopped), [] (count_stops& cs) {
192 throw expected_exception();
193 }).handle_exception_type([&] (const expected_exception&) {
194 // cs.stop() should be called before count_stops is destroyed
195 // also when func throws
196 BOOST_REQUIRE_EQUAL(stopped, 1);
197 });
198 });
199 }
200
201 SEASTAR_THREAD_TEST_CASE(gate_holder_basic_test) {
202 gate g;
203 auto gh = g.hold();
204 auto fut = g.close();
205 BOOST_CHECK(!fut.available());
206 gh.release();
207 fut.get();
208 }
209
210 SEASTAR_THREAD_TEST_CASE(gate_holder_closed_test) {
211 gate g;
212 g.close().get();
213 BOOST_REQUIRE_THROW(g.hold(), gate_closed_exception);
214 }
215
216 SEASTAR_THREAD_TEST_CASE(gate_holder_move_test) {
217 gate g;
218 auto gh0 = g.hold();
219 auto fut = g.close();
220 BOOST_CHECK(!fut.available());
221 auto gh1 = std::move(gh0);
222 BOOST_CHECK(!fut.available());
223 gh1.release();
224 fut.get();
225 }
226
227 SEASTAR_THREAD_TEST_CASE(gate_holder_copy_test) {
228 gate g;
229 auto gh0 = g.hold();
230 auto gh1 = gh0;
231 auto fut = g.close();
232 BOOST_CHECK(!fut.available());
233 gh0.release();
234 BOOST_CHECK(!fut.available());
235 gh1.release();
236 fut.get();
237 }
238
239 SEASTAR_THREAD_TEST_CASE(gate_holder_copy_and_move_test) {
240 gate g0;
241 auto gh00 = g0.hold();
242 auto gh01 = gh00;
243 auto fut0 = g0.close();
244 BOOST_CHECK(!fut0.available());
245 gate g1;
246 auto gh1 = g1.hold();
247 auto fut1 = g1.close();
248 BOOST_CHECK(!fut1.available());
249 gh01.release();
250 BOOST_CHECK(!fut0.available());
251 BOOST_CHECK(!fut1.available());
252 gh00 = std::move(gh1);
253 fut0.get();
254 BOOST_CHECK(!fut1.available());
255 gh00.release();
256 fut1.get();
257 }
258
259 SEASTAR_THREAD_TEST_CASE(gate_holder_copy_after_close_test) {
260 gate g;
261 auto gh0 = g.hold();
262 auto fut = g.close();
263 BOOST_CHECK(g.is_closed());
264 gate::holder gh1 = gh0;
265 BOOST_CHECK(!fut.available());
266 gh0.release();
267 BOOST_CHECK(!fut.available());
268 gh1.release();
269 fut.get();
270 }
271
272 SEASTAR_TEST_CASE(gate_holder_parallel_copy_test) {
273 constexpr int expected = 42;
274 return do_with(0, [expected] (int& count) {
275 return with_closeable(gate(), [&] (gate& g) {
276 auto gh = g.hold();
277 // Copying the gate::holder in the lambda below should keep it open
278 // until all instances complete
279 (void)parallel_for_each(boost::irange(0, expected), [&count, gh = gh] (int) {
280 count++;
281 return make_ready_future<>();
282 });
283 return 17;
284 }).then([&, expected] (int res) {
285 // res should be returned by the function called
286 // by with_closeable.
287 BOOST_REQUIRE_EQUAL(res, 17);
288 // closing the gate should wait for
289 // all background continuations to complete
290 BOOST_REQUIRE_EQUAL(count, expected);
291 });
292 });
293 }