]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2018 Red Hat, Inc. | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include "rgw/rgw_aio_throttle.h" | |
11fdf7f2 | 16 | |
9f95a23c TL |
17 | #include <optional> |
18 | #include <thread> | |
19 | #include "include/scope_guard.h" | |
11fdf7f2 | 20 | |
9f95a23c | 21 | #include <spawn/spawn.hpp> |
11fdf7f2 TL |
22 | #include <gtest/gtest.h> |
23 | ||
24 | struct RadosEnv : public ::testing::Environment { | |
25 | public: | |
26 | static constexpr auto poolname = "ceph_test_rgw_throttle"; | |
27 | ||
28 | static std::optional<RGWSI_RADOS> rados; | |
29 | ||
30 | void SetUp() override { | |
31 | rados.emplace(g_ceph_context); | |
b3b6e05e TL |
32 | const NoDoutPrefix no_dpp(g_ceph_context, 1); |
33 | ASSERT_EQ(0, rados->start(null_yield, &no_dpp)); | |
11fdf7f2 TL |
34 | int r = rados->pool({poolname}).create(); |
35 | if (r == -EEXIST) | |
36 | r = 0; | |
37 | ASSERT_EQ(0, r); | |
38 | } | |
9f95a23c TL |
39 | void TearDown() override { |
40 | rados->shutdown(); | |
11fdf7f2 TL |
41 | rados.reset(); |
42 | } | |
43 | }; | |
44 | std::optional<RGWSI_RADOS> RadosEnv::rados; | |
45 | ||
46 | auto *const rados_env = ::testing::AddGlobalTestEnvironment(new RadosEnv); | |
47 | ||
48 | // test fixture for global setup/teardown | |
49 | class RadosFixture : public ::testing::Test { | |
50 | protected: | |
51 | RGWSI_RADOS::Obj make_obj(const std::string& oid) { | |
52 | auto obj = RadosEnv::rados->obj({{RadosEnv::poolname}, oid}); | |
b3b6e05e TL |
53 | const NoDoutPrefix no_dpp(g_ceph_context, 1); |
54 | ceph_assert_always(0 == obj.open(&no_dpp)); | |
11fdf7f2 TL |
55 | return obj; |
56 | } | |
57 | }; | |
58 | ||
59 | using Aio_Throttle = RadosFixture; | |
60 | ||
61 | namespace rgw { | |
62 | ||
9f95a23c TL |
63 | struct scoped_completion { |
64 | Aio* aio = nullptr; | |
65 | AioResult* result = nullptr; | |
66 | ~scoped_completion() { if (aio) { complete(-ECANCELED); } } | |
67 | void complete(int r) { | |
68 | result->result = r; | |
69 | aio->put(*result); | |
70 | aio = nullptr; | |
71 | } | |
72 | }; | |
73 | ||
74 | auto wait_on(scoped_completion& c) { | |
75 | return [&c] (Aio* aio, AioResult& r) { c.aio = aio; c.result = &r; }; | |
76 | } | |
77 | ||
78 | auto wait_for(boost::asio::io_context& context, ceph::timespan duration) { | |
79 | return [&context, duration] (Aio* aio, AioResult& r) { | |
80 | using Clock = ceph::coarse_mono_clock; | |
81 | using Timer = boost::asio::basic_waitable_timer<Clock>; | |
82 | auto t = std::make_unique<Timer>(context); | |
83 | t->expires_after(duration); | |
84 | t->async_wait([aio, &r, t=std::move(t)] (boost::system::error_code ec) { | |
85 | if (ec != boost::asio::error::operation_aborted) { | |
86 | aio->put(r); | |
87 | } | |
88 | }); | |
89 | }; | |
90 | } | |
91 | ||
11fdf7f2 TL |
92 | TEST_F(Aio_Throttle, NoThrottleUpToMax) |
93 | { | |
9f95a23c | 94 | BlockingAioThrottle throttle(4); |
11fdf7f2 TL |
95 | auto obj = make_obj(__PRETTY_FUNCTION__); |
96 | { | |
9f95a23c TL |
97 | scoped_completion op1; |
98 | auto c1 = throttle.get(obj, wait_on(op1), 1, 0); | |
11fdf7f2 | 99 | EXPECT_TRUE(c1.empty()); |
9f95a23c TL |
100 | scoped_completion op2; |
101 | auto c2 = throttle.get(obj, wait_on(op2), 1, 0); | |
11fdf7f2 | 102 | EXPECT_TRUE(c2.empty()); |
9f95a23c TL |
103 | scoped_completion op3; |
104 | auto c3 = throttle.get(obj, wait_on(op3), 1, 0); | |
11fdf7f2 | 105 | EXPECT_TRUE(c3.empty()); |
9f95a23c TL |
106 | scoped_completion op4; |
107 | auto c4 = throttle.get(obj, wait_on(op4), 1, 0); | |
11fdf7f2 TL |
108 | EXPECT_TRUE(c4.empty()); |
109 | // no completions because no ops had to wait | |
110 | auto c5 = throttle.poll(); | |
9f95a23c | 111 | EXPECT_TRUE(c5.empty()); |
11fdf7f2 TL |
112 | } |
113 | auto completions = throttle.drain(); | |
114 | ASSERT_EQ(4u, completions.size()); | |
115 | for (auto& c : completions) { | |
9f95a23c | 116 | EXPECT_EQ(-ECANCELED, c.result); |
11fdf7f2 TL |
117 | } |
118 | } | |
119 | ||
120 | TEST_F(Aio_Throttle, CostOverWindow) | |
121 | { | |
9f95a23c | 122 | BlockingAioThrottle throttle(4); |
11fdf7f2 TL |
123 | auto obj = make_obj(__PRETTY_FUNCTION__); |
124 | ||
9f95a23c TL |
125 | scoped_completion op; |
126 | auto c = throttle.get(obj, wait_on(op), 8, 0); | |
11fdf7f2 TL |
127 | ASSERT_EQ(1u, c.size()); |
128 | EXPECT_EQ(-EDEADLK, c.front().result); | |
129 | } | |
130 | ||
131 | TEST_F(Aio_Throttle, ThrottleOverMax) | |
132 | { | |
133 | constexpr uint64_t window = 4; | |
9f95a23c | 134 | BlockingAioThrottle throttle(window); |
11fdf7f2 TL |
135 | |
136 | auto obj = make_obj(__PRETTY_FUNCTION__); | |
137 | ||
138 | // issue 32 writes, and verify that max_outstanding <= window | |
139 | constexpr uint64_t total = 32; | |
140 | uint64_t max_outstanding = 0; | |
141 | uint64_t outstanding = 0; | |
142 | ||
9f95a23c TL |
143 | // timer thread |
144 | boost::asio::io_context context; | |
145 | using Executor = boost::asio::io_context::executor_type; | |
146 | using Work = boost::asio::executor_work_guard<Executor>; | |
147 | std::optional<Work> work(context.get_executor()); | |
148 | std::thread worker([&context] { context.run(); }); | |
149 | auto g = make_scope_guard([&work, &worker] { | |
150 | work.reset(); | |
151 | worker.join(); | |
152 | }); | |
153 | ||
11fdf7f2 | 154 | for (uint64_t i = 0; i < total; i++) { |
9f95a23c TL |
155 | using namespace std::chrono_literals; |
156 | auto c = throttle.get(obj, wait_for(context, 10ms), 1, 0); | |
11fdf7f2 TL |
157 | outstanding++; |
158 | outstanding -= c.size(); | |
159 | if (max_outstanding < outstanding) { | |
160 | max_outstanding = outstanding; | |
161 | } | |
162 | } | |
163 | auto c = throttle.drain(); | |
164 | outstanding -= c.size(); | |
165 | EXPECT_EQ(0u, outstanding); | |
166 | EXPECT_EQ(window, max_outstanding); | |
167 | } | |
168 | ||
9f95a23c TL |
169 | TEST_F(Aio_Throttle, YieldCostOverWindow) |
170 | { | |
171 | auto obj = make_obj(__PRETTY_FUNCTION__); | |
172 | ||
173 | boost::asio::io_context context; | |
174 | spawn::spawn(context, | |
175 | [&] (spawn::yield_context yield) { | |
176 | YieldingAioThrottle throttle(4, context, yield); | |
177 | scoped_completion op; | |
178 | auto c = throttle.get(obj, wait_on(op), 8, 0); | |
179 | ASSERT_EQ(1u, c.size()); | |
180 | EXPECT_EQ(-EDEADLK, c.front().result); | |
181 | }); | |
182 | } | |
183 | ||
184 | TEST_F(Aio_Throttle, YieldingThrottleOverMax) | |
185 | { | |
186 | constexpr uint64_t window = 4; | |
187 | ||
188 | auto obj = make_obj(__PRETTY_FUNCTION__); | |
189 | ||
190 | // issue 32 writes, and verify that max_outstanding <= window | |
191 | constexpr uint64_t total = 32; | |
192 | uint64_t max_outstanding = 0; | |
193 | uint64_t outstanding = 0; | |
194 | ||
195 | boost::asio::io_context context; | |
196 | spawn::spawn(context, | |
197 | [&] (spawn::yield_context yield) { | |
198 | YieldingAioThrottle throttle(window, context, yield); | |
199 | for (uint64_t i = 0; i < total; i++) { | |
200 | using namespace std::chrono_literals; | |
201 | auto c = throttle.get(obj, wait_for(context, 10ms), 1, 0); | |
202 | outstanding++; | |
203 | outstanding -= c.size(); | |
204 | if (max_outstanding < outstanding) { | |
205 | max_outstanding = outstanding; | |
206 | } | |
207 | } | |
208 | auto c = throttle.drain(); | |
209 | outstanding -= c.size(); | |
210 | }); | |
211 | context.poll(); // run until we block | |
212 | EXPECT_EQ(window, outstanding); | |
213 | ||
214 | context.run(); | |
215 | EXPECT_EQ(0u, outstanding); | |
216 | EXPECT_EQ(window, max_outstanding); | |
217 | } | |
9f95a23c | 218 | |
11fdf7f2 | 219 | } // namespace rgw |