#include "test/jemalloc_test.h" #include "jemalloc/internal/edata_cache.h" static void test_edata_cache_init(edata_cache_t *edata_cache) { base_t *base = base_new(TSDN_NULL, /* ind */ 1, &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); assert_ptr_not_null(base, ""); bool err = edata_cache_init(edata_cache, base); assert_false(err, ""); } static void test_edata_cache_destroy(edata_cache_t *edata_cache) { base_delete(TSDN_NULL, edata_cache->base); } TEST_BEGIN(test_edata_cache) { edata_cache_t ec; test_edata_cache_init(&ec); /* Get one */ edata_t *ed1 = edata_cache_get(TSDN_NULL, &ec); assert_ptr_not_null(ed1, ""); /* Cache should be empty */ assert_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); /* Get another */ edata_t *ed2 = edata_cache_get(TSDN_NULL, &ec); assert_ptr_not_null(ed2, ""); /* Still empty */ assert_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); /* Put one back, and the cache should now have one item */ edata_cache_put(TSDN_NULL, &ec, ed1); assert_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 1, ""); /* Reallocating should reuse the item, and leave an empty cache. */ edata_t *ed1_again = edata_cache_get(TSDN_NULL, &ec); assert_ptr_eq(ed1, ed1_again, ""); assert_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); test_edata_cache_destroy(&ec); } TEST_END static size_t ecf_count(edata_cache_fast_t *ecf) { size_t count = 0; edata_t *cur; ql_foreach(cur, &ecf->list.head, ql_link_inactive) { count++; } return count; } TEST_BEGIN(test_edata_cache_fast_simple) { edata_cache_t ec; edata_cache_fast_t ecf; test_edata_cache_init(&ec); edata_cache_fast_init(&ecf, &ec); edata_t *ed1 = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(ed1, ""); expect_zu_eq(ecf_count(&ecf), 0, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); edata_t *ed2 = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(ed2, ""); expect_zu_eq(ecf_count(&ecf), 0, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); edata_cache_fast_put(TSDN_NULL, &ecf, ed1); expect_zu_eq(ecf_count(&ecf), 1, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); edata_cache_fast_put(TSDN_NULL, &ecf, ed2); expect_zu_eq(ecf_count(&ecf), 2, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); /* LIFO ordering. */ expect_ptr_eq(ed2, edata_cache_fast_get(TSDN_NULL, &ecf), ""); expect_zu_eq(ecf_count(&ecf), 1, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); expect_ptr_eq(ed1, edata_cache_fast_get(TSDN_NULL, &ecf), ""); expect_zu_eq(ecf_count(&ecf), 0, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); test_edata_cache_destroy(&ec); } TEST_END TEST_BEGIN(test_edata_cache_fill) { edata_cache_t ec; edata_cache_fast_t ecf; test_edata_cache_init(&ec); edata_cache_fast_init(&ecf, &ec); edata_t *allocs[EDATA_CACHE_FAST_FILL * 2]; /* * If the fallback cache can't satisfy the request, we shouldn't do * extra allocations until compelled to. Put half the fill goal in the * fallback. */ for (int i = 0; i < EDATA_CACHE_FAST_FILL / 2; i++) { allocs[i] = edata_cache_get(TSDN_NULL, &ec); } for (int i = 0; i < EDATA_CACHE_FAST_FILL / 2; i++) { edata_cache_put(TSDN_NULL, &ec, allocs[i]); } expect_zu_eq(EDATA_CACHE_FAST_FILL / 2, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); allocs[0] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_zu_eq(EDATA_CACHE_FAST_FILL / 2 - 1, ecf_count(&ecf), "Should have grabbed all edatas available but no more."); for (int i = 1; i < EDATA_CACHE_FAST_FILL / 2; i++) { allocs[i] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(allocs[i], ""); } expect_zu_eq(0, ecf_count(&ecf), ""); /* When forced, we should alloc from the base. */ edata_t *edata = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(edata, ""); expect_zu_eq(0, ecf_count(&ecf), "Allocated more than necessary"); expect_zu_eq(0, atomic_load_zu(&ec.count, ATOMIC_RELAXED), "Allocated more than necessary"); /* * We should correctly fill in the common case where the fallback isn't * exhausted, too. */ for (int i = 0; i < EDATA_CACHE_FAST_FILL * 2; i++) { allocs[i] = edata_cache_get(TSDN_NULL, &ec); expect_ptr_not_null(allocs[i], ""); } for (int i = 0; i < EDATA_CACHE_FAST_FILL * 2; i++) { edata_cache_put(TSDN_NULL, &ec, allocs[i]); } allocs[0] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_zu_eq(EDATA_CACHE_FAST_FILL - 1, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); for (int i = 1; i < EDATA_CACHE_FAST_FILL; i++) { expect_zu_eq(EDATA_CACHE_FAST_FILL - i, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); allocs[i] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(allocs[i], ""); } expect_zu_eq(0, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); allocs[0] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_zu_eq(EDATA_CACHE_FAST_FILL - 1, ecf_count(&ecf), ""); expect_zu_eq(0, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); for (int i = 1; i < EDATA_CACHE_FAST_FILL; i++) { expect_zu_eq(EDATA_CACHE_FAST_FILL - i, ecf_count(&ecf), ""); expect_zu_eq(0, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); allocs[i] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(allocs[i], ""); } expect_zu_eq(0, ecf_count(&ecf), ""); expect_zu_eq(0, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); test_edata_cache_destroy(&ec); } TEST_END TEST_BEGIN(test_edata_cache_disable) { edata_cache_t ec; edata_cache_fast_t ecf; test_edata_cache_init(&ec); edata_cache_fast_init(&ecf, &ec); for (int i = 0; i < EDATA_CACHE_FAST_FILL; i++) { edata_t *edata = edata_cache_get(TSDN_NULL, &ec); expect_ptr_not_null(edata, ""); edata_cache_fast_put(TSDN_NULL, &ecf, edata); } expect_zu_eq(EDATA_CACHE_FAST_FILL, ecf_count(&ecf), ""); expect_zu_eq(0, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); edata_cache_fast_disable(TSDN_NULL, &ecf); expect_zu_eq(0, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL, atomic_load_zu(&ec.count, ATOMIC_RELAXED), "Disabling should flush"); edata_t *edata = edata_cache_fast_get(TSDN_NULL, &ecf); expect_zu_eq(0, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL - 1, atomic_load_zu(&ec.count, ATOMIC_RELAXED), "Disabled ecf should forward on get"); edata_cache_fast_put(TSDN_NULL, &ecf, edata); expect_zu_eq(0, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL, atomic_load_zu(&ec.count, ATOMIC_RELAXED), "Disabled ecf should forward on put"); test_edata_cache_destroy(&ec); } TEST_END int main(void) { return test( test_edata_cache, test_edata_cache_fast_simple, test_edata_cache_fill, test_edata_cache_disable); }