From c53a45ec9d417e7289f2630d1b6d3f96041dc3e9 Mon Sep 17 00:00:00 2001 From: Nikita Pettik <korablev@tarantool.org> Date: Tue, 12 Apr 2022 15:08:33 +0300 Subject: [PATCH] lua: introduce interface for prbuf Simply add Lua wrappers for recently introduced prbuf. Introduce new buffer (in addition to ibuf) - prbuf. "pr" stands for "partitioned ring-". It save all metadata in the same memory chunk provided for storage, so it can be completely restored from the 'raw' memory. API: ``` -- mem is a chunk of raw (char *) memory, of size mem_size. -- It is used for data storage. Note that available space is of less -- size due to prbuf metadata overhead. -- Returns handle to prbuf. -- require('buffer').prbuf_create(mem, mem_size) -- mem is a chunk of memory, which contains already created prbuf. -- It implies that once prbuf_create() was called with the same memory. -- If mem does not contain valid buffer - raise an appropriate error. require('buffer').prbuf_open(mem) -- Returns continuous chunk of memory with given size. May return nil -- in case if requested chunk is too large. Note that after copying -- object to returned chunk, it should be committed with prbuf:commit(); -- otherwise :prepare() may return the same chunk twice. prbuf:prepare(size) -- Commits the last prepared chunk. Undefined behaviour in case of -- committing the same chunk twice. prbuf:commit() -- Create and return prbuf_iterator. Does not fail. Created iterator -- points to nowhere - it should be adjusted with :next() call to -- the first entry. prbuf:iterator_create() -- Advance iterator position. Returns prbuf_entry or nil in case -- iterator has reached the end. Entry consists of two members: -- size and ptr. The last one is an array of characters of given size. iterator:next() ``` Usage examples: ``` local ibuf = buffer.ibuf() local prbuf_size = 100 local memory = ibuf:alloc(prbuf_size) local prbuf = buffer.prbuf_create(memory, prbuf_size) local sample_size = 4 local raw = prbuf:prepare(4) if raw == nil then -- allocation size is too large, try smaller. end raw[0] = ... ... raw[3] = ... prbuf:commit() local prbuf_recovered = buffer.prbuf_open(memory) local iter = prbuf_recovered:iterator_create() local entry = iter:next() assert(tonumber(entry.size) == 4) -- Check values stored in the buffer. assert(entry.ptr[0] == ...) entry = iter:next() -- Our buffer has only one entry. assert(entry == nil) ``` NO_DOC=<Feature for internal usage> NO_CHANGELOG=<Feature for internal usage> --- extra/exports | 6 ++ src/lua/buffer.lua | 131 ++++++++++++++++++++++++++++++++ test/app-luatest/prbuf_test.lua | 44 +++++++++++ 3 files changed, 181 insertions(+) create mode 100644 test/app-luatest/prbuf_test.lua diff --git a/extra/exports b/extra/exports index c8d2ceef46..2ecb4ef12f 100644 --- a/extra/exports +++ b/extra/exports @@ -368,6 +368,12 @@ PMurHash32 PMurHash32_Process PMurHash32_Result port_destroy +prbuf_create +prbuf_open +prbuf_prepare +prbuf_commit +prbuf_iterator_create +prbuf_iterator_next random_bytes say_logger_init say_logger_initialized diff --git a/src/lua/buffer.lua b/src/lua/buffer.lua index 182c0b015c..a93dfbc5d8 100644 --- a/src/lua/buffer.lua +++ b/src/lua/buffer.lua @@ -41,6 +41,45 @@ ibuf_reinit(struct ibuf *ibuf); void * ibuf_reserve_slow(struct ibuf *ibuf, size_t size); + +/* + * prbuf iterface; see src/lib/core/prbuf.c for details. + */ + +struct prbuf_header; +struct prbuf_record; + +struct prbuf { + struct prbuf_header *header; +}; + +struct prbuf_entry { + size_t size; + char *ptr; +}; + +struct prbuf_iterator { + struct prbuf *buf; + struct prbuf_record *current; +}; + +void +prbuf_create(struct prbuf *buf, void *mem, size_t size); + +int +prbuf_open(struct prbuf *buf, void *mem); + +void * +prbuf_prepare(struct prbuf *buf, size_t size); + +void +prbuf_commit(struct prbuf *buf); + +void +prbuf_iterator_create(struct prbuf *buf, struct prbuf_iterator *iter); + +int +prbuf_iterator_next(struct prbuf_iterator *iter, struct prbuf_entry *res); ]] local builtin = ffi.C @@ -182,6 +221,96 @@ local function ibuf_new(arg) errorf('Usage: ibuf([size])') end +local prbuf_t = ffi.typeof('struct prbuf') +local prbuf_iterator_t = ffi.typeof('struct prbuf_iterator') +local prbuf_entry_t = ffi.typeof('struct prbuf_entry') + +local function prbuf_open(mem) + if not ffi.istype(ffi.typeof('char *'), mem) then + errorf('Attempt to prbuf_open() with argument of wrong type %s', + ffi.typeof(mem)) + end + local buf = ffi.new(prbuf_t) + local rc = builtin.prbuf_open(buf, mem) + if rc ~= 0 then + errorf("Failed to open prbuf") + end + return buf +end + +local function prbuf_create(mem, size) + if not ffi.istype(ffi.typeof('char *'), mem) then + errorf('Attempt to prbuf_open() with argument of wrong type %s', + ffi.typeof(mem)) + end + local buf = ffi.new(prbuf_t) + builtin.prbuf_create(buf, mem, size) + return buf +end + +local function prbuf_prepare(buf, size) + if not ffi.istype(prbuf_t, buf) then + errorf('Attempt to call method without object, use prbuf:prepare()') + end + local ptr = builtin.prbuf_prepare(buf, size) + if ptr == nil then return nil end + return ffi.cast('char *', ptr) +end + + +local function prbuf_commit(buf) + if not ffi.istype(prbuf_t, buf) then + errorf('Attempt to call method without object, use prbuf:commit()') + end + builtin.prbuf_commit(buf) +end + +local function prbuf_iterator_create(buf) + if not ffi.istype(prbuf_t, buf) then + errorf('Attempt to call method without object, use prbuf:create()') + end + local iterator = ffi.new(prbuf_iterator_t) + builtin.prbuf_iterator_create(buf, iterator) + return iterator +end + +local prbuf_methods = { + prepare = prbuf_prepare; + commit = prbuf_commit; + iterator_create = prbuf_iterator_create; +} + +local function prbuf_iterator_next(iterator) + if not ffi.istype(prbuf_iterator_t, iterator) then + errorf('Attempt to iterator:next() without object, use iterator:next()') + end + local entry = ffi.new(prbuf_entry_t) + local rc = builtin.prbuf_iterator_next(iterator, entry) + if rc ~= 0 then return nil end + return entry +end + +local prbuf_iterator_methods = { + next = prbuf_iterator_next; +} + +local prbuf_iterator_mt = { + __index = prbuf_iterator_methods; +} + +ffi.metatype(prbuf_iterator_t, prbuf_iterator_mt); + +local function prbuf_tostring(self) + return '<prbuf>' +end + +local prbuf_mt = { + __index = prbuf_methods; + __tostring = prbuf_tostring; +}; + +ffi.metatype(prbuf_t, prbuf_mt); + -- -- Stash keeps an FFI object for re-usage and helps to ensure the proper -- ownership. Is supposed to be used in yield-free code when almost always it is @@ -273,6 +402,8 @@ local internal = { return { internal = internal, ibuf = ibuf_new; + prbuf_open = prbuf_open; + prbuf_create = prbuf_create; READAHEAD = READAHEAD; ffi_stash_new = ffi_stash_new, } diff --git a/test/app-luatest/prbuf_test.lua b/test/app-luatest/prbuf_test.lua new file mode 100644 index 0000000000..633eea8935 --- /dev/null +++ b/test/app-luatest/prbuf_test.lua @@ -0,0 +1,44 @@ +local buffer = require('buffer') +local t = require('luatest') +local g = t.group() + +local function fill_memory(memory, size) + for i = 0, size - 1 do + memory[i] = i + end +end + +local function check_memory(memory, size) + for i = 0, size - 1 do + if memory[i] ~= i then return false end + end + return true +end + +g.test_object_misc = function() + local ibuf = buffer.ibuf() + local prbuf_size = 100 + local memory = ibuf:alloc(prbuf_size) + local prbuf = buffer.prbuf_create(memory, prbuf_size) + + local sample_size = 4 + local entry_count = 5 + for _ = 1, entry_count do + local raw = prbuf:prepare(sample_size) + t.assert_equals(raw ~= nil, true) + fill_memory(raw, sample_size) + prbuf:commit() + end + + local prbuf_recovered = buffer.prbuf_open(memory) + local iter = prbuf_recovered:iterator_create() + entry_count = 0 + local entry = iter:next() + while entry ~= nil do + entry_count = entry_count + 1 + t.assert_equals(entry.size, sample_size) + t.assert_equals(check_memory(entry.ptr, tonumber(entry.size)), true) + entry = iter:next() + end + t.assert_equals(entry_count, 5) +end -- GitLab