1 #include "test/jemalloc_test.h"
2
3 const char *malloc_conf = "decay_time:1,lg_tcache_max:0";
4
5 static nstime_monotonic_t *nstime_monotonic_orig;
6 static nstime_update_t *nstime_update_orig;
7
8 static unsigned nupdates_mock;
9 static nstime_t time_mock;
10 static bool monotonic_mock;
11
12 static bool
nstime_monotonic_mock(void)13 nstime_monotonic_mock(void)
14 {
15 return (monotonic_mock);
16 }
17
18 static bool
nstime_update_mock(nstime_t * time)19 nstime_update_mock(nstime_t *time)
20 {
21 nupdates_mock++;
22 if (monotonic_mock)
23 nstime_copy(time, &time_mock);
24 return (!monotonic_mock);
25 }
26
TEST_BEGIN(test_decay_ticks)27 TEST_BEGIN(test_decay_ticks)
28 {
29 ticker_t *decay_ticker;
30 unsigned tick0, tick1;
31 size_t sz, large0;
32 void *p;
33
34 decay_ticker = decay_ticker_get(tsd_fetch(), 0);
35 assert_ptr_not_null(decay_ticker,
36 "Unexpected failure getting decay ticker");
37
38 sz = sizeof(size_t);
39 assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL,
40 0), 0, "Unexpected mallctl failure");
41
42 /*
43 * Test the standard APIs using a large size class, since we can't
44 * control tcache interactions for small size classes (except by
45 * completely disabling tcache for the entire test program).
46 */
47
48 /* malloc(). */
49 tick0 = ticker_read(decay_ticker);
50 p = malloc(large0);
51 assert_ptr_not_null(p, "Unexpected malloc() failure");
52 tick1 = ticker_read(decay_ticker);
53 assert_u32_ne(tick1, tick0, "Expected ticker to tick during malloc()");
54 /* free(). */
55 tick0 = ticker_read(decay_ticker);
56 free(p);
57 tick1 = ticker_read(decay_ticker);
58 assert_u32_ne(tick1, tick0, "Expected ticker to tick during free()");
59
60 /* calloc(). */
61 tick0 = ticker_read(decay_ticker);
62 p = calloc(1, large0);
63 assert_ptr_not_null(p, "Unexpected calloc() failure");
64 tick1 = ticker_read(decay_ticker);
65 assert_u32_ne(tick1, tick0, "Expected ticker to tick during calloc()");
66 free(p);
67
68 /* posix_memalign(). */
69 tick0 = ticker_read(decay_ticker);
70 assert_d_eq(posix_memalign(&p, sizeof(size_t), large0), 0,
71 "Unexpected posix_memalign() failure");
72 tick1 = ticker_read(decay_ticker);
73 assert_u32_ne(tick1, tick0,
74 "Expected ticker to tick during posix_memalign()");
75 free(p);
76
77 /* aligned_alloc(). */
78 tick0 = ticker_read(decay_ticker);
79 p = aligned_alloc(sizeof(size_t), large0);
80 assert_ptr_not_null(p, "Unexpected aligned_alloc() failure");
81 tick1 = ticker_read(decay_ticker);
82 assert_u32_ne(tick1, tick0,
83 "Expected ticker to tick during aligned_alloc()");
84 free(p);
85
86 /* realloc(). */
87 /* Allocate. */
88 tick0 = ticker_read(decay_ticker);
89 p = realloc(NULL, large0);
90 assert_ptr_not_null(p, "Unexpected realloc() failure");
91 tick1 = ticker_read(decay_ticker);
92 assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
93 /* Reallocate. */
94 tick0 = ticker_read(decay_ticker);
95 p = realloc(p, large0);
96 assert_ptr_not_null(p, "Unexpected realloc() failure");
97 tick1 = ticker_read(decay_ticker);
98 assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
99 /* Deallocate. */
100 tick0 = ticker_read(decay_ticker);
101 realloc(p, 0);
102 tick1 = ticker_read(decay_ticker);
103 assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
104
105 /*
106 * Test the *allocx() APIs using large and small size classes, with
107 * tcache explicitly disabled.
108 */
109 {
110 unsigned i;
111 size_t allocx_sizes[2];
112 allocx_sizes[0] = large0;
113 allocx_sizes[1] = 1;
114
115 for (i = 0; i < sizeof(allocx_sizes) / sizeof(size_t); i++) {
116 sz = allocx_sizes[i];
117
118 /* mallocx(). */
119 tick0 = ticker_read(decay_ticker);
120 p = mallocx(sz, MALLOCX_TCACHE_NONE);
121 assert_ptr_not_null(p, "Unexpected mallocx() failure");
122 tick1 = ticker_read(decay_ticker);
123 assert_u32_ne(tick1, tick0,
124 "Expected ticker to tick during mallocx() (sz=%zu)",
125 sz);
126 /* rallocx(). */
127 tick0 = ticker_read(decay_ticker);
128 p = rallocx(p, sz, MALLOCX_TCACHE_NONE);
129 assert_ptr_not_null(p, "Unexpected rallocx() failure");
130 tick1 = ticker_read(decay_ticker);
131 assert_u32_ne(tick1, tick0,
132 "Expected ticker to tick during rallocx() (sz=%zu)",
133 sz);
134 /* xallocx(). */
135 tick0 = ticker_read(decay_ticker);
136 xallocx(p, sz, 0, MALLOCX_TCACHE_NONE);
137 tick1 = ticker_read(decay_ticker);
138 assert_u32_ne(tick1, tick0,
139 "Expected ticker to tick during xallocx() (sz=%zu)",
140 sz);
141 /* dallocx(). */
142 tick0 = ticker_read(decay_ticker);
143 dallocx(p, MALLOCX_TCACHE_NONE);
144 tick1 = ticker_read(decay_ticker);
145 assert_u32_ne(tick1, tick0,
146 "Expected ticker to tick during dallocx() (sz=%zu)",
147 sz);
148 /* sdallocx(). */
149 p = mallocx(sz, MALLOCX_TCACHE_NONE);
150 assert_ptr_not_null(p, "Unexpected mallocx() failure");
151 tick0 = ticker_read(decay_ticker);
152 sdallocx(p, sz, MALLOCX_TCACHE_NONE);
153 tick1 = ticker_read(decay_ticker);
154 assert_u32_ne(tick1, tick0,
155 "Expected ticker to tick during sdallocx() "
156 "(sz=%zu)", sz);
157 }
158 }
159
160 /*
161 * Test tcache fill/flush interactions for large and small size classes,
162 * using an explicit tcache.
163 */
164 if (config_tcache) {
165 unsigned tcache_ind, i;
166 size_t tcache_sizes[2];
167 tcache_sizes[0] = large0;
168 tcache_sizes[1] = 1;
169
170 sz = sizeof(unsigned);
171 assert_d_eq(mallctl("tcache.create", (void *)&tcache_ind, &sz,
172 NULL, 0), 0, "Unexpected mallctl failure");
173
174 for (i = 0; i < sizeof(tcache_sizes) / sizeof(size_t); i++) {
175 sz = tcache_sizes[i];
176
177 /* tcache fill. */
178 tick0 = ticker_read(decay_ticker);
179 p = mallocx(sz, MALLOCX_TCACHE(tcache_ind));
180 assert_ptr_not_null(p, "Unexpected mallocx() failure");
181 tick1 = ticker_read(decay_ticker);
182 assert_u32_ne(tick1, tick0,
183 "Expected ticker to tick during tcache fill "
184 "(sz=%zu)", sz);
185 /* tcache flush. */
186 dallocx(p, MALLOCX_TCACHE(tcache_ind));
187 tick0 = ticker_read(decay_ticker);
188 assert_d_eq(mallctl("tcache.flush", NULL, NULL,
189 (void *)&tcache_ind, sizeof(unsigned)), 0,
190 "Unexpected mallctl failure");
191 tick1 = ticker_read(decay_ticker);
192 assert_u32_ne(tick1, tick0,
193 "Expected ticker to tick during tcache flush "
194 "(sz=%zu)", sz);
195 }
196 }
197 }
198 TEST_END
199
TEST_BEGIN(test_decay_ticker)200 TEST_BEGIN(test_decay_ticker)
201 {
202 #define NPS 1024
203 int flags = (MALLOCX_ARENA(0) | MALLOCX_TCACHE_NONE);
204 void *ps[NPS];
205 uint64_t epoch;
206 uint64_t npurge0 = 0;
207 uint64_t npurge1 = 0;
208 size_t sz, large;
209 unsigned i, nupdates0;
210 nstime_t time, decay_time, deadline;
211
212 /*
213 * Allocate a bunch of large objects, pause the clock, deallocate the
214 * objects, restore the clock, then [md]allocx() in a tight loop to
215 * verify the ticker triggers purging.
216 */
217
218 if (config_tcache) {
219 size_t tcache_max;
220
221 sz = sizeof(size_t);
222 assert_d_eq(mallctl("arenas.tcache_max", (void *)&tcache_max,
223 &sz, NULL, 0), 0, "Unexpected mallctl failure");
224 large = nallocx(tcache_max + 1, flags);
225 } else {
226 sz = sizeof(size_t);
227 assert_d_eq(mallctl("arenas.lextent.0.size", &large, &sz, NULL,
228 0), 0, "Unexpected mallctl failure");
229 }
230
231 assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
232 "Unexpected mallctl failure");
233 assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch,
234 sizeof(uint64_t)), 0, "Unexpected mallctl failure");
235 sz = sizeof(uint64_t);
236 assert_d_eq(mallctl("stats.arenas.0.npurge", (void *)&npurge0, &sz,
237 NULL, 0), config_stats ? 0 : ENOENT, "Unexpected mallctl result");
238
239 for (i = 0; i < NPS; i++) {
240 ps[i] = mallocx(large, flags);
241 assert_ptr_not_null(ps[i], "Unexpected mallocx() failure");
242 }
243
244 nupdates_mock = 0;
245 nstime_init(&time_mock, 0);
246 nstime_update(&time_mock);
247 monotonic_mock = true;
248
249 nstime_monotonic_orig = nstime_monotonic;
250 nstime_update_orig = nstime_update;
251 nstime_monotonic = nstime_monotonic_mock;
252 nstime_update = nstime_update_mock;
253
254 for (i = 0; i < NPS; i++) {
255 dallocx(ps[i], flags);
256 nupdates0 = nupdates_mock;
257 assert_d_eq(mallctl("arena.0.decay", NULL, NULL, NULL, 0), 0,
258 "Unexpected arena.0.decay failure");
259 assert_u_gt(nupdates_mock, nupdates0,
260 "Expected nstime_update() to be called");
261 }
262
263 nstime_monotonic = nstime_monotonic_orig;
264 nstime_update = nstime_update_orig;
265
266 nstime_init(&time, 0);
267 nstime_update(&time);
268 nstime_init2(&decay_time, opt_decay_time, 0);
269 nstime_copy(&deadline, &time);
270 nstime_add(&deadline, &decay_time);
271 do {
272 for (i = 0; i < DECAY_NTICKS_PER_UPDATE / 2; i++) {
273 void *p = mallocx(1, flags);
274 assert_ptr_not_null(p, "Unexpected mallocx() failure");
275 dallocx(p, flags);
276 }
277 assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch,
278 sizeof(uint64_t)), 0, "Unexpected mallctl failure");
279 sz = sizeof(uint64_t);
280 assert_d_eq(mallctl("stats.arenas.0.npurge", (void *)&npurge1,
281 &sz, NULL, 0), config_stats ? 0 : ENOENT,
282 "Unexpected mallctl result");
283
284 nstime_update(&time);
285 } while (nstime_compare(&time, &deadline) <= 0 && npurge1 == npurge0);
286
287 if (config_stats)
288 assert_u64_gt(npurge1, npurge0, "Expected purging to occur");
289 #undef NPS
290 }
291 TEST_END
292
TEST_BEGIN(test_decay_nonmonotonic)293 TEST_BEGIN(test_decay_nonmonotonic)
294 {
295 #define NPS (SMOOTHSTEP_NSTEPS + 1)
296 int flags = (MALLOCX_ARENA(0) | MALLOCX_TCACHE_NONE);
297 void *ps[NPS];
298 uint64_t epoch;
299 uint64_t npurge0 = 0;
300 uint64_t npurge1 = 0;
301 size_t sz, large0;
302 unsigned i, nupdates0;
303
304 sz = sizeof(size_t);
305 assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL,
306 0), 0, "Unexpected mallctl failure");
307
308 assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
309 "Unexpected mallctl failure");
310 assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch,
311 sizeof(uint64_t)), 0, "Unexpected mallctl failure");
312 sz = sizeof(uint64_t);
313 assert_d_eq(mallctl("stats.arenas.0.npurge", (void *)&npurge0, &sz,
314 NULL, 0), config_stats ? 0 : ENOENT, "Unexpected mallctl result");
315
316 nupdates_mock = 0;
317 nstime_init(&time_mock, 0);
318 nstime_update(&time_mock);
319 monotonic_mock = false;
320
321 nstime_monotonic_orig = nstime_monotonic;
322 nstime_update_orig = nstime_update;
323 nstime_monotonic = nstime_monotonic_mock;
324 nstime_update = nstime_update_mock;
325
326 for (i = 0; i < NPS; i++) {
327 ps[i] = mallocx(large0, flags);
328 assert_ptr_not_null(ps[i], "Unexpected mallocx() failure");
329 }
330
331 for (i = 0; i < NPS; i++) {
332 dallocx(ps[i], flags);
333 nupdates0 = nupdates_mock;
334 assert_d_eq(mallctl("arena.0.decay", NULL, NULL, NULL, 0), 0,
335 "Unexpected arena.0.decay failure");
336 assert_u_gt(nupdates_mock, nupdates0,
337 "Expected nstime_update() to be called");
338 }
339
340 assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch,
341 sizeof(uint64_t)), 0, "Unexpected mallctl failure");
342 sz = sizeof(uint64_t);
343 assert_d_eq(mallctl("stats.arenas.0.npurge", (void *)&npurge1, &sz,
344 NULL, 0), config_stats ? 0 : ENOENT, "Unexpected mallctl result");
345
346 if (config_stats)
347 assert_u64_eq(npurge0, npurge1, "Unexpected purging occurred");
348
349 nstime_monotonic = nstime_monotonic_orig;
350 nstime_update = nstime_update_orig;
351 #undef NPS
352 }
353 TEST_END
354
355 int
main(void)356 main(void)
357 {
358 return (test(
359 test_decay_ticks,
360 test_decay_ticker,
361 test_decay_nonmonotonic));
362 }
363