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