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