1 //
2 //
3 // Copyright 2017 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
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, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18
19 #include "src/core/lib/resource_quota/arena.h"
20
21 #include <grpc/support/sync.h>
22 #include <grpc/support/time.h>
23 #include <inttypes.h>
24 #include <string.h>
25
26 #include <algorithm>
27 #include <iosfwd>
28 #include <memory>
29 #include <ostream>
30 #include <string>
31 #include <vector>
32
33 #include "absl/strings/str_join.h"
34 #include "gmock/gmock.h"
35 #include "gtest/gtest.h"
36 #include "src/core/lib/iomgr/exec_ctx.h"
37 #include "src/core/lib/resource_quota/resource_quota.h"
38 #include "src/core/util/ref_counted_ptr.h"
39 #include "src/core/util/thd.h"
40 #include "test/core/test_util/test_config.h"
41
42 using testing::Mock;
43 using testing::Return;
44 using testing::StrictMock;
45
46 namespace grpc_core {
47
48 struct AllocShape {
49 size_t initial_size;
50 std::vector<size_t> allocs;
51 };
52
operator <<(std::ostream & out,const AllocShape & shape)53 std::ostream& operator<<(std::ostream& out, const AllocShape& shape) {
54 out << "AllocShape{initial_size=" << shape.initial_size
55 << ", allocs=" << absl::StrJoin(shape.allocs, ",") << "}";
56 return out;
57 }
58
59 class AllocTest : public ::testing::TestWithParam<AllocShape> {};
60
TEST_P(AllocTest,Works)61 TEST_P(AllocTest, Works) {
62 ExecCtx exec_ctx;
63 RefCountedPtr<Arena> a =
64 SimpleArenaAllocator(GetParam().initial_size)->MakeArena();
65 std::vector<void*> allocated;
66 for (auto alloc : GetParam().allocs) {
67 void* p = a->Alloc(alloc);
68 // ensure the returned address is aligned
69 EXPECT_EQ(((intptr_t)p & 0xf), 0);
70 // ensure no duplicate results
71 for (auto other_p : allocated) {
72 EXPECT_NE(p, other_p);
73 }
74 // ensure writable
75 memset(p, 1, alloc);
76 allocated.push_back(p);
77 }
78 }
79
80 INSTANTIATE_TEST_SUITE_P(
81 AllocTests, AllocTest,
82 ::testing::Values(AllocShape{0, {1}}, AllocShape{1, {1}},
83 AllocShape{1, {2}}, AllocShape{1, {3}},
84 AllocShape{1, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
85 AllocShape{6, {1, 2, 3}}));
86
87 class MockMemoryAllocatorImpl
88 : public grpc_event_engine::experimental::internal::MemoryAllocatorImpl {
89 public:
90 MOCK_METHOD(size_t, Reserve, (MemoryRequest));
91 MOCK_METHOD(grpc_slice, MakeSlice, (MemoryRequest));
92 MOCK_METHOD(void, Release, (size_t));
93 MOCK_METHOD(void, Shutdown, ());
94 };
95
TEST(ArenaTest,InitialReservationCorrect)96 TEST(ArenaTest, InitialReservationCorrect) {
97 auto allocator_impl = std::make_shared<StrictMock<MockMemoryAllocatorImpl>>();
98 auto allocator = SimpleArenaAllocator(1024, MemoryAllocator(allocator_impl));
99 EXPECT_CALL(*allocator_impl, Reserve(MemoryRequest(1024, 1024)))
100 .WillOnce(Return(1024));
101 auto arena = allocator->MakeArena();
102 Mock::VerifyAndClearExpectations(allocator_impl.get());
103 EXPECT_CALL(*allocator_impl, Release(1024));
104 arena.reset();
105 Mock::VerifyAndClearExpectations(allocator_impl.get());
106 EXPECT_CALL(*allocator_impl, Shutdown());
107 }
108
TEST(ArenaTest,SubsequentReservationCorrect)109 TEST(ArenaTest, SubsequentReservationCorrect) {
110 auto allocator_impl = std::make_shared<StrictMock<MockMemoryAllocatorImpl>>();
111 auto allocator = SimpleArenaAllocator(1024, MemoryAllocator(allocator_impl));
112 EXPECT_CALL(*allocator_impl, Reserve(MemoryRequest(1024, 1024)))
113 .WillOnce(Return(1024));
114 auto arena = allocator->MakeArena();
115 Mock::VerifyAndClearExpectations(allocator_impl.get());
116 EXPECT_CALL(*allocator_impl,
117 Reserve(MemoryRequest(4096 + Arena::ArenaZoneOverhead(),
118 4096 + Arena::ArenaZoneOverhead())))
119 .WillOnce(Return(4096 + Arena::ArenaZoneOverhead()));
120 arena->Alloc(4096);
121 Mock::VerifyAndClearExpectations(allocator_impl.get());
122 EXPECT_CALL(*allocator_impl,
123 Release(1024 + 4096 + Arena::ArenaZoneOverhead()));
124 arena.reset();
125 Mock::VerifyAndClearExpectations(allocator_impl.get());
126 EXPECT_CALL(*allocator_impl, Shutdown());
127 }
128
129 #define CONCURRENT_TEST_THREADS 10
130
concurrent_test_iterations()131 size_t concurrent_test_iterations() {
132 if (sizeof(void*) < 8) return 1000;
133 return 100000;
134 }
135
136 typedef struct {
137 gpr_event ev_start;
138 RefCountedPtr<Arena> arena;
139 } concurrent_test_args;
140
TEST(ArenaTest,NoOp)141 TEST(ArenaTest, NoOp) { SimpleArenaAllocator()->MakeArena(); }
142
TEST(ArenaTest,ManagedNew)143 TEST(ArenaTest, ManagedNew) {
144 ExecCtx exec_ctx;
145 auto arena = SimpleArenaAllocator(1)->MakeArena();
146 for (int i = 0; i < 100; i++) {
147 arena->ManagedNew<std::unique_ptr<int>>(std::make_unique<int>(i));
148 }
149 }
150
TEST(ArenaTest,ConcurrentAlloc)151 TEST(ArenaTest, ConcurrentAlloc) {
152 concurrent_test_args args;
153 gpr_event_init(&args.ev_start);
154 args.arena = SimpleArenaAllocator()->MakeArena();
155
156 Thread thds[CONCURRENT_TEST_THREADS];
157
158 for (int i = 0; i < CONCURRENT_TEST_THREADS; i++) {
159 thds[i] = Thread(
160 "grpc_concurrent_test",
161 [](void* arg) {
162 concurrent_test_args* a = static_cast<concurrent_test_args*>(arg);
163 gpr_event_wait(&a->ev_start, gpr_inf_future(GPR_CLOCK_REALTIME));
164 for (size_t i = 0; i < concurrent_test_iterations(); i++) {
165 *static_cast<char*>(a->arena->Alloc(1)) = static_cast<char>(i);
166 }
167 },
168 &args);
169 thds[i].Start();
170 }
171
172 gpr_event_set(&args.ev_start, reinterpret_cast<void*>(1));
173
174 for (auto& th : thds) {
175 th.Join();
176 }
177 }
178
TEST(ArenaTest,ConcurrentManagedNew)179 TEST(ArenaTest, ConcurrentManagedNew) {
180 concurrent_test_args args;
181 gpr_event_init(&args.ev_start);
182 args.arena = SimpleArenaAllocator()->MakeArena();
183
184 Thread thds[CONCURRENT_TEST_THREADS];
185
186 for (int i = 0; i < CONCURRENT_TEST_THREADS; i++) {
187 thds[i] = Thread(
188 "grpc_concurrent_test",
189 [](void* arg) {
190 concurrent_test_args* a = static_cast<concurrent_test_args*>(arg);
191 gpr_event_wait(&a->ev_start, gpr_inf_future(GPR_CLOCK_REALTIME));
192 for (size_t i = 0; i < concurrent_test_iterations(); i++) {
193 a->arena->ManagedNew<std::unique_ptr<int>>(
194 std::make_unique<int>(static_cast<int>(i)));
195 }
196 },
197 &args);
198 thds[i].Start();
199 }
200
201 gpr_event_set(&args.ev_start, reinterpret_cast<void*>(1));
202
203 for (auto& th : thds) {
204 th.Join();
205 }
206 }
207
208 template <typename Int>
Scribble(Int * ints,int n,int offset)209 void Scribble(Int* ints, int n, int offset) {
210 for (int i = 0; i < n; i++) {
211 ints[i] = static_cast<Int>(i + offset);
212 }
213 }
214
215 template <typename Int>
IsScribbled(Int * ints,int n,int offset)216 bool IsScribbled(Int* ints, int n, int offset) {
217 for (int i = 0; i < n; i++) {
218 if (ints[i] != static_cast<Int>(i + offset)) return false;
219 }
220 return true;
221 }
222
TEST(ArenaTest,CreateManyObjects)223 TEST(ArenaTest, CreateManyObjects) {
224 struct TestObj {
225 char a[100];
226 };
227 auto arena = SimpleArenaAllocator()->MakeArena();
228 std::vector<Arena::PoolPtr<TestObj>> objs;
229 objs.reserve(1000);
230 for (int i = 0; i < 1000; i++) {
231 objs.emplace_back(arena->MakePooled<TestObj>());
232 Scribble(objs.back()->a, 100, i);
233 }
234 for (int i = 0; i < 1000; i++) {
235 EXPECT_TRUE(IsScribbled(objs[i]->a, 100, i));
236 }
237 }
238
TEST(ArenaTest,CreateManyObjectsWithDestructors)239 TEST(ArenaTest, CreateManyObjectsWithDestructors) {
240 using TestObj = std::unique_ptr<int>;
241 auto arena = SimpleArenaAllocator()->MakeArena();
242 std::vector<Arena::PoolPtr<TestObj>> objs;
243 objs.reserve(1000);
244 for (int i = 0; i < 1000; i++) {
245 objs.emplace_back(arena->MakePooled<TestObj>(new int(i)));
246 }
247 }
248
TEST(ArenaTest,CreatePoolArray)249 TEST(ArenaTest, CreatePoolArray) {
250 auto arena = SimpleArenaAllocator()->MakeArena();
251 auto p = arena->MakePooledArray<int>(1024);
252 EXPECT_TRUE(p.get_deleter().has_freelist());
253 p = arena->MakePooledArray<int>(5);
254 EXPECT_TRUE(p.get_deleter().has_freelist());
255 Scribble(p.get(), 5, 1);
256 EXPECT_TRUE(IsScribbled(p.get(), 5, 1));
257 }
258
TEST(ArenaTest,ConcurrentMakePooled)259 TEST(ArenaTest, ConcurrentMakePooled) {
260 concurrent_test_args args;
261 gpr_event_init(&args.ev_start);
262 args.arena = SimpleArenaAllocator()->MakeArena();
263
264 class BaseClass {
265 public:
266 virtual ~BaseClass() {}
267 virtual int Foo() = 0;
268 };
269
270 class Type1 : public BaseClass {
271 public:
272 int Foo() override { return 1; }
273 };
274
275 class Type2 : public BaseClass {
276 public:
277 int Foo() override { return 2; }
278 };
279
280 Thread thds1[CONCURRENT_TEST_THREADS / 2];
281 Thread thds2[CONCURRENT_TEST_THREADS / 2];
282
283 for (int i = 0; i < CONCURRENT_TEST_THREADS / 2; i++) {
284 thds1[i] = Thread(
285 "grpc_concurrent_test",
286 [](void* arg) {
287 concurrent_test_args* a = static_cast<concurrent_test_args*>(arg);
288 gpr_event_wait(&a->ev_start, gpr_inf_future(GPR_CLOCK_REALTIME));
289 for (size_t i = 0; i < concurrent_test_iterations(); i++) {
290 EXPECT_EQ(a->arena->MakePooled<Type1>()->Foo(), 1);
291 }
292 },
293 &args);
294 thds1[i].Start();
295
296 thds2[i] = Thread(
297 "grpc_concurrent_test",
298 [](void* arg) {
299 concurrent_test_args* a = static_cast<concurrent_test_args*>(arg);
300 gpr_event_wait(&a->ev_start, gpr_inf_future(GPR_CLOCK_REALTIME));
301 for (size_t i = 0; i < concurrent_test_iterations(); i++) {
302 EXPECT_EQ(a->arena->MakePooled<Type2>()->Foo(), 2);
303 }
304 },
305 &args);
306 thds2[i].Start();
307 }
308
309 gpr_event_set(&args.ev_start, reinterpret_cast<void*>(1));
310
311 for (auto& th : thds1) {
312 th.Join();
313 }
314 for (auto& th : thds2) {
315 th.Join();
316 }
317 }
318
319 struct Foo {
Foogrpc_core::Foo320 explicit Foo(int x) : p(std::make_unique<int>(x)) {}
321 std::unique_ptr<int> p;
322 };
323
324 template <>
325 struct ArenaContextType<Foo> {
Destroygrpc_core::ArenaContextType326 static void Destroy(Foo* p) { p->~Foo(); }
327 };
328
329 struct alignas(16) VeryAligned {};
330
TEST(ArenaTest,FooContext)331 TEST(ArenaTest, FooContext) {
332 auto arena = SimpleArenaAllocator()->MakeArena();
333 EXPECT_EQ(arena->GetContext<Foo>(), nullptr);
334 arena->SetContext(arena->New<Foo>(42));
335 ASSERT_NE(arena->GetContext<Foo>(), nullptr);
336 EXPECT_EQ(*arena->GetContext<Foo>()->p, 42);
337 arena->New<VeryAligned>();
338 arena->New<VeryAligned>();
339 }
340
341 class MockArenaFactory : public ArenaFactory {
342 public:
MockArenaFactory()343 MockArenaFactory()
344 : ArenaFactory(
345 ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator(
346 "test")) {}
347 MOCK_METHOD(RefCountedPtr<Arena>, MakeArena, (), (override));
348 MOCK_METHOD(void, FinalizeArena, (Arena * arena), (override));
349 };
350
TEST(ArenaTest,FinalizeArenaIsCalled)351 TEST(ArenaTest, FinalizeArenaIsCalled) {
352 auto factory = MakeRefCounted<StrictMock<MockArenaFactory>>();
353 auto arena = Arena::Create(1, factory);
354 EXPECT_CALL(*factory, FinalizeArena(arena.get()));
355 arena.reset();
356 }
357
TEST(ArenaTest,AccurateBaseByteCount)358 TEST(ArenaTest, AccurateBaseByteCount) {
359 auto factory = MakeRefCounted<StrictMock<MockArenaFactory>>();
360 auto arena = Arena::Create(1, factory);
361 EXPECT_CALL(*factory, FinalizeArena(arena.get())).WillOnce([](Arena* a) {
362 EXPECT_EQ(a->TotalUsedBytes(),
363 Arena::ArenaOverhead() +
364 GPR_ROUND_UP_TO_ALIGNMENT_SIZE(
365 arena_detail::BaseArenaContextTraits::ContextSize()));
366 });
367 arena.reset();
368 }
369
TEST(ArenaTest,AccurateByteCountWithAllocation)370 TEST(ArenaTest, AccurateByteCountWithAllocation) {
371 auto factory = MakeRefCounted<StrictMock<MockArenaFactory>>();
372 auto arena = Arena::Create(1, factory);
373 arena->Alloc(1000);
374 EXPECT_CALL(*factory, FinalizeArena(arena.get())).WillOnce([](Arena* a) {
375 EXPECT_EQ(a->TotalUsedBytes(),
376 Arena::ArenaOverhead() +
377 GPR_ROUND_UP_TO_ALIGNMENT_SIZE(
378 arena_detail::BaseArenaContextTraits::ContextSize()) +
379 GPR_ROUND_UP_TO_ALIGNMENT_SIZE(1000));
380 });
381 arena.reset();
382 }
383
384 } // namespace grpc_core
385
main(int argc,char * argv[])386 int main(int argc, char* argv[]) {
387 grpc::testing::TestEnvironment give_me_a_name(&argc, argv);
388 ::testing::InitGoogleTest(&argc, argv);
389 return RUN_ALL_TESTS();
390 }
391