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 <inttypes.h>
22 #include <string.h>
23
24 #include <algorithm>
25 #include <iosfwd>
26 #include <memory>
27 #include <ostream>
28 #include <string>
29 #include <vector>
30
31 #include "absl/strings/str_join.h"
32 #include "gtest/gtest.h"
33
34 #include <grpc/support/sync.h>
35 #include <grpc/support/time.h>
36
37 #include "src/core/lib/gprpp/ref_counted_ptr.h"
38 #include "src/core/lib/gprpp/thd.h"
39 #include "src/core/lib/iomgr/exec_ctx.h"
40 #include "src/core/lib/resource_quota/resource_quota.h"
41 #include "test/core/util/test_config.h"
42
43 namespace grpc_core {
44
45 struct AllocShape {
46 size_t initial_size;
47 std::vector<size_t> allocs;
48 };
49
operator <<(std::ostream & out,const AllocShape & shape)50 std::ostream& operator<<(std::ostream& out, const AllocShape& shape) {
51 out << "AllocShape{initial_size=" << shape.initial_size
52 << ", allocs=" << absl::StrJoin(shape.allocs, ",") << "}";
53 return out;
54 }
55
56 class AllocTest : public ::testing::TestWithParam<AllocShape> {};
57
TEST_P(AllocTest,Works)58 TEST_P(AllocTest, Works) {
59 ExecCtx exec_ctx;
60 MemoryAllocator memory_allocator = MemoryAllocator(
61 ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator("test"));
62 Arena* a = Arena::Create(GetParam().initial_size, &memory_allocator);
63 std::vector<void*> allocated;
64 for (auto alloc : GetParam().allocs) {
65 void* p = a->Alloc(alloc);
66 // ensure the returned address is aligned
67 EXPECT_EQ(((intptr_t)p & 0xf), 0);
68 // ensure no duplicate results
69 for (auto other_p : allocated) {
70 EXPECT_NE(p, other_p);
71 }
72 // ensure writable
73 memset(p, 1, alloc);
74 allocated.push_back(p);
75 }
76 a->Destroy();
77 }
78
79 INSTANTIATE_TEST_SUITE_P(
80 AllocTests, AllocTest,
81 ::testing::Values(AllocShape{0, {1}}, AllocShape{1, {1}},
82 AllocShape{1, {2}}, AllocShape{1, {3}},
83 AllocShape{1, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
84 AllocShape{6, {1, 2, 3}}));
85
86 #define CONCURRENT_TEST_THREADS 10
87
concurrent_test_iterations()88 size_t concurrent_test_iterations() {
89 if (sizeof(void*) < 8) return 1000;
90 return 100000;
91 }
92
93 typedef struct {
94 gpr_event ev_start;
95 Arena* arena;
96 } concurrent_test_args;
97
98 class ArenaTest : public ::testing::Test {
99 protected:
100 MemoryAllocator memory_allocator_ = MemoryAllocator(
101 ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator("test"));
102 };
103
TEST_F(ArenaTest,NoOp)104 TEST_F(ArenaTest, NoOp) {
105 ExecCtx exec_ctx;
106 Arena::Create(1, &memory_allocator_)->Destroy();
107 }
108
TEST_F(ArenaTest,ManagedNew)109 TEST_F(ArenaTest, ManagedNew) {
110 ExecCtx exec_ctx;
111 Arena* arena = Arena::Create(1, &memory_allocator_);
112 for (int i = 0; i < 100; i++) {
113 arena->ManagedNew<std::unique_ptr<int>>(std::make_unique<int>(i));
114 }
115 arena->Destroy();
116 }
117
TEST_F(ArenaTest,ConcurrentAlloc)118 TEST_F(ArenaTest, ConcurrentAlloc) {
119 concurrent_test_args args;
120 gpr_event_init(&args.ev_start);
121 args.arena = Arena::Create(1024, &memory_allocator_);
122
123 Thread thds[CONCURRENT_TEST_THREADS];
124
125 for (int i = 0; i < CONCURRENT_TEST_THREADS; i++) {
126 thds[i] = Thread(
127 "grpc_concurrent_test",
128 [](void* arg) {
129 concurrent_test_args* a = static_cast<concurrent_test_args*>(arg);
130 gpr_event_wait(&a->ev_start, gpr_inf_future(GPR_CLOCK_REALTIME));
131 for (size_t i = 0; i < concurrent_test_iterations(); i++) {
132 *static_cast<char*>(a->arena->Alloc(1)) = static_cast<char>(i);
133 }
134 },
135 &args);
136 thds[i].Start();
137 }
138
139 gpr_event_set(&args.ev_start, reinterpret_cast<void*>(1));
140
141 for (auto& th : thds) {
142 th.Join();
143 }
144
145 args.arena->Destroy();
146 }
147
TEST_F(ArenaTest,ConcurrentManagedNew)148 TEST_F(ArenaTest, ConcurrentManagedNew) {
149 concurrent_test_args args;
150 gpr_event_init(&args.ev_start);
151 args.arena = Arena::Create(1024, &memory_allocator_);
152
153 Thread thds[CONCURRENT_TEST_THREADS];
154
155 for (int i = 0; i < CONCURRENT_TEST_THREADS; i++) {
156 thds[i] = Thread(
157 "grpc_concurrent_test",
158 [](void* arg) {
159 concurrent_test_args* a = static_cast<concurrent_test_args*>(arg);
160 gpr_event_wait(&a->ev_start, gpr_inf_future(GPR_CLOCK_REALTIME));
161 for (size_t i = 0; i < concurrent_test_iterations(); i++) {
162 a->arena->ManagedNew<std::unique_ptr<int>>(
163 std::make_unique<int>(static_cast<int>(i)));
164 }
165 },
166 &args);
167 thds[i].Start();
168 }
169
170 gpr_event_set(&args.ev_start, reinterpret_cast<void*>(1));
171
172 for (auto& th : thds) {
173 th.Join();
174 }
175
176 args.arena->Destroy();
177 }
178
179 template <typename Int>
Scribble(Int * ints,int n,int offset)180 void Scribble(Int* ints, int n, int offset) {
181 for (int i = 0; i < n; i++) {
182 ints[i] = static_cast<Int>(i + offset);
183 }
184 }
185
186 template <typename Int>
IsScribbled(Int * ints,int n,int offset)187 bool IsScribbled(Int* ints, int n, int offset) {
188 for (int i = 0; i < n; i++) {
189 if (ints[i] != static_cast<Int>(i + offset)) return false;
190 }
191 return true;
192 }
193
194 #ifndef GRPC_ARENA_POOLED_ALLOCATIONS_USE_MALLOC
TEST_F(ArenaTest,PooledObjectsArePooled)195 TEST_F(ArenaTest, PooledObjectsArePooled) {
196 struct TestObj {
197 char a[100];
198 };
199
200 auto arena = MakeScopedArena(1024, &memory_allocator_);
201 auto obj = arena->MakePooled<TestObj>();
202 Scribble(obj->a, 100, 1);
203 EXPECT_TRUE(IsScribbled(obj->a, 100, 1));
204 void* p = obj.get();
205 obj.reset();
206 obj = arena->MakePooled<TestObj>();
207 EXPECT_FALSE(IsScribbled(obj->a, 100, 1));
208 EXPECT_EQ(p, obj.get());
209 Scribble(obj->a, 100, 2);
210 EXPECT_TRUE(IsScribbled(obj->a, 100, 2));
211 }
212 #endif
213
TEST_F(ArenaTest,CreateManyObjects)214 TEST_F(ArenaTest, CreateManyObjects) {
215 struct TestObj {
216 char a[100];
217 };
218 auto arena = MakeScopedArena(1024, &memory_allocator_);
219 std::vector<Arena::PoolPtr<TestObj>> objs;
220 objs.reserve(1000);
221 for (int i = 0; i < 1000; i++) {
222 objs.emplace_back(arena->MakePooled<TestObj>());
223 Scribble(objs.back()->a, 100, i);
224 }
225 for (int i = 0; i < 1000; i++) {
226 EXPECT_TRUE(IsScribbled(objs[i]->a, 100, i));
227 }
228 }
229
TEST_F(ArenaTest,CreateManyObjectsWithDestructors)230 TEST_F(ArenaTest, CreateManyObjectsWithDestructors) {
231 using TestObj = std::unique_ptr<int>;
232 auto arena = MakeScopedArena(1024, &memory_allocator_);
233 std::vector<Arena::PoolPtr<TestObj>> objs;
234 objs.reserve(1000);
235 for (int i = 0; i < 1000; i++) {
236 objs.emplace_back(arena->MakePooled<TestObj>(new int(i)));
237 }
238 }
239
TEST_F(ArenaTest,CreatePoolArray)240 TEST_F(ArenaTest, CreatePoolArray) {
241 auto arena = MakeScopedArena(1024, &memory_allocator_);
242 auto p = arena->MakePooledArray<int>(1024);
243 #ifndef GRPC_ARENA_POOLED_ALLOCATIONS_USE_MALLOC
244 EXPECT_FALSE(p.get_deleter().has_freelist());
245 #else
246 EXPECT_TRUE(p.get_deleter().has_freelist());
247 #endif
248 p = arena->MakePooledArray<int>(5);
249 EXPECT_TRUE(p.get_deleter().has_freelist());
250 Scribble(p.get(), 5, 1);
251 EXPECT_TRUE(IsScribbled(p.get(), 5, 1));
252 }
253
TEST_F(ArenaTest,ConcurrentMakePooled)254 TEST_F(ArenaTest, ConcurrentMakePooled) {
255 concurrent_test_args args;
256 gpr_event_init(&args.ev_start);
257 args.arena = Arena::Create(1024, &memory_allocator_);
258
259 class BaseClass {
260 public:
261 virtual ~BaseClass() {}
262 virtual int Foo() = 0;
263 };
264
265 class Type1 : public BaseClass {
266 public:
267 int Foo() override { return 1; }
268 };
269
270 class Type2 : public BaseClass {
271 public:
272 int Foo() override { return 2; }
273 };
274
275 Thread thds1[CONCURRENT_TEST_THREADS / 2];
276 Thread thds2[CONCURRENT_TEST_THREADS / 2];
277
278 for (int i = 0; i < CONCURRENT_TEST_THREADS / 2; i++) {
279 thds1[i] = Thread(
280 "grpc_concurrent_test",
281 [](void* arg) {
282 concurrent_test_args* a = static_cast<concurrent_test_args*>(arg);
283 gpr_event_wait(&a->ev_start, gpr_inf_future(GPR_CLOCK_REALTIME));
284 for (size_t i = 0; i < concurrent_test_iterations(); i++) {
285 EXPECT_EQ(a->arena->MakePooled<Type1>()->Foo(), 1);
286 }
287 },
288 &args);
289 thds1[i].Start();
290
291 thds2[i] = Thread(
292 "grpc_concurrent_test",
293 [](void* arg) {
294 concurrent_test_args* a = static_cast<concurrent_test_args*>(arg);
295 gpr_event_wait(&a->ev_start, gpr_inf_future(GPR_CLOCK_REALTIME));
296 for (size_t i = 0; i < concurrent_test_iterations(); i++) {
297 EXPECT_EQ(a->arena->MakePooled<Type2>()->Foo(), 2);
298 }
299 },
300 &args);
301 thds2[i].Start();
302 }
303
304 gpr_event_set(&args.ev_start, reinterpret_cast<void*>(1));
305
306 for (auto& th : thds1) {
307 th.Join();
308 }
309 for (auto& th : thds2) {
310 th.Join();
311 }
312
313 args.arena->Destroy();
314 }
315
316 } // namespace grpc_core
317
main(int argc,char * argv[])318 int main(int argc, char* argv[]) {
319 grpc::testing::TestEnvironment give_me_a_name(&argc, argv);
320 ::testing::InitGoogleTest(&argc, argv);
321 return RUN_ALL_TESTS();
322 }
323