1 #include "test/jemalloc_test.h"
2 
3 #include "jemalloc/internal/ticker.h"
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
check_background_thread_enabled(void)13 check_background_thread_enabled(void) {
14 	bool enabled;
15 	size_t sz = sizeof(bool);
16 	int ret = mallctl("background_thread", (void *)&enabled, &sz, NULL,0);
17 	if (ret == ENOENT) {
18 		return false;
19 	}
20 	assert_d_eq(ret, 0, "Unexpected mallctl error");
21 	return enabled;
22 }
23 
24 static bool
nstime_monotonic_mock(void)25 nstime_monotonic_mock(void) {
26 	return monotonic_mock;
27 }
28 
29 static bool
nstime_update_mock(nstime_t * time)30 nstime_update_mock(nstime_t *time) {
31 	nupdates_mock++;
32 	if (monotonic_mock) {
33 		nstime_copy(time, &time_mock);
34 	}
35 	return !monotonic_mock;
36 }
37 
38 static unsigned
do_arena_create(ssize_t dirty_decay_ms,ssize_t muzzy_decay_ms)39 do_arena_create(ssize_t dirty_decay_ms, ssize_t muzzy_decay_ms) {
40 	unsigned arena_ind;
41 	size_t sz = sizeof(unsigned);
42 	assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0),
43 	    0, "Unexpected mallctl() failure");
44 	size_t mib[3];
45 	size_t miblen = sizeof(mib)/sizeof(size_t);
46 
47 	assert_d_eq(mallctlnametomib("arena.0.dirty_decay_ms", mib, &miblen),
48 	    0, "Unexpected mallctlnametomib() failure");
49 	mib[1] = (size_t)arena_ind;
50 	assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL,
51 	    (void *)&dirty_decay_ms, sizeof(dirty_decay_ms)), 0,
52 	    "Unexpected mallctlbymib() failure");
53 
54 	assert_d_eq(mallctlnametomib("arena.0.muzzy_decay_ms", mib, &miblen),
55 	    0, "Unexpected mallctlnametomib() failure");
56 	mib[1] = (size_t)arena_ind;
57 	assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL,
58 	    (void *)&muzzy_decay_ms, sizeof(muzzy_decay_ms)), 0,
59 	    "Unexpected mallctlbymib() failure");
60 
61 	return arena_ind;
62 }
63 
64 static void
do_arena_destroy(unsigned arena_ind)65 do_arena_destroy(unsigned arena_ind) {
66 	size_t mib[3];
67 	size_t miblen = sizeof(mib)/sizeof(size_t);
68 	assert_d_eq(mallctlnametomib("arena.0.destroy", mib, &miblen), 0,
69 	    "Unexpected mallctlnametomib() failure");
70 	mib[1] = (size_t)arena_ind;
71 	assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
72 	    "Unexpected mallctlbymib() failure");
73 }
74 
75 void
do_epoch(void)76 do_epoch(void) {
77 	uint64_t epoch = 1;
78 	assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
79 	    0, "Unexpected mallctl() failure");
80 }
81 
82 void
do_purge(unsigned arena_ind)83 do_purge(unsigned arena_ind) {
84 	size_t mib[3];
85 	size_t miblen = sizeof(mib)/sizeof(size_t);
86 	assert_d_eq(mallctlnametomib("arena.0.purge", mib, &miblen), 0,
87 	    "Unexpected mallctlnametomib() failure");
88 	mib[1] = (size_t)arena_ind;
89 	assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
90 	    "Unexpected mallctlbymib() failure");
91 }
92 
93 void
do_decay(unsigned arena_ind)94 do_decay(unsigned arena_ind) {
95 	size_t mib[3];
96 	size_t miblen = sizeof(mib)/sizeof(size_t);
97 	assert_d_eq(mallctlnametomib("arena.0.decay", mib, &miblen), 0,
98 	    "Unexpected mallctlnametomib() failure");
99 	mib[1] = (size_t)arena_ind;
100 	assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
101 	    "Unexpected mallctlbymib() failure");
102 }
103 
104 static uint64_t
get_arena_npurge_impl(const char * mibname,unsigned arena_ind)105 get_arena_npurge_impl(const char *mibname, unsigned arena_ind) {
106 	size_t mib[4];
107 	size_t miblen = sizeof(mib)/sizeof(size_t);
108 	assert_d_eq(mallctlnametomib(mibname, mib, &miblen), 0,
109 	    "Unexpected mallctlnametomib() failure");
110 	mib[2] = (size_t)arena_ind;
111 	uint64_t npurge = 0;
112 	size_t sz = sizeof(npurge);
113 	assert_d_eq(mallctlbymib(mib, miblen, (void *)&npurge, &sz, NULL, 0),
114 	    config_stats ? 0 : ENOENT, "Unexpected mallctlbymib() failure");
115 	return npurge;
116 }
117 
118 static uint64_t
get_arena_dirty_npurge(unsigned arena_ind)119 get_arena_dirty_npurge(unsigned arena_ind) {
120 	do_epoch();
121 	return get_arena_npurge_impl("stats.arenas.0.dirty_npurge", arena_ind);
122 }
123 
124 static uint64_t
get_arena_muzzy_npurge(unsigned arena_ind)125 get_arena_muzzy_npurge(unsigned arena_ind) {
126 	do_epoch();
127 	return get_arena_npurge_impl("stats.arenas.0.muzzy_npurge", arena_ind);
128 }
129 
130 static uint64_t
get_arena_npurge(unsigned arena_ind)131 get_arena_npurge(unsigned arena_ind) {
132 	do_epoch();
133 	return get_arena_npurge_impl("stats.arenas.0.dirty_npurge", arena_ind) +
134 	    get_arena_npurge_impl("stats.arenas.0.muzzy_npurge", arena_ind);
135 }
136 
137 static size_t
get_arena_pdirty(unsigned arena_ind)138 get_arena_pdirty(unsigned arena_ind) {
139 	do_epoch();
140 	size_t mib[4];
141 	size_t miblen = sizeof(mib)/sizeof(size_t);
142 	assert_d_eq(mallctlnametomib("stats.arenas.0.pdirty", mib, &miblen), 0,
143 	    "Unexpected mallctlnametomib() failure");
144 	mib[2] = (size_t)arena_ind;
145 	size_t pdirty;
146 	size_t sz = sizeof(pdirty);
147 	assert_d_eq(mallctlbymib(mib, miblen, (void *)&pdirty, &sz, NULL, 0), 0,
148 	    "Unexpected mallctlbymib() failure");
149 	return pdirty;
150 }
151 
152 static size_t
get_arena_pmuzzy(unsigned arena_ind)153 get_arena_pmuzzy(unsigned arena_ind) {
154 	do_epoch();
155 	size_t mib[4];
156 	size_t miblen = sizeof(mib)/sizeof(size_t);
157 	assert_d_eq(mallctlnametomib("stats.arenas.0.pmuzzy", mib, &miblen), 0,
158 	    "Unexpected mallctlnametomib() failure");
159 	mib[2] = (size_t)arena_ind;
160 	size_t pmuzzy;
161 	size_t sz = sizeof(pmuzzy);
162 	assert_d_eq(mallctlbymib(mib, miblen, (void *)&pmuzzy, &sz, NULL, 0), 0,
163 	    "Unexpected mallctlbymib() failure");
164 	return pmuzzy;
165 }
166 
167 static void *
do_mallocx(size_t size,int flags)168 do_mallocx(size_t size, int flags) {
169 	void *p = mallocx(size, flags);
170 	assert_ptr_not_null(p, "Unexpected mallocx() failure");
171 	return p;
172 }
173 
174 static void
generate_dirty(unsigned arena_ind,size_t size)175 generate_dirty(unsigned arena_ind, size_t size) {
176 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
177 	void *p = do_mallocx(size, flags);
178 	dallocx(p, flags);
179 }
180 
TEST_BEGIN(test_decay_ticks)181 TEST_BEGIN(test_decay_ticks) {
182 	test_skip_if(check_background_thread_enabled());
183 
184 	ticker_t *decay_ticker;
185 	unsigned tick0, tick1, arena_ind;
186 	size_t sz, large0;
187 	void *p;
188 
189 	sz = sizeof(size_t);
190 	assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL,
191 	    0), 0, "Unexpected mallctl failure");
192 
193 	/* Set up a manually managed arena for test. */
194 	arena_ind = do_arena_create(0, 0);
195 
196 	/* Migrate to the new arena, and get the ticker. */
197 	unsigned old_arena_ind;
198 	size_t sz_arena_ind = sizeof(old_arena_ind);
199 	assert_d_eq(mallctl("thread.arena", (void *)&old_arena_ind,
200 	    &sz_arena_ind, (void *)&arena_ind, sizeof(arena_ind)), 0,
201 	    "Unexpected mallctl() failure");
202 	decay_ticker = decay_ticker_get(tsd_fetch(), arena_ind);
203 	assert_ptr_not_null(decay_ticker,
204 	    "Unexpected failure getting decay ticker");
205 
206 	/*
207 	 * Test the standard APIs using a large size class, since we can't
208 	 * control tcache interactions for small size classes (except by
209 	 * completely disabling tcache for the entire test program).
210 	 */
211 
212 	/* malloc(). */
213 	tick0 = ticker_read(decay_ticker);
214 	p = malloc(large0);
215 	assert_ptr_not_null(p, "Unexpected malloc() failure");
216 	tick1 = ticker_read(decay_ticker);
217 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during malloc()");
218 	/* free(). */
219 	tick0 = ticker_read(decay_ticker);
220 	free(p);
221 	tick1 = ticker_read(decay_ticker);
222 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during free()");
223 
224 	/* calloc(). */
225 	tick0 = ticker_read(decay_ticker);
226 	p = calloc(1, large0);
227 	assert_ptr_not_null(p, "Unexpected calloc() failure");
228 	tick1 = ticker_read(decay_ticker);
229 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during calloc()");
230 	free(p);
231 
232 	/* posix_memalign(). */
233 	tick0 = ticker_read(decay_ticker);
234 	assert_d_eq(posix_memalign(&p, sizeof(size_t), large0), 0,
235 	    "Unexpected posix_memalign() failure");
236 	tick1 = ticker_read(decay_ticker);
237 	assert_u32_ne(tick1, tick0,
238 	    "Expected ticker to tick during posix_memalign()");
239 	free(p);
240 
241 	/* aligned_alloc(). */
242 	tick0 = ticker_read(decay_ticker);
243 	p = aligned_alloc(sizeof(size_t), large0);
244 	assert_ptr_not_null(p, "Unexpected aligned_alloc() failure");
245 	tick1 = ticker_read(decay_ticker);
246 	assert_u32_ne(tick1, tick0,
247 	    "Expected ticker to tick during aligned_alloc()");
248 	free(p);
249 
250 	/* realloc(). */
251 	/* Allocate. */
252 	tick0 = ticker_read(decay_ticker);
253 	p = realloc(NULL, large0);
254 	assert_ptr_not_null(p, "Unexpected realloc() failure");
255 	tick1 = ticker_read(decay_ticker);
256 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
257 	/* Reallocate. */
258 	tick0 = ticker_read(decay_ticker);
259 	p = realloc(p, large0);
260 	assert_ptr_not_null(p, "Unexpected realloc() failure");
261 	tick1 = ticker_read(decay_ticker);
262 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
263 	/* Deallocate. */
264 	tick0 = ticker_read(decay_ticker);
265 	realloc(p, 0);
266 	tick1 = ticker_read(decay_ticker);
267 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
268 
269 	/*
270 	 * Test the *allocx() APIs using large and small size classes, with
271 	 * tcache explicitly disabled.
272 	 */
273 	{
274 		unsigned i;
275 		size_t allocx_sizes[2];
276 		allocx_sizes[0] = large0;
277 		allocx_sizes[1] = 1;
278 
279 		for (i = 0; i < sizeof(allocx_sizes) / sizeof(size_t); i++) {
280 			sz = allocx_sizes[i];
281 
282 			/* mallocx(). */
283 			tick0 = ticker_read(decay_ticker);
284 			p = mallocx(sz, MALLOCX_TCACHE_NONE);
285 			assert_ptr_not_null(p, "Unexpected mallocx() failure");
286 			tick1 = ticker_read(decay_ticker);
287 			assert_u32_ne(tick1, tick0,
288 			    "Expected ticker to tick during mallocx() (sz=%zu)",
289 			    sz);
290 			/* rallocx(). */
291 			tick0 = ticker_read(decay_ticker);
292 			p = rallocx(p, sz, MALLOCX_TCACHE_NONE);
293 			assert_ptr_not_null(p, "Unexpected rallocx() failure");
294 			tick1 = ticker_read(decay_ticker);
295 			assert_u32_ne(tick1, tick0,
296 			    "Expected ticker to tick during rallocx() (sz=%zu)",
297 			    sz);
298 			/* xallocx(). */
299 			tick0 = ticker_read(decay_ticker);
300 			xallocx(p, sz, 0, MALLOCX_TCACHE_NONE);
301 			tick1 = ticker_read(decay_ticker);
302 			assert_u32_ne(tick1, tick0,
303 			    "Expected ticker to tick during xallocx() (sz=%zu)",
304 			    sz);
305 			/* dallocx(). */
306 			tick0 = ticker_read(decay_ticker);
307 			dallocx(p, MALLOCX_TCACHE_NONE);
308 			tick1 = ticker_read(decay_ticker);
309 			assert_u32_ne(tick1, tick0,
310 			    "Expected ticker to tick during dallocx() (sz=%zu)",
311 			    sz);
312 			/* sdallocx(). */
313 			p = mallocx(sz, MALLOCX_TCACHE_NONE);
314 			assert_ptr_not_null(p, "Unexpected mallocx() failure");
315 			tick0 = ticker_read(decay_ticker);
316 			sdallocx(p, sz, MALLOCX_TCACHE_NONE);
317 			tick1 = ticker_read(decay_ticker);
318 			assert_u32_ne(tick1, tick0,
319 			    "Expected ticker to tick during sdallocx() "
320 			    "(sz=%zu)", sz);
321 		}
322 	}
323 
324 	/*
325 	 * Test tcache fill/flush interactions for large and small size classes,
326 	 * using an explicit tcache.
327 	 */
328 	unsigned tcache_ind, i;
329 	size_t tcache_sizes[2];
330 	tcache_sizes[0] = large0;
331 	tcache_sizes[1] = 1;
332 
333 	size_t tcache_max, sz_tcache_max;
334 	sz_tcache_max = sizeof(tcache_max);
335 	assert_d_eq(mallctl("arenas.tcache_max", (void *)&tcache_max,
336 	    &sz_tcache_max, NULL, 0), 0, "Unexpected mallctl() failure");
337 
338 	sz = sizeof(unsigned);
339 	assert_d_eq(mallctl("tcache.create", (void *)&tcache_ind, &sz,
340 	    NULL, 0), 0, "Unexpected mallctl failure");
341 
342 	for (i = 0; i < sizeof(tcache_sizes) / sizeof(size_t); i++) {
343 		sz = tcache_sizes[i];
344 
345 		/* tcache fill. */
346 		tick0 = ticker_read(decay_ticker);
347 		p = mallocx(sz, MALLOCX_TCACHE(tcache_ind));
348 		assert_ptr_not_null(p, "Unexpected mallocx() failure");
349 		tick1 = ticker_read(decay_ticker);
350 		assert_u32_ne(tick1, tick0,
351 		    "Expected ticker to tick during tcache fill "
352 		    "(sz=%zu)", sz);
353 		/* tcache flush. */
354 		dallocx(p, MALLOCX_TCACHE(tcache_ind));
355 		tick0 = ticker_read(decay_ticker);
356 		assert_d_eq(mallctl("tcache.flush", NULL, NULL,
357 		    (void *)&tcache_ind, sizeof(unsigned)), 0,
358 		    "Unexpected mallctl failure");
359 		tick1 = ticker_read(decay_ticker);
360 
361 		/* Will only tick if it's in tcache. */
362 		if (sz <= tcache_max) {
363 			assert_u32_ne(tick1, tick0,
364 			    "Expected ticker to tick during tcache "
365 			    "flush (sz=%zu)", sz);
366 		} else {
367 			assert_u32_eq(tick1, tick0,
368 			    "Unexpected ticker tick during tcache "
369 			    "flush (sz=%zu)", sz);
370 		}
371 	}
372 }
373 TEST_END
374 
375 static void
decay_ticker_helper(unsigned arena_ind,int flags,bool dirty,ssize_t dt,uint64_t dirty_npurge0,uint64_t muzzy_npurge0,bool terminate_asap)376 decay_ticker_helper(unsigned arena_ind, int flags, bool dirty, ssize_t dt,
377     uint64_t dirty_npurge0, uint64_t muzzy_npurge0, bool terminate_asap) {
378 #define NINTERVALS 101
379 	nstime_t time, update_interval, decay_ms, deadline;
380 
381 	nstime_init(&time, 0);
382 	nstime_update(&time);
383 
384 	nstime_init2(&decay_ms, dt, 0);
385 	nstime_copy(&deadline, &time);
386 	nstime_add(&deadline, &decay_ms);
387 
388 	nstime_init2(&update_interval, dt, 0);
389 	nstime_idivide(&update_interval, NINTERVALS);
390 
391 	/*
392 	 * Keep q's slab from being deallocated during the looping below.  If a
393 	 * cached slab were to repeatedly come and go during looping, it could
394 	 * prevent the decay backlog ever becoming empty.
395 	 */
396 	void *p = do_mallocx(1, flags);
397 	uint64_t dirty_npurge1, muzzy_npurge1;
398 	do {
399 		for (unsigned i = 0; i < DECAY_NTICKS_PER_UPDATE / 2;
400 		    i++) {
401 			void *q = do_mallocx(1, flags);
402 			dallocx(q, flags);
403 		}
404 		dirty_npurge1 = get_arena_dirty_npurge(arena_ind);
405 		muzzy_npurge1 = get_arena_muzzy_npurge(arena_ind);
406 
407 		nstime_add(&time_mock, &update_interval);
408 		nstime_update(&time);
409 	} while (nstime_compare(&time, &deadline) <= 0 && ((dirty_npurge1 ==
410 	    dirty_npurge0 && muzzy_npurge1 == muzzy_npurge0) ||
411 	    !terminate_asap));
412 	dallocx(p, flags);
413 
414 	if (config_stats) {
415 		assert_u64_gt(dirty_npurge1 + muzzy_npurge1, dirty_npurge0 +
416 		    muzzy_npurge0, "Expected purging to occur");
417 	}
418 #undef NINTERVALS
419 }
420 
TEST_BEGIN(test_decay_ticker)421 TEST_BEGIN(test_decay_ticker) {
422 	test_skip_if(check_background_thread_enabled());
423 #define NPS 2048
424 	ssize_t ddt = opt_dirty_decay_ms;
425 	ssize_t mdt = opt_muzzy_decay_ms;
426 	unsigned arena_ind = do_arena_create(ddt, mdt);
427 	int flags = (MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE);
428 	void *ps[NPS];
429 	size_t large;
430 
431 	/*
432 	 * Allocate a bunch of large objects, pause the clock, deallocate every
433 	 * other object (to fragment virtual memory), restore the clock, then
434 	 * [md]allocx() in a tight loop while advancing time rapidly to verify
435 	 * the ticker triggers purging.
436 	 */
437 
438 	size_t tcache_max;
439 	size_t sz = sizeof(size_t);
440 	assert_d_eq(mallctl("arenas.tcache_max", (void *)&tcache_max, &sz, NULL,
441 	    0), 0, "Unexpected mallctl failure");
442 	large = nallocx(tcache_max + 1, flags);
443 
444 	do_purge(arena_ind);
445 	uint64_t dirty_npurge0 = get_arena_dirty_npurge(arena_ind);
446 	uint64_t muzzy_npurge0 = get_arena_muzzy_npurge(arena_ind);
447 
448 	for (unsigned i = 0; i < NPS; i++) {
449 		ps[i] = do_mallocx(large, flags);
450 	}
451 
452 	nupdates_mock = 0;
453 	nstime_init(&time_mock, 0);
454 	nstime_update(&time_mock);
455 	monotonic_mock = true;
456 
457 	nstime_monotonic_orig = nstime_monotonic;
458 	nstime_update_orig = nstime_update;
459 	nstime_monotonic = nstime_monotonic_mock;
460 	nstime_update = nstime_update_mock;
461 
462 	for (unsigned i = 0; i < NPS; i += 2) {
463 		dallocx(ps[i], flags);
464 		unsigned nupdates0 = nupdates_mock;
465 		do_decay(arena_ind);
466 		assert_u_gt(nupdates_mock, nupdates0,
467 		    "Expected nstime_update() to be called");
468 	}
469 
470 	decay_ticker_helper(arena_ind, flags, true, ddt, dirty_npurge0,
471 	    muzzy_npurge0, true);
472 	decay_ticker_helper(arena_ind, flags, false, ddt+mdt, dirty_npurge0,
473 	    muzzy_npurge0, false);
474 
475 	do_arena_destroy(arena_ind);
476 
477 	nstime_monotonic = nstime_monotonic_orig;
478 	nstime_update = nstime_update_orig;
479 #undef NPS
480 }
481 TEST_END
482 
TEST_BEGIN(test_decay_nonmonotonic)483 TEST_BEGIN(test_decay_nonmonotonic) {
484 	test_skip_if(check_background_thread_enabled());
485 #define NPS (SMOOTHSTEP_NSTEPS + 1)
486 	int flags = (MALLOCX_ARENA(0) | MALLOCX_TCACHE_NONE);
487 	void *ps[NPS];
488 	uint64_t npurge0 = 0;
489 	uint64_t npurge1 = 0;
490 	size_t sz, large0;
491 	unsigned i, nupdates0;
492 
493 	sz = sizeof(size_t);
494 	assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL,
495 	    0), 0, "Unexpected mallctl failure");
496 
497 	assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
498 	    "Unexpected mallctl failure");
499 	do_epoch();
500 	sz = sizeof(uint64_t);
501 	npurge0 = get_arena_npurge(0);
502 
503 	nupdates_mock = 0;
504 	nstime_init(&time_mock, 0);
505 	nstime_update(&time_mock);
506 	monotonic_mock = false;
507 
508 	nstime_monotonic_orig = nstime_monotonic;
509 	nstime_update_orig = nstime_update;
510 	nstime_monotonic = nstime_monotonic_mock;
511 	nstime_update = nstime_update_mock;
512 
513 	for (i = 0; i < NPS; i++) {
514 		ps[i] = mallocx(large0, flags);
515 		assert_ptr_not_null(ps[i], "Unexpected mallocx() failure");
516 	}
517 
518 	for (i = 0; i < NPS; i++) {
519 		dallocx(ps[i], flags);
520 		nupdates0 = nupdates_mock;
521 		assert_d_eq(mallctl("arena.0.decay", NULL, NULL, NULL, 0), 0,
522 		    "Unexpected arena.0.decay failure");
523 		assert_u_gt(nupdates_mock, nupdates0,
524 		    "Expected nstime_update() to be called");
525 	}
526 
527 	do_epoch();
528 	sz = sizeof(uint64_t);
529 	npurge1 = get_arena_npurge(0);
530 
531 	if (config_stats) {
532 		assert_u64_eq(npurge0, npurge1, "Unexpected purging occurred");
533 	}
534 
535 	nstime_monotonic = nstime_monotonic_orig;
536 	nstime_update = nstime_update_orig;
537 #undef NPS
538 }
539 TEST_END
540 
TEST_BEGIN(test_decay_now)541 TEST_BEGIN(test_decay_now) {
542 	test_skip_if(check_background_thread_enabled());
543 
544 	unsigned arena_ind = do_arena_create(0, 0);
545 	assert_zu_eq(get_arena_pdirty(arena_ind), 0, "Unexpected dirty pages");
546 	assert_zu_eq(get_arena_pmuzzy(arena_ind), 0, "Unexpected muzzy pages");
547 	size_t sizes[] = {16, PAGE<<2, HUGEPAGE<<2};
548 	/* Verify that dirty/muzzy pages never linger after deallocation. */
549 	for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) {
550 		size_t size = sizes[i];
551 		generate_dirty(arena_ind, size);
552 		assert_zu_eq(get_arena_pdirty(arena_ind), 0,
553 		    "Unexpected dirty pages");
554 		assert_zu_eq(get_arena_pmuzzy(arena_ind), 0,
555 		    "Unexpected muzzy pages");
556 	}
557 	do_arena_destroy(arena_ind);
558 }
559 TEST_END
560 
TEST_BEGIN(test_decay_never)561 TEST_BEGIN(test_decay_never) {
562 	test_skip_if(check_background_thread_enabled());
563 
564 	unsigned arena_ind = do_arena_create(-1, -1);
565 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
566 	assert_zu_eq(get_arena_pdirty(arena_ind), 0, "Unexpected dirty pages");
567 	assert_zu_eq(get_arena_pmuzzy(arena_ind), 0, "Unexpected muzzy pages");
568 	size_t sizes[] = {16, PAGE<<2, HUGEPAGE<<2};
569 	void *ptrs[sizeof(sizes)/sizeof(size_t)];
570 	for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) {
571 		ptrs[i] = do_mallocx(sizes[i], flags);
572 	}
573 	/* Verify that each deallocation generates additional dirty pages. */
574 	size_t pdirty_prev = get_arena_pdirty(arena_ind);
575 	size_t pmuzzy_prev = get_arena_pmuzzy(arena_ind);
576 	assert_zu_eq(pdirty_prev, 0, "Unexpected dirty pages");
577 	assert_zu_eq(pmuzzy_prev, 0, "Unexpected muzzy pages");
578 	for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) {
579 		dallocx(ptrs[i], flags);
580 		size_t pdirty = get_arena_pdirty(arena_ind);
581 		size_t pmuzzy = get_arena_pmuzzy(arena_ind);
582 		assert_zu_gt(pdirty, pdirty_prev,
583 		    "Expected dirty pages to increase.");
584 		assert_zu_eq(pmuzzy, 0, "Unexpected muzzy pages");
585 		pdirty_prev = pdirty;
586 	}
587 	do_arena_destroy(arena_ind);
588 }
589 TEST_END
590 
591 int
main(void)592 main(void) {
593 	return test(
594 	    test_decay_ticks,
595 	    test_decay_ticker,
596 	    test_decay_nonmonotonic,
597 	    test_decay_now,
598 	    test_decay_never);
599 }
600