10
10
*/
11
11
12
12
#include " gmock/gmock.h"
13
+ #include " units.h"
13
14
#include " wasm/allocator.h"
14
15
16
+ #include < seastar/core/reactor.hh>
17
+ #include < seastar/core/when_all.hh>
18
+
15
19
#include < gmock/gmock.h>
16
20
#include < gtest/gtest.h>
17
21
22
26
23
27
namespace wasm {
24
28
29
+ constexpr static auto default_memset_chunk_size = 10_MiB;
30
+
25
31
using ::testing::Optional;
26
32
27
33
TEST (HeapAllocatorParamsTest, SizeIsAligned) {
28
34
size_t page_size = ::getpagesize ();
29
35
heap_allocator allocator (heap_allocator::config{
30
36
.heap_memory_size = page_size + 3 ,
31
37
.num_heaps = 1 ,
38
+ .memset_chunk_size = default_memset_chunk_size,
32
39
});
33
- auto mem = allocator.allocate (
34
- {.minimum = 0 , .maximum = std::numeric_limits<size_t >::max ()});
40
+ auto mem = allocator
41
+ .allocate (
42
+ {.minimum = 0 ,
43
+ .maximum = std::numeric_limits<size_t >::max ()})
44
+ .get ();
35
45
ASSERT_TRUE (mem.has_value ());
36
46
EXPECT_EQ (mem->size , page_size * 2 );
37
47
}
@@ -41,8 +51,10 @@ TEST(HeapAllocatorTest, CanAllocateOne) {
41
51
heap_allocator allocator (heap_allocator::config{
42
52
.heap_memory_size = page_size,
43
53
.num_heaps = 1 ,
54
+ .memset_chunk_size = default_memset_chunk_size,
44
55
});
45
- auto mem = allocator.allocate ({.minimum = page_size, .maximum = page_size});
56
+ auto mem
57
+ = allocator.allocate ({.minimum = page_size, .maximum = page_size}).get ();
46
58
ASSERT_TRUE (mem.has_value ());
47
59
EXPECT_EQ (mem->size , page_size);
48
60
}
@@ -52,14 +64,17 @@ TEST(HeapAllocatorTest, MustAllocateWithinBounds) {
52
64
heap_allocator allocator (heap_allocator::config{
53
65
.heap_memory_size = page_size,
54
66
.num_heaps = 1 ,
67
+ .memset_chunk_size = default_memset_chunk_size,
55
68
});
56
69
// minimum too large
57
- auto mem = allocator.allocate (
58
- {.minimum = page_size * 2 , .maximum = page_size * 3 });
70
+ auto mem = allocator
71
+ .allocate ({.minimum = page_size * 2 , .maximum = page_size * 3 })
72
+ .get ();
59
73
EXPECT_FALSE (mem.has_value ());
60
74
// maximum too small
61
- mem = allocator.allocate (
62
- {.minimum = page_size / 2 , .maximum = page_size - 1 });
75
+ mem = allocator
76
+ .allocate ({.minimum = page_size / 2 , .maximum = page_size - 1 })
77
+ .get ();
63
78
EXPECT_FALSE (mem.has_value ());
64
79
}
65
80
@@ -68,10 +83,13 @@ TEST(HeapAllocatorTest, Exhaustion) {
68
83
heap_allocator allocator (heap_allocator::config{
69
84
.heap_memory_size = page_size,
70
85
.num_heaps = 1 ,
86
+ .memset_chunk_size = default_memset_chunk_size,
71
87
});
72
- auto mem = allocator.allocate ({.minimum = page_size, .maximum = page_size});
88
+ auto mem
89
+ = allocator.allocate ({.minimum = page_size, .maximum = page_size}).get ();
73
90
EXPECT_TRUE (mem.has_value ());
74
- mem = allocator.allocate ({.minimum = page_size, .maximum = page_size});
91
+ mem
92
+ = allocator.allocate ({.minimum = page_size, .maximum = page_size}).get ();
75
93
EXPECT_FALSE (mem.has_value ());
76
94
}
77
95
@@ -80,25 +98,90 @@ TEST(HeapAllocatorTest, CanReturnMemoryToThePool) {
80
98
heap_allocator allocator (heap_allocator::config{
81
99
.heap_memory_size = page_size,
82
100
.num_heaps = 3 ,
101
+ .memset_chunk_size = default_memset_chunk_size,
83
102
});
84
103
heap_allocator::request req{.minimum = page_size, .maximum = page_size};
85
104
std::vector<heap_memory> allocated;
86
105
for (int i = 0 ; i < 3 ; ++i) {
87
- auto mem = allocator.allocate (req);
106
+ auto mem = allocator.allocate (req). get () ;
88
107
ASSERT_TRUE (mem.has_value ());
89
108
allocated.push_back (std::move (*mem));
90
109
}
91
- auto mem = allocator.allocate (req);
110
+ auto mem = allocator.allocate (req). get () ;
92
111
EXPECT_FALSE (mem.has_value ());
93
112
mem = std::move (allocated.back ());
94
113
allocated.pop_back ();
95
114
allocator.deallocate (std::move (*mem), /* used_amount=*/ 0 );
96
- mem = allocator.allocate (req);
115
+ mem = allocator.allocate (req). get () ;
97
116
EXPECT_TRUE (mem.has_value ());
98
- mem = allocator.allocate (req);
117
+ mem = allocator.allocate (req). get () ;
99
118
EXPECT_FALSE (mem.has_value ());
100
119
}
101
120
121
+ // We want to test a specific scenario where the deallocation happens
122
+ // asynchronously, however, release mode continuations can be "inlined" into the
123
+ // current executing task, so we can't enforce the scenario we want to test, so
124
+ // this test only runs in debug mode, which forces as many scheduling points as
125
+ // possible and the zeroing task always happens asynchronously.
126
+ #ifndef NDEBUG
127
+
128
+ using ::testing::_;
129
+
130
+ TEST (HeapAllocatorTest, AsyncDeallocationOnlyOneAwakened) {
131
+ size_t page_size = ::getpagesize ();
132
+ // force deallocations to be asynchronous.
133
+ size_t test_chunk_size = page_size / 4 ;
134
+ heap_allocator allocator (heap_allocator::config{
135
+ .heap_memory_size = page_size,
136
+ .num_heaps = 2 ,
137
+ .memset_chunk_size = test_chunk_size,
138
+ });
139
+ heap_allocator::request req{.minimum = page_size, .maximum = page_size};
140
+ // Start on deallocation in the background.
141
+ allocator.deallocate (allocator.allocate (req).get ().value (), page_size);
142
+ // Start another deallocation in the background so there is no memory left.
143
+ allocator.deallocate (allocator.allocate (req).get ().value (), page_size);
144
+ auto waiter1 = allocator.allocate (req);
145
+ auto waiter2 = allocator.allocate (req);
146
+ // There should not be memory available, so both our requests for
147
+ // memory should be waiting.
148
+ EXPECT_FALSE (waiter1.available ());
149
+ EXPECT_FALSE (waiter2.available ());
150
+ // waiter1 should be notified first there is memory available, but waiter2
151
+ // should not be notified yet as there is a deallocation in flight.
152
+ // waiter2 may or may not be ready depending on task execution order (which
153
+ // is randomized in debug mode), so we cannot assert that it's not
154
+ // completed.
155
+ EXPECT_THAT (waiter1.get (), Optional (_));
156
+ // The second waiter completes and we can allocate memory
157
+ EXPECT_THAT (waiter2.get (), Optional (_));
158
+ }
159
+
160
+ TEST (HeapAllocatorTest, AsyncDeallocationNotEnoughMemory) {
161
+ size_t page_size = ::getpagesize ();
162
+ // force deallocations to be asynchronous.
163
+ size_t test_chunk_size = page_size / 4 ;
164
+ heap_allocator allocator (heap_allocator::config{
165
+ .heap_memory_size = page_size,
166
+ .num_heaps = 1 ,
167
+ .memset_chunk_size = test_chunk_size,
168
+ });
169
+ heap_allocator::request req{.minimum = page_size, .maximum = page_size};
170
+ allocator.deallocate (allocator.allocate (req).get ().value (), page_size);
171
+
172
+ // The first request succeeded and is waiting for the deallocation to finish
173
+ auto waiter1 = allocator.allocate (req);
174
+ EXPECT_FALSE (waiter1.available ());
175
+
176
+ // Future requests fail immediately, no memory is available
177
+ auto waiter2 = allocator.allocate (req);
178
+ EXPECT_TRUE (waiter2.available ());
179
+
180
+ EXPECT_THAT (waiter1.get (), Optional (_));
181
+ EXPECT_EQ (waiter2.get (), std::nullopt );
182
+ }
183
+ #endif
184
+
102
185
MATCHER (HeapIsZeroed, " is zeroed" ) {
103
186
std::span<uint8_t > d = {arg.data .get (), arg.size };
104
187
for (auto e : d) {
@@ -114,15 +197,16 @@ TEST(HeapAllocatorTest, MemoryIsZeroFilled) {
114
197
heap_allocator allocator (heap_allocator::config{
115
198
.heap_memory_size = page_size,
116
199
.num_heaps = 1 ,
200
+ .memset_chunk_size = default_memset_chunk_size,
117
201
});
118
202
heap_allocator::request req{.minimum = page_size, .maximum = page_size};
119
- auto allocated = allocator.allocate (req);
203
+ auto allocated = allocator.allocate (req). get () ;
120
204
ASSERT_TRUE (allocated.has_value ());
121
205
EXPECT_THAT (allocated, Optional (HeapIsZeroed ()));
122
206
std::fill_n (allocated->data .get (), 4 , 1 );
123
207
allocator.deallocate (*std::move (allocated), 4 );
124
208
125
- allocated = allocator.allocate (req);
209
+ allocated = allocator.allocate (req). get () ;
126
210
ASSERT_TRUE (allocated.has_value ());
127
211
EXPECT_THAT (allocated, Optional (HeapIsZeroed ()));
128
212
}
0 commit comments