diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index b5020a870c910f87c1d850d4c17451ead8d092cb..f020518f218fc1407622e40e883950a04654918f 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -313,3 +313,6 @@ target_link_libraries(grp_alloc.test unit) add_executable(latch.test latch.c core_test_utils.c) target_link_libraries(latch.test core unit stat) + +add_executable(memtx_allocator.test memtx_allocator.cc box_test_utils.c) +target_link_libraries(memtx_allocator.test unit core box) diff --git a/test/unit/memtx_allocator.cc b/test/unit/memtx_allocator.cc new file mode 100644 index 0000000000000000000000000000000000000000..760a63a335ab4fea596b075a0d48fbface02b994 --- /dev/null +++ b/test/unit/memtx_allocator.cc @@ -0,0 +1,268 @@ +#include "box/allocator.h" +#include "box/memtx_allocator.h" +#include "box/tuple.h" +#include "box/tuple_format.h" +#include "fiber.h" +#include "memory.h" +#include "say.h" +#include "small/mempool.h" +#include "small/slab_arena.h" +#include "small/slab_cache.h" +#include "small/quota.h" +#include "unit.h" + +#define ARENA_SIZE (16 * 1024 * 1024) +#define SLAB_SIZE (1024 * 1024) +#define OBJSIZE_MIN 16 +#define GRANULARITY 8 +#define ALLOC_FACTOR 1.05 + +static struct tuple_format *test_tuple_format; + +static struct tuple * +test_tuple_new(struct tuple_format *format, const char *data, const char *end) +{ + assert(format == test_tuple_format); + assert(data == NULL); + assert(end == NULL); + (void)data; + (void)end; + size_t size = sizeof(struct tuple); + struct tuple *tuple = MemtxAllocator<SmallAlloc>::alloc_tuple(size); + tuple_create(tuple, /*local_refs=*/0, tuple_format_id(format), + /*data_offset=*/size, /*bsize=*/0, /*make_compact=*/true); + return tuple; +} + +static void +test_tuple_delete(struct tuple_format *format, struct tuple *tuple) +{ + assert(format == test_tuple_format); + (void)format; + assert(tuple_is_unreferenced(tuple)); + MemtxAllocator<SmallAlloc>::free_tuple(tuple); +} + +static struct tuple_format_vtab test_tuple_format_vtab = { + .tuple_delete = test_tuple_delete, + .tuple_new = test_tuple_new, +}; + +static struct tuple * +alloc_tuple() +{ + struct tuple *tuple = tuple_new(test_tuple_format, NULL, NULL); + fail_if(tuple == NULL); + return tuple; +} + +static void +free_tuple(struct tuple *tuple) +{ + tuple_delete(tuple); +} + +struct alloc_tuple_count_ctx { + int count; +}; + +static int +alloc_tuple_count_cb(const void *stats_, void *ctx_) +{ + const struct mempool_stats *stats = + (const struct mempool_stats *)stats_; + struct alloc_tuple_count_ctx *ctx = + (struct alloc_tuple_count_ctx *)ctx_; + ctx->count += stats->objcount; + return 0; +} + +static int +alloc_tuple_count() +{ + /* Trigger garbage collection before checking count. */ + struct tuple *tuple = alloc_tuple(); + fail_if(tuple == NULL); + free_tuple(tuple); + struct alloc_tuple_count_ctx ctx; + struct allocator_stats unused; + ctx.count = 0; + SmallAlloc::stats(&unused, alloc_tuple_count_cb, &ctx); + return ctx.count; +} + +/** + * Checks allocator statistics after allocating and freeing some tuples. + */ +static void +test_alloc_stats() +{ + plan(5); + header(); + + is(alloc_tuple_count(), 0, "count before alloc"); + struct tuple *tuple[15]; + for (int i = 0; i < 10; ++i) + tuple[i] = alloc_tuple(); + is(alloc_tuple_count(), 10, "count after alloc 1"); + for (int i = 10; i < 15; ++i) + tuple[i] = alloc_tuple(); + is(alloc_tuple_count(), 15, "count after alloc 2"); + for (int i = 0; i < 5; ++i) + free_tuple(tuple[i]); + is(alloc_tuple_count(), 10, "count after free 1"); + for (int i = 5; i < 15; ++i) + free_tuple(tuple[i]); + is(alloc_tuple_count(), 0, "count after free 2"); + + footer(); + check_plan(); +} + +/** + * Checks that freeing of a tuple is delayed if there is a read view that was + * created after the tuple was allocated. + */ +static void +test_free_delayed_if_alloc_before_read_view() +{ + plan(4); + header(); + + is(alloc_tuple_count(), 0, "count before alloc"); + struct tuple *tuple = alloc_tuple(); + is(alloc_tuple_count(), 1, "count after alloc"); + memtx_allocators_enter_delayed_free_mode(); + free_tuple(tuple); + is(alloc_tuple_count(), 1, "count after free"); + memtx_allocators_leave_delayed_free_mode(); + is(alloc_tuple_count(), 0, "count after read view closed"); + + footer(); + check_plan(); +} + +/** + * Checks that freeing of a tuple is delayed until the last read view from + * which it is visible is closed. + */ +static void +test_free_delayed_until_all_read_views_closed() +{ + plan(5); + header(); + + is(alloc_tuple_count(), 0, "count before alloc"); + struct tuple *tuple = alloc_tuple(); + is(alloc_tuple_count(), 1, "count after alloc"); + memtx_allocators_enter_delayed_free_mode(); + memtx_allocators_enter_delayed_free_mode(); + free_tuple(tuple); + is(alloc_tuple_count(), 1, "count after free"); + memtx_allocators_leave_delayed_free_mode(); + is(alloc_tuple_count(), 1, "count after first read view closed"); + memtx_allocators_leave_delayed_free_mode(); + is(alloc_tuple_count(), 0, "count after second read view closed"); + + footer(); + check_plan(); +} + +/** + * Checks that freeing of a tuple is not delayed if it was allocated after + * the last read view was created. + */ +static void +test_free_not_delayed_if_alloc_after_read_view() +{ + plan(3); + header(); + + memtx_allocators_enter_delayed_free_mode(); + is(alloc_tuple_count(), 0, "count before alloc"); + struct tuple *tuple = alloc_tuple(); + is(alloc_tuple_count(), 1, "count after alloc"); + free_tuple(tuple); + is(alloc_tuple_count(), 0, "count after free"); + memtx_allocators_leave_delayed_free_mode(); + + footer(); + check_plan(); +} + +/** + * Checks that freeing of a temporary tuple is never delayed. + */ +static void +test_free_not_delayed_if_temporary() +{ + plan(3); + header(); + + is(alloc_tuple_count(), 0, "count before alloc"); + struct tuple *tuple = alloc_tuple(); + is(alloc_tuple_count(), 1, "count after alloc"); + tuple_set_flag(tuple, TUPLE_IS_TEMPORARY); + memtx_allocators_enter_delayed_free_mode(); + free_tuple(tuple); + is(alloc_tuple_count(), 0, "count after free"); + memtx_allocators_leave_delayed_free_mode(); + + footer(); + check_plan(); +} + +static int +test_main() +{ + plan(5); + header(); + + test_alloc_stats(); + test_free_delayed_if_alloc_before_read_view(); + test_free_delayed_until_all_read_views_closed(); + test_free_not_delayed_if_alloc_after_read_view(); + test_free_not_delayed_if_temporary(); + + footer(); + return check_plan(); +} + +int +main() +{ + say_logger_init("/dev/null", S_INFO, /*nonblock=*/true, "plain", + /*background=*/false); + memory_init(); + fiber_init(fiber_c_invoke); + tuple_init(NULL); + struct quota quota; + quota_init("a, QUOTA_MAX); + struct slab_arena arena; + tuple_arena_create(&arena, "a, ARENA_SIZE, SLAB_SIZE, + /*dontdump=*/false, "test"); + struct slab_cache cache; + slab_cache_create(&cache, &arena); + float actual_alloc_factor; + allocator_settings alloc_settings; + allocator_settings_init(&alloc_settings, &cache, OBJSIZE_MIN, + GRANULARITY, ALLOC_FACTOR, + &actual_alloc_factor, "a); + memtx_allocators_init(&alloc_settings); + test_tuple_format = simple_tuple_format_new( + &test_tuple_format_vtab, /*engine=*/NULL, + /*keys=*/NULL, /*key_count=*/0); + fail_if(test_tuple_format == NULL); + + int rc = test_main(); + + tuple_format_delete(test_tuple_format); + memtx_allocators_destroy(); + slab_cache_destroy(&cache); + tuple_arena_destroy(&arena); + tuple_free(); + fiber_free(); + memory_free(); + say_logger_free(); + return rc; +} diff --git a/test/unit/memtx_allocator.result b/test/unit/memtx_allocator.result new file mode 100644 index 0000000000000000000000000000000000000000..7e815d85675200dbb71dffd0496d3a4651a112a9 --- /dev/null +++ b/test/unit/memtx_allocator.result @@ -0,0 +1,43 @@ +1..5 + *** test_main *** + 1..5 + *** test_alloc_stats *** + ok 1 - count before alloc + ok 2 - count after alloc 1 + ok 3 - count after alloc 2 + ok 4 - count after free 1 + ok 5 - count after free 2 + *** test_alloc_stats: done *** +ok 1 - subtests + 1..4 + *** test_free_delayed_if_alloc_before_read_view *** + ok 1 - count before alloc + ok 2 - count after alloc + ok 3 - count after free + ok 4 - count after read view closed + *** test_free_delayed_if_alloc_before_read_view: done *** +ok 2 - subtests + 1..5 + *** test_free_delayed_until_all_read_views_closed *** + ok 1 - count before alloc + ok 2 - count after alloc + ok 3 - count after free + ok 4 - count after first read view closed + ok 5 - count after second read view closed + *** test_free_delayed_until_all_read_views_closed: done *** +ok 3 - subtests + 1..3 + *** test_free_not_delayed_if_alloc_after_read_view *** + ok 1 - count before alloc + ok 2 - count after alloc + ok 3 - count after free + *** test_free_not_delayed_if_alloc_after_read_view: done *** +ok 4 - subtests + 1..3 + *** test_free_not_delayed_if_temporary *** + ok 1 - count before alloc + ok 2 - count after alloc + ok 3 - count after free + *** test_free_not_delayed_if_temporary: done *** +ok 5 - subtests + *** test_main: done ***