Home | History | Annotate | Line # | Download | only in unit
      1  1.1  christos #include "test/jemalloc_test.h"
      2  1.1  christos #include "test/sleep.h"
      3  1.1  christos 
      4  1.1  christos static void
      5  1.1  christos sleep_for_background_thread_interval() {
      6  1.1  christos 	/*
      7  1.1  christos 	 * The sleep interval set in our .sh file is 50ms.  So it likely will
      8  1.1  christos 	 * run if we sleep for four times that.
      9  1.1  christos 	 */
     10  1.1  christos 	sleep_ns(200 * 1000 * 1000);
     11  1.1  christos }
     12  1.1  christos 
     13  1.1  christos static unsigned
     14  1.1  christos create_arena() {
     15  1.1  christos 	unsigned arena_ind;
     16  1.1  christos 	size_t sz;
     17  1.1  christos 
     18  1.1  christos 	sz = sizeof(unsigned);
     19  1.1  christos 	expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 2),
     20  1.1  christos 	    0, "Unexpected mallctl() failure");
     21  1.1  christos 	return arena_ind;
     22  1.1  christos }
     23  1.1  christos 
     24  1.1  christos static size_t
     25  1.1  christos get_empty_ndirty(unsigned arena_ind) {
     26  1.1  christos 	int err;
     27  1.1  christos 	size_t ndirty_huge;
     28  1.1  christos 	size_t ndirty_nonhuge;
     29  1.1  christos 	uint64_t epoch = 1;
     30  1.1  christos 	size_t sz = sizeof(epoch);
     31  1.1  christos 	err = je_mallctl("epoch", (void *)&epoch, &sz, (void *)&epoch,
     32  1.1  christos 	    sizeof(epoch));
     33  1.1  christos 	expect_d_eq(0, err, "Unexpected mallctl() failure");
     34  1.1  christos 
     35  1.1  christos 	size_t mib[6];
     36  1.1  christos 	size_t miblen = sizeof(mib)/sizeof(mib[0]);
     37  1.1  christos 	err = mallctlnametomib(
     38  1.1  christos 	    "stats.arenas.0.hpa_shard.empty_slabs.ndirty_nonhuge", mib,
     39  1.1  christos 	    &miblen);
     40  1.1  christos 	expect_d_eq(0, err, "Unexpected mallctlnametomib() failure");
     41  1.1  christos 
     42  1.1  christos 	sz = sizeof(ndirty_nonhuge);
     43  1.1  christos 	mib[2] = arena_ind;
     44  1.1  christos 	err = mallctlbymib(mib, miblen, &ndirty_nonhuge, &sz, NULL, 0);
     45  1.1  christos 	expect_d_eq(0, err, "Unexpected mallctlbymib() failure");
     46  1.1  christos 
     47  1.1  christos 	err = mallctlnametomib(
     48  1.1  christos 	    "stats.arenas.0.hpa_shard.empty_slabs.ndirty_huge", mib,
     49  1.1  christos 	    &miblen);
     50  1.1  christos 	expect_d_eq(0, err, "Unexpected mallctlnametomib() failure");
     51  1.1  christos 
     52  1.1  christos 	sz = sizeof(ndirty_huge);
     53  1.1  christos 	mib[2] = arena_ind;
     54  1.1  christos 	err = mallctlbymib(mib, miblen, &ndirty_huge, &sz, NULL, 0);
     55  1.1  christos 	expect_d_eq(0, err, "Unexpected mallctlbymib() failure");
     56  1.1  christos 
     57  1.1  christos 	return ndirty_huge + ndirty_nonhuge;
     58  1.1  christos }
     59  1.1  christos 
     60  1.1  christos static void
     61  1.1  christos set_background_thread_enabled(bool enabled) {
     62  1.1  christos 	int err;
     63  1.1  christos 	err = je_mallctl("background_thread", NULL, NULL, &enabled,
     64  1.1  christos 	    sizeof(enabled));
     65  1.1  christos 	expect_d_eq(0, err, "Unexpected mallctl failure");
     66  1.1  christos }
     67  1.1  christos 
     68  1.1  christos static void
     69  1.1  christos wait_until_thread_is_enabled(unsigned arena_id) {
     70  1.1  christos 	tsd_t* tsd = tsd_fetch();
     71  1.1  christos 
     72  1.1  christos 	bool sleeping = false;
     73  1.1  christos 	int iterations = 0;
     74  1.1  christos 	do {
     75  1.1  christos 		background_thread_info_t *info =
     76  1.1  christos 		    background_thread_info_get(arena_id);
     77  1.1  christos 		malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
     78  1.1  christos 		malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
     79  1.1  christos 		sleeping = background_thread_indefinite_sleep(info);
     80  1.1  christos 		assert_d_lt(iterations, UINT64_C(1000000),
     81  1.1  christos 		    "Waiting for a thread to start for too long");
     82  1.1  christos 	} while (!sleeping);
     83  1.1  christos }
     84  1.1  christos 
     85  1.1  christos static void
     86  1.1  christos expect_purging(unsigned arena_ind, bool expect_deferred) {
     87  1.1  christos 	size_t empty_ndirty;
     88  1.1  christos 
     89  1.1  christos 	empty_ndirty = get_empty_ndirty(arena_ind);
     90  1.1  christos 	expect_zu_eq(0, empty_ndirty, "Expected arena to start unused.");
     91  1.1  christos 
     92  1.1  christos 	/*
     93  1.1  christos 	 * It's possible that we get unlucky with our stats collection timing,
     94  1.1  christos 	 * and the background thread runs in between the deallocation and the
     95  1.1  christos 	 * stats collection.  So we retry 10 times, and see if we *ever* see
     96  1.1  christos 	 * deferred reclamation.
     97  1.1  christos 	 */
     98  1.1  christos 	bool observed_dirty_page = false;
     99  1.1  christos 	for (int i = 0; i < 10; i++) {
    100  1.1  christos 		void *ptr = mallocx(PAGE,
    101  1.1  christos 		    MALLOCX_TCACHE_NONE | MALLOCX_ARENA(arena_ind));
    102  1.1  christos 		empty_ndirty = get_empty_ndirty(arena_ind);
    103  1.1  christos 		expect_zu_eq(0, empty_ndirty, "All pages should be active");
    104  1.1  christos 		dallocx(ptr, MALLOCX_TCACHE_NONE);
    105  1.1  christos 		empty_ndirty = get_empty_ndirty(arena_ind);
    106  1.1  christos 		if (expect_deferred) {
    107  1.1  christos 			expect_true(empty_ndirty == 0 || empty_ndirty == 1 ||
    108  1.1  christos 			    opt_prof, "Unexpected extra dirty page count: %zu",
    109  1.1  christos 			    empty_ndirty);
    110  1.1  christos 		} else {
    111  1.1  christos 			assert_zu_eq(0, empty_ndirty,
    112  1.1  christos 			    "Saw dirty pages without deferred purging");
    113  1.1  christos 		}
    114  1.1  christos 		if (empty_ndirty > 0) {
    115  1.1  christos 			observed_dirty_page = true;
    116  1.1  christos 			break;
    117  1.1  christos 		}
    118  1.1  christos 	}
    119  1.1  christos 	expect_b_eq(expect_deferred, observed_dirty_page, "");
    120  1.1  christos 
    121  1.1  christos 	/*
    122  1.1  christos 	 * Under high concurrency / heavy test load (e.g. using run_test.sh),
    123  1.1  christos 	 * the background thread may not get scheduled for a longer period of
    124  1.1  christos 	 * time.  Retry 100 times max before bailing out.
    125  1.1  christos 	 */
    126  1.1  christos 	unsigned retry = 0;
    127  1.1  christos 	while ((empty_ndirty = get_empty_ndirty(arena_ind)) > 0 &&
    128  1.1  christos 	    expect_deferred && (retry++ < 100)) {
    129  1.1  christos 		sleep_for_background_thread_interval();
    130  1.1  christos 	}
    131  1.1  christos 
    132  1.1  christos 	expect_zu_eq(0, empty_ndirty, "Should have seen a background purge");
    133  1.1  christos }
    134  1.1  christos 
    135  1.1  christos TEST_BEGIN(test_hpa_background_thread_purges) {
    136  1.1  christos 	test_skip_if(!config_stats);
    137  1.1  christos 	test_skip_if(!hpa_supported());
    138  1.1  christos 	test_skip_if(!have_background_thread);
    139  1.1  christos 	/* Skip since guarded pages cannot be allocated from hpa. */
    140  1.1  christos 	test_skip_if(san_guard_enabled());
    141  1.1  christos 
    142  1.1  christos 	unsigned arena_ind = create_arena();
    143  1.1  christos 	/*
    144  1.1  christos 	 * Our .sh sets dirty mult to 0, so all dirty pages should get purged
    145  1.1  christos 	 * any time any thread frees.
    146  1.1  christos 	 */
    147  1.1  christos 	expect_purging(arena_ind, /* expect_deferred */ true);
    148  1.1  christos }
    149  1.1  christos TEST_END
    150  1.1  christos 
    151  1.1  christos TEST_BEGIN(test_hpa_background_thread_enable_disable) {
    152  1.1  christos 	test_skip_if(!config_stats);
    153  1.1  christos 	test_skip_if(!hpa_supported());
    154  1.1  christos 	test_skip_if(!have_background_thread);
    155  1.1  christos 	/* Skip since guarded pages cannot be allocated from hpa. */
    156  1.1  christos 	test_skip_if(san_guard_enabled());
    157  1.1  christos 
    158  1.1  christos 	unsigned arena_ind = create_arena();
    159  1.1  christos 
    160  1.1  christos 	set_background_thread_enabled(false);
    161  1.1  christos 	expect_purging(arena_ind, false);
    162  1.1  christos 
    163  1.1  christos 	set_background_thread_enabled(true);
    164  1.1  christos 	wait_until_thread_is_enabled(arena_ind);
    165  1.1  christos 	expect_purging(arena_ind, true);
    166  1.1  christos }
    167  1.1  christos TEST_END
    168  1.1  christos 
    169  1.1  christos int
    170  1.1  christos main(void) {
    171  1.1  christos 	/*
    172  1.1  christos 	 * OK, this is a sort of nasty hack.  We don't want to add *another*
    173  1.1  christos 	 * config option for HPA (the intent is that it becomes available on
    174  1.1  christos 	 * more platforms over time, and we're trying to prune back config
    175  1.1  christos 	 * options generally.  But we'll get initialization errors on other
    176  1.1  christos 	 * platforms if we set hpa:true in the MALLOC_CONF (even if we set
    177  1.1  christos 	 * abort_conf:false as well).  So we reach into the internals and set
    178  1.1  christos 	 * them directly, but only if we know that we're actually going to do
    179  1.1  christos 	 * something nontrivial in the tests.
    180  1.1  christos 	 */
    181  1.1  christos 	if (config_stats && hpa_supported() && have_background_thread) {
    182  1.1  christos 		opt_hpa = true;
    183  1.1  christos 		opt_background_thread = true;
    184  1.1  christos 	}
    185  1.1  christos 	return test_no_reentrancy(
    186  1.1  christos 	    test_hpa_background_thread_purges,
    187  1.1  christos 	    test_hpa_background_thread_enable_disable);
    188  1.1  christos }
    189