diff --git a/src/exports.h b/src/exports.h index 6d830318077e117ef6320e489e20519554f7f36e..3cfac77c0d1841cfaa13af17802928267965225f 100644 --- a/src/exports.h +++ b/src/exports.h @@ -36,6 +36,10 @@ EXPORT(box_latch_lock) EXPORT(box_latch_new) EXPORT(box_latch_trylock) EXPORT(box_latch_unlock) +EXPORT(box_region_aligned_alloc) +EXPORT(box_region_alloc) +EXPORT(box_region_truncate) +EXPORT(box_region_used) EXPORT(box_replace) EXPORT(box_return_mp) EXPORT(box_return_tuple) diff --git a/src/lib/core/fiber.c b/src/lib/core/fiber.c index 223c841df1cb7cce71a11de725493a8d2b12333f..fab7740b23f28d3199f1ecddb458e710b10108c8 100644 --- a/src/lib/core/fiber.c +++ b/src/lib/core/fiber.c @@ -1368,6 +1368,36 @@ fiber_top_disable(void) } #endif /* ENABLE_FIBER_TOP */ +size_t +box_region_used(void) +{ + return region_used(&fiber()->gc); +} + +void * +box_region_alloc(size_t size) +{ + void *res = region_alloc(&fiber()->gc, size); + if (res == NULL) + diag_set(OutOfMemory, size, "region_alloc", "data"); + return res; +} + +void * +box_region_aligned_alloc(size_t size, size_t alignment) +{ + void *res = region_aligned_alloc(&fiber()->gc, size, alignment); + if (res == NULL) + diag_set(OutOfMemory, size, "region_alloc", "aligned data"); + return res; +} + +void +box_region_truncate(size_t size) +{ + return region_truncate(&fiber()->gc, size); +} + void cord_create(struct cord *cord, const char *name) { diff --git a/src/lib/core/fiber.h b/src/lib/core/fiber.h index 16ee9f41400ab31ed13e0ef1413b4635d9337105..539e5c8e7b8097c337429d421bc647eeafede2af 100644 --- a/src/lib/core/fiber.h +++ b/src/lib/core/fiber.h @@ -386,6 +386,88 @@ struct slab_cache; API_EXPORT struct slab_cache * cord_slab_cache(void); +/** + * box region allocator + * + * It is the region allocator from the small library. It is useful + * for allocating tons of small objects and free them at once. + * + * Typical usage is illustrated in the sketch below. + * + * | size_t region_svp = box_region_used(); + * | while (<...>) { + * | char *buf = box_region_alloc(<...>); + * | <...> + * | } + * | box_region_truncate(region_svp); + * + * There are module API functions that return a result on + * this region. In this case a module is responsible to free the + * result: + * + * | size_t region_svp = box_region_used(); + * | char *buf = box_<...>(<...>); + * | <...> + * | box_region_truncate(region_svp); + * + * This API provides better compatibility guarantees over using + * the small library directly in a module. A binary layout of + * internal structures may be changed in a future, but + * <box_region_*>() functions will remain API and ABI compatible. + * + * Data allocated on the region are guaranteed to be valid until + * a fiber yield or a call of a function from the certain set: + * + * - Related to transactions. + * - Ones that may cause box initialization (box.cfg()). + * - Ones that may involve SQL execution. + * + * FIXME: Provide more strict list of functions, which may + * invalidate the data: ones that may lead to calling of + * fiber_gc(). + * + * It is safe to call simple box APIs around tuples, key_defs and + * so on -- they don't invalidate the allocated data. + * + * Don't assume this region as fiber local. This is an + * implementation detail and may be changed in a future. + */ + +/** How much memory is used by the box region. */ +API_EXPORT size_t +box_region_used(void); + +/** + * Allocate size bytes from the box region. + * + * Don't use this function to allocate a memory block for a value + * or array of values of a type with alignment requirements. A + * violation of alignment requirements leads to undefined + * behaviour. + * + * In case of a memory error set a diag and return NULL. + * @sa <box_error_last>(). + */ +API_EXPORT void * +box_region_alloc(size_t size); + +/** + * Allocate size bytes from the box region with given alignment. + * + * Alignment must be a power of 2. + * + * In case of a memory error set a diag and return NULL. + * @sa <box_error_last>(). + */ +API_EXPORT void * +box_region_aligned_alloc(size_t size, size_t alignment); + +/** + * Truncate the box region to the given size. + */ +API_EXPORT void +box_region_truncate(size_t size); + /** \endcond public */ /** diff --git a/test/app-tap/module_api.c b/test/app-tap/module_api.c index a79fbed0d5aa5f566c51cad33369e099774b98fa..12d20e8864b3f96565a012170bf2e4888116a0d7 100644 --- a/test/app-tap/module_api.c +++ b/test/app-tap/module_api.c @@ -15,6 +15,10 @@ #define STR2(x) #x #define STR(x) STR2(x) +#ifndef lengthof +#define lengthof(array) (sizeof(array) / sizeof((array)[0])) +#endif + /* Test for constants */ static const char *consts[] = { PACKAGE_VERSION, @@ -451,6 +455,85 @@ test_iscallable(lua_State *L) return 1; } +/* {{{ test_box_region */ + +/** + * Verify basic usage of box region. + */ +static int +test_box_region(struct lua_State *L) +{ + size_t region_svp_0 = box_region_used(); + + /* Verify allocation and box_region_used(). */ + size_t size_arr[] = {1, 7, 19, 10 * 1024 * 1024, 1, 18, 1024}; + size_t region_svp_arr[lengthof(size_arr)]; + char *ptr_arr[lengthof(size_arr)]; + for (size_t i = 0; i < lengthof(size_arr); ++i) { + size_t size = size_arr[i]; + size_t region_svp = box_region_used(); + char *ptr = box_region_alloc(size); + + /* Verify box_region_used() after allocation. */ + assert(box_region_used() - region_svp == size); + + /* Verify that data is accessible. */ + for (char *p = ptr; p < ptr + size; ++p) + *p = 'x'; + + /* + * Save data pointer and savepoint to verify + * truncation later. + */ + ptr_arr[i] = ptr; + region_svp_arr[i] = region_svp; + } + + /* Verify truncation. */ + for (ssize_t i = lengthof(region_svp_arr) - 1; i >= 0; --i) { + box_region_truncate(region_svp_arr[i]); + assert(box_region_used() == region_svp_arr[i]); + + /* + * Verify that all data before this savepoint + * still accessible. + */ + for (ssize_t j = 0; j < i; ++j) { + size_t size = size_arr[j]; + char *ptr = ptr_arr[j]; + for (char *p = ptr; p < ptr + size; ++p) { + assert(*p == 'x' || *p == 'y'); + *p = 'y'; + } + } + } + assert(box_region_used() == region_svp_0); + + /* Verify aligned allocation. */ + size_t a_size_arr[] = {1, 3, 5, 7, 11, 13, 17, 19}; + size_t alignment_arr[] = {1, 2, 4, 8, 16, 32, 64}; + for (size_t s = 0; s < lengthof(a_size_arr); ++s) { + for (size_t a = 0; a < lengthof(alignment_arr); ++a) { + size_t size = a_size_arr[s]; + size_t alignment = alignment_arr[a]; + char *ptr = box_region_aligned_alloc(size, alignment); + assert((uintptr_t) ptr % alignment == 0); + + /* Data is accessible. */ + for (char *p = ptr; p < ptr + size; ++p) + *p = 'x'; + } + } + + /* Clean up. */ + box_region_truncate(region_svp_0); + + lua_pushboolean(L, true); + return 1; +} + +/* }}} test_box_region */ + LUA_API int luaopen_module_api(lua_State *L) { @@ -479,6 +562,7 @@ luaopen_module_api(lua_State *L) {"test_state", test_state}, {"test_tostring", test_tostring}, {"iscallable", test_iscallable}, + {"test_box_region", test_box_region}, {NULL, NULL} }; luaL_register(L, "module_api", lib); diff --git a/test/app-tap/module_api.test.lua b/test/app-tap/module_api.test.lua index a6658cc613f57ce5a04c343e0e0fbae6bb591226..08e8add35d497947fb6d46017c39dd684a1e752a 100755 --- a/test/app-tap/module_api.test.lua +++ b/test/app-tap/module_api.test.lua @@ -117,7 +117,7 @@ local function test_iscallable(test, module) end local test = require('tap').test("module_api", function(test) - test:plan(24) + test:plan(25) local status, module = pcall(require, 'module_api') test:is(status, true, "module") test:ok(status, "module is loaded")