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