]>
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 | /* | |
20 | * Copyright (C) 2021 ScyllaDB | |
21 | */ | |
22 | ||
23 | #include <seastar/core/thread.hh> | |
24 | #include <seastar/testing/test_case.hh> | |
25 | #include <seastar/testing/thread_test_case.hh> | |
26 | #include <seastar/testing/test_runner.hh> | |
27 | #include <seastar/core/reactor.hh> | |
28 | #include <seastar/core/smp.hh> | |
29 | #include <seastar/core/when_all.hh> | |
30 | #include <seastar/core/file.hh> | |
31 | #include <seastar/core/io_queue.hh> | |
32 | #include <seastar/core/io_intent.hh> | |
33 | #include <seastar/core/internal/io_request.hh> | |
34 | #include <seastar/core/internal/io_sink.hh> | |
35 | ||
36 | using namespace seastar; | |
37 | ||
38 | template <size_t Len> | |
39 | struct fake_file { | |
40 | int data[Len] = {}; | |
41 | ||
42 | static internal::io_request make_write_req(size_t idx, int* buf) { | |
43 | return internal::io_request::make_write(0, idx, buf, 1, false); | |
44 | } | |
45 | ||
46 | void execute_write_req(internal::io_request& rq, io_completion* desc) { | |
47 | data[rq.pos()] = *(reinterpret_cast<int*>(rq.address())); | |
48 | desc->complete_with(rq.size()); | |
49 | } | |
50 | }; | |
51 | ||
52 | struct io_queue_for_tests { | |
53 | io_group_ptr group; | |
54 | internal::io_sink sink; | |
55 | io_queue queue; | |
56 | ||
57 | io_queue_for_tests() | |
58 | : group(std::make_shared<io_group>(io_queue::config{0})) | |
59 | , sink() | |
60 | , queue(group, sink) | |
61 | {} | |
62 | }; | |
63 | ||
64 | SEASTAR_THREAD_TEST_CASE(test_basic_flow) { | |
65 | io_queue_for_tests tio; | |
66 | fake_file<1> file; | |
67 | ||
68 | auto val = std::make_unique<int>(42); | |
69 | auto f = tio.queue.queue_request(default_priority_class(), 0, file.make_write_req(0, val.get()), nullptr) | |
70 | .then([&file] (size_t len) { | |
71 | BOOST_REQUIRE(file.data[0] == 42); | |
72 | }); | |
73 | ||
74 | tio.queue.poll_io_queue(); | |
75 | tio.sink.drain([&file] (internal::io_request& rq, io_completion* desc) -> bool { | |
76 | file.execute_write_req(rq, desc); | |
77 | return true; | |
78 | }); | |
79 | ||
80 | f.get(); | |
81 | } | |
82 | ||
83 | SEASTAR_THREAD_TEST_CASE(test_intent_safe_ref) { | |
84 | auto get_cancelled = [] (internal::intent_reference& iref) -> bool { | |
85 | try { | |
86 | iref.retrieve(); | |
87 | return false; | |
88 | } catch(seastar::cancelled_error& err) { | |
89 | return true; | |
90 | } | |
91 | }; | |
92 | ||
93 | io_intent intent, intent_x; | |
94 | ||
95 | internal::intent_reference ref_orig(&intent); | |
96 | BOOST_REQUIRE(ref_orig.retrieve() == &intent); | |
97 | ||
98 | // Test move armed | |
99 | internal::intent_reference ref_armed(std::move(ref_orig)); | |
100 | BOOST_REQUIRE(ref_orig.retrieve() == nullptr); | |
101 | BOOST_REQUIRE(ref_armed.retrieve() == &intent); | |
102 | ||
103 | internal::intent_reference ref_armed_2(&intent_x); | |
104 | ref_armed_2 = std::move(ref_armed); | |
105 | BOOST_REQUIRE(ref_armed.retrieve() == nullptr); | |
106 | BOOST_REQUIRE(ref_armed_2.retrieve() == &intent); | |
107 | ||
108 | intent.cancel(); | |
109 | BOOST_REQUIRE(get_cancelled(ref_armed_2)); | |
110 | ||
111 | // Test move cancelled | |
112 | internal::intent_reference ref_cancelled(std::move(ref_armed_2)); | |
113 | BOOST_REQUIRE(ref_armed_2.retrieve() == nullptr); | |
114 | BOOST_REQUIRE(get_cancelled(ref_cancelled)); | |
115 | ||
116 | internal::intent_reference ref_cancelled_2(&intent_x); | |
117 | ref_cancelled_2 = std::move(ref_cancelled); | |
118 | BOOST_REQUIRE(ref_cancelled.retrieve() == nullptr); | |
119 | BOOST_REQUIRE(get_cancelled(ref_cancelled_2)); | |
120 | ||
121 | // Test move empty | |
122 | internal::intent_reference ref_empty(std::move(ref_orig)); | |
123 | BOOST_REQUIRE(ref_empty.retrieve() == nullptr); | |
124 | ||
125 | internal::intent_reference ref_empty_2(&intent_x); | |
126 | ref_empty_2 = std::move(ref_empty); | |
127 | BOOST_REQUIRE(ref_empty_2.retrieve() == nullptr); | |
128 | } | |
129 | ||
130 | static constexpr int nr_requests = 24; | |
131 | ||
132 | SEASTAR_THREAD_TEST_CASE(test_io_cancellation) { | |
133 | fake_file<nr_requests> file; | |
134 | ||
135 | io_queue_for_tests tio; | |
136 | io_priority_class pc0 = io_priority_class::register_one("a", 100); | |
137 | io_priority_class pc1 = io_priority_class::register_one("b", 100); | |
138 | ||
139 | size_t idx = 0; | |
140 | int val = 100; | |
141 | ||
142 | io_intent live, dead; | |
143 | ||
144 | std::vector<future<>> finished; | |
145 | std::vector<future<>> cancelled; | |
146 | ||
147 | auto queue_legacy_request = [&] (io_queue_for_tests& q, io_priority_class& pc) { | |
148 | auto buf = std::make_unique<int>(val); | |
149 | auto f = q.queue.queue_request(pc, 0, file.make_write_req(idx, buf.get()), nullptr) | |
150 | .then([&file, idx, val, buf = std::move(buf)] (size_t len) { | |
151 | BOOST_REQUIRE(file.data[idx] == val); | |
152 | return make_ready_future<>(); | |
153 | }); | |
154 | finished.push_back(std::move(f)); | |
155 | idx++; | |
156 | val++; | |
157 | }; | |
158 | ||
159 | auto queue_live_request = [&] (io_queue_for_tests& q, io_priority_class& pc) { | |
160 | auto buf = std::make_unique<int>(val); | |
161 | auto f = q.queue.queue_request(pc, 0, file.make_write_req(idx, buf.get()), &live) | |
162 | .then([&file, idx, val, buf = std::move(buf)] (size_t len) { | |
163 | BOOST_REQUIRE(file.data[idx] == val); | |
164 | return make_ready_future<>(); | |
165 | }); | |
166 | finished.push_back(std::move(f)); | |
167 | idx++; | |
168 | val++; | |
169 | }; | |
170 | ||
171 | auto queue_dead_request = [&] (io_queue_for_tests& q, io_priority_class& pc) { | |
172 | auto buf = std::make_unique<int>(val); | |
173 | auto f = q.queue.queue_request(pc, 0, file.make_write_req(idx, buf.get()), &dead) | |
174 | .then_wrapped([buf = std::move(buf)] (auto&& f) { | |
175 | try { | |
176 | f.get(); | |
177 | BOOST_REQUIRE(false); | |
178 | } catch(...) {} | |
179 | return make_ready_future<>(); | |
180 | }) | |
181 | .then([&file, idx] () { | |
182 | BOOST_REQUIRE(file.data[idx] == 0); | |
183 | }); | |
184 | cancelled.push_back(std::move(f)); | |
185 | idx++; | |
186 | val++; | |
187 | }; | |
188 | ||
189 | auto seed = std::random_device{}(); | |
190 | std::default_random_engine reng(seed); | |
191 | std::uniform_int_distribution<> dice(0, 5); | |
192 | ||
193 | for (int i = 0; i < nr_requests; i++) { | |
194 | int pc = dice(reng) % 2; | |
195 | if (dice(reng) < 3) { | |
196 | fmt::print("queue live req to pc {}\n", pc); | |
197 | queue_live_request(tio, pc == 0 ? pc0 : pc1); | |
198 | } else if (dice(reng) < 5) { | |
199 | fmt::print("queue dead req to pc {}\n", pc); | |
200 | queue_dead_request(tio, pc == 0 ? pc0 : pc1); | |
201 | } else { | |
202 | fmt::print("queue legacy req to pc {}\n", pc); | |
203 | queue_legacy_request(tio, pc == 0 ? pc0 : pc1); | |
204 | } | |
205 | } | |
206 | ||
207 | dead.cancel(); | |
208 | ||
209 | // cancelled requests must resolve right at once | |
210 | ||
211 | when_all_succeed(cancelled.begin(), cancelled.end()).get(); | |
212 | ||
213 | tio.queue.poll_io_queue(); | |
214 | tio.sink.drain([&file] (internal::io_request& rq, io_completion* desc) -> bool { | |
215 | file.execute_write_req(rq, desc); | |
216 | return true; | |
217 | }); | |
218 | ||
219 | when_all_succeed(finished.begin(), finished.end()).get(); | |
220 | } |