[Tarantool-patches] [PATCH 16/16] buffer: remove Lua registers
Vladislav Shpilevoy
v.shpilevoy at tarantool.org
Sat Mar 20 03:42:38 MSK 2021
Lua buffer module used to have a couple of preallocated objects of
type 'union c_register'. It was a bunch of C scalar and array
types intended for use instead of ffi.new() where it was needed to
allocate a temporary object like 'int[1]' just to be able to pass
'int *' into a C function via FFI.
It was a bit faster than ffi.new() even for small sizes. For
instance (when JIT works), getting a register to use it as
'int[1]' cost around 0.2-0.3 ns while ffi.new('int[1]') costs
around 0.4 ns. Also the code looked cleaner.
But Lua registers were global and therefore had the same issue as
IBUF_SHARED and static_alloc() in Lua - no ownership, and sudden
reuse when GC starts right the register is still in use in some
Lua code. __gc handlers could wipe the register values making the
original code behave unpredictably.
IBUF_SHARED was fixed by proper ownership implementation, but it
is not necessary with Lua registers. It could be done with the
buffer.ffi_stash_new() feature, but its performance is about 0.8
ns which is worse than plain ffi.new() for simple scalar types.
This patch eliminates Lua registers, and uses ffi.new() instead
everywhere.
Closes #5632
---
changelogs/unreleased/fix-ibuf-static.md | 7 +++
src/box/lua/net_box.lua | 4 +-
src/box/lua/schema.lua | 2 +-
src/lua/buffer.lua | 43 -------------------
src/lua/crypto.lua | 11 ++---
src/lua/msgpackffi.lua | 50 ++++++++++++----------
src/lua/socket.lua | 20 +++------
src/lua/string.lua | 8 +++-
src/lua/swim.lua | 2 +-
test/app-tap/gh-5632-gc-buf-reuse.test.lua | 46 +++++++++++++++++++-
test/app-tap/module_api.test.lua | 3 +-
test/app/buffer.result | 36 ----------------
test/app/buffer.test.lua | 14 ------
13 files changed, 102 insertions(+), 144 deletions(-)
create mode 100644 changelogs/unreleased/fix-ibuf-static.md
delete mode 100644 test/app/buffer.result
delete mode 100644 test/app/buffer.test.lua
diff --git a/changelogs/unreleased/fix-ibuf-static.md b/changelogs/unreleased/fix-ibuf-static.md
new file mode 100644
index 000000000..34450b85d
--- /dev/null
+++ b/changelogs/unreleased/fix-ibuf-static.md
@@ -0,0 +1,7 @@
+## bugfix/core
+
+* Extensive usage of `uri` and `uuid` modules with debug log level could lead to
+ a crash or corrupted result of the functions from these modules. Also their
+ usage from the callbacks passed to `ffi.gc()` could lead to the same but much
+ easier. The same could happen with some functions from the modules `fio`,
+ `box.tuple`, `iconv` (gh-5632).
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index de25026a5..3878abf21 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -15,7 +15,6 @@ local fiber_clock = fiber.clock
local fiber_self = fiber.self
local decode = msgpack.decode_unchecked
local decode_map_header = msgpack.decode_map_header
-local buffer_reg = buffer.reg1
local table_new = require('table.new')
local check_iterator_type = box.internal.check_iterator_type
@@ -147,8 +146,7 @@ local method_decoder = {
}
local function decode_error(raw_data)
- local ptr = buffer_reg.acucp
- ptr[0] = raw_data
+ local ptr = ffi.new('const char *[1]', raw_data)
local err = ffi.C.error_unpack_unsafe(ptr)
if err ~= nil then
err._refs = err._refs + 1
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index b345b3050..96503a50e 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -2234,7 +2234,7 @@ sequence_mt.next = function(self)
end
sequence_mt.current = function(self)
- local ai64 = buffer.reg1.ai64
+ local ai64 = ffi.new('int64_t[1]')
local rc = builtin.box_sequence_current(self.id, ai64)
if rc < 0 then
box.error(box.error.last())
diff --git a/src/lua/buffer.lua b/src/lua/buffer.lua
index 4d0bef91a..182c0b015 100644
--- a/src/lua/buffer.lua
+++ b/src/lua/buffer.lua
@@ -41,35 +41,6 @@ ibuf_reinit(struct ibuf *ibuf);
void *
ibuf_reserve_slow(struct ibuf *ibuf, size_t size);
-
-/**
- * Register is a buffer to use with FFI functions, which usually
- * operate with pointers to scalar values like int, char, size_t,
- * void *. To avoid doing 'ffi.new(<type>[1])' on each such FFI
- * function invocation, a module can use one of attributes of the
- * register union.
- *
- * Naming policy of the attributes is easy to remember:
- * 'a' for array type + type name first letters + 'p' for pointer.
- *
- * For example:
- * - int[1] - <a>rray of <i>nt - ai;
- * - const unsigned char *[1] -
- * <a>rray of <c>onst <u>nsigned <c>har <p> pointer - acucp.
- */
-union c_register {
- size_t as[1];
- void *ap[1];
- int ai[1];
- char ac[1];
- const unsigned char *acucp[1];
- unsigned long aul[1];
- uint16_t u16;
- uint32_t u32;
- uint64_t u64;
- int64_t i64;
- int64_t ai64[1];
-};
]]
local builtin = ffi.C
@@ -260,16 +231,6 @@ local function ffi_stash_new(c_type)
}
end
---
--- Sometimes it is wanted to use several temporary registers at
--- once. For example, when a C function takes 2 C pointers. Then
--- one register is not enough - its attributes share memory. Note,
--- registers are not allocated with separate ffi.new calls
--- deliberately. With a single allocation they fit into 1 cache
--- line, and reduce the heap fragmentation.
---
-local reg_array = ffi.new('union c_register[?]', 2)
-
--
-- Cord buffer is useful for the places, where
--
@@ -313,9 +274,5 @@ return {
internal = internal,
ibuf = ibuf_new;
READAHEAD = READAHEAD;
- -- Keep reference.
- reg_array = reg_array,
- reg1 = reg_array[0],
- reg2 = reg_array[1],
ffi_stash_new = ffi_stash_new,
}
diff --git a/src/lua/crypto.lua b/src/lua/crypto.lua
index 5a219cf30..6a62368b5 100644
--- a/src/lua/crypto.lua
+++ b/src/lua/crypto.lua
@@ -2,7 +2,6 @@
local ffi = require('ffi')
local buffer = require('buffer')
-local reg = buffer.reg1
local cord_ibuf_take = buffer.internal.cord_ibuf_take
local cord_ibuf_put = buffer.internal.cord_ibuf_put
@@ -134,10 +133,11 @@ local function digest_final(self)
return error('Digest not initialized')
end
self.initialized = false
- if ffi.C.EVP_DigestFinal_ex(self.ctx, self.buf.wpos, reg.ai) ~= 1 then
+ local ai = ffi.new('int[1]')
+ if ffi.C.EVP_DigestFinal_ex(self.ctx, self.buf.wpos, ai) ~= 1 then
return error('Can\'t finalize digest: ' .. openssl_err_str())
end
- return ffi.string(self.buf.wpos, reg.ai[0])
+ return ffi.string(self.buf.wpos, ai[0])
end
local function digest_free(self)
@@ -208,11 +208,12 @@ local function hmac_final(self)
self.initialized = false
local ibuf = cord_ibuf_take()
local buf = ibuf:alloc(64)
- if ffi.C.HMAC_Final(self.ctx, buf, reg.ai) ~= 1 then
+ local ai = ffi.new('int[1]')
+ if ffi.C.HMAC_Final(self.ctx, buf, ai) ~= 1 then
cord_ibuf_put(ibuf)
return error('Can\'t finalize HMAC: ' .. openssl_err_str())
end
- buf = ffi.string(buf, reg.ai[0])
+ buf = ffi.string(buf, ai[0])
cord_ibuf_put(ibuf)
return buf
end
diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua
index 6c245d7aa..1d54f11b8 100644
--- a/src/lua/msgpackffi.lua
+++ b/src/lua/msgpackffi.lua
@@ -39,7 +39,6 @@ uuid_unpack(const char **data, uint32_t len, struct tt_uuid *uuid);
]])
local strict_alignment = (jit.arch == 'arm')
-local reg = buffer.reg1
local function bswap_u16(num)
return bit.rshift(bit.bswap(tonumber(num)), 16)
@@ -76,10 +75,10 @@ end
local encode_u16
if strict_alignment then
encode_u16 = function(buf, code, num)
- reg.u16 = bswap_u16(num)
+ local u16 = ffi.new('uint16_t[1]', bswap_u16(num))
local p = buf:alloc(3)
p[0] = code
- ffi.copy(p + 1, reg, 2)
+ ffi.copy(p + 1, u16, 2)
end
else
encode_u16 = function(buf, code, num)
@@ -92,10 +91,11 @@ end
local encode_u32
if strict_alignment then
encode_u32 = function(buf, code, num)
- reg.u32 = ffi.cast('uint32_t', bit.bswap(tonumber(num)))
+ local u32 = ffi.new('uint32_t[1]',
+ ffi.cast('uint32_t', bit.bswap(tonumber(num))))
local p = buf:alloc(5)
p[0] = code
- ffi.copy(p + 1, reg, 4)
+ ffi.copy(p + 1, u32, 4)
end
else
encode_u32 = function(buf, code, num)
@@ -109,10 +109,10 @@ end
local encode_u64
if strict_alignment then
encode_u64 = function(buf, code, num)
- reg.u64 = bit.bswap(ffi.cast('uint64_t', num))
+ local u64 = ffi.new('uint64_t[1]', bit.bswap(ffi.cast('uint64_t', num)))
local p = buf:alloc(9)
p[0] = code
- ffi.copy(p + 1, reg, 8)
+ ffi.copy(p + 1, u64, 8)
end
else
encode_u64 = function(buf, code, num)
@@ -339,9 +339,10 @@ end
local decode_u16
if strict_alignment then
decode_u16 = function(data)
- ffi.copy(reg, data[0], 2)
+ local u16 = ffi.new('uint16_t[1]')
+ ffi.copy(u16, data[0], 2)
data[0] = data[0] + 2
- return tonumber(bswap_u16(reg.u16))
+ return tonumber(bswap_u16(u16[0]))
end
else
decode_u16 = function(data)
@@ -354,10 +355,11 @@ end
local decode_u32
if strict_alignment then
decode_u32 = function(data)
- ffi.copy(reg, data[0], 4)
+ local u32 = ffi.new('uint32_t[1]')
+ ffi.copy(u32, data[0], 4)
data[0] = data[0] + 4
return tonumber(
- ffi.cast('uint32_t', bit.bswap(tonumber(reg.u32))))
+ ffi.cast('uint32_t', bit.bswap(tonumber(u32[0]))))
end
else
decode_u32 = function(data)
@@ -371,9 +373,10 @@ end
local decode_u64
if strict_alignment then
decode_u64 = function(data)
- ffi.copy(reg, data[0], 8);
+ local u64 = ffi.new('uint64_t[1]')
+ ffi.copy(u64, data[0], 8);
data[0] = data[0] + 8
- local num = bit.bswap(reg.u64)
+ local num = bit.bswap(u64[0])
if num <= DBL_INT_MAX then
return tonumber(num) -- return as 'number'
end
@@ -400,8 +403,9 @@ end
local decode_i16
if strict_alignment then
decode_i16 = function(data)
- ffi.copy(reg, data[0], 2)
- local num = bswap_u16(reg.u16)
+ local u16 = ffi.new('uint16_t[1]')
+ ffi.copy(u16, data[0], 2)
+ local num = bswap_u16(u16[0])
data[0] = data[0] + 2
-- note: this double cast is actually necessary
return tonumber(ffi.cast('int16_t', ffi.cast('uint16_t', num)))
@@ -418,8 +422,9 @@ end
local decode_i32
if strict_alignment then
decode_i32 = function(data)
- ffi.copy(reg, data[0], 4)
- local num = bit.bswap(tonumber(reg.u32))
+ local u32 = ffi.new('uint32_t[1]')
+ ffi.copy(u32, data[0], 4)
+ local num = bit.bswap(tonumber(u32[0]))
data[0] = data[0] + 4
return num
end
@@ -434,9 +439,10 @@ end
local decode_i64
if strict_alignment then
decode_i64 = function(data)
- ffi.copy(reg, data[0], 8)
+ local i64 = ffi.new('int64_t[1]')
+ ffi.copy(i64, data[0], 8)
data[0] = data[0] + 8
- local num = bit.bswap(reg.i64)
+ local num = bit.bswap(i64[0])
if num >= -DBL_INT_MAX and num <= DBL_INT_MAX then
return tonumber(num) -- return as 'number'
end
@@ -588,13 +594,11 @@ decode_r = function(data)
end
---
--- A temporary const char ** buffer.
-- All decode_XXX functions accept const char **data as its first argument,
-- like libmsgpuck does. After decoding data[0] position is changed to the next
-- element. It is significally faster on LuaJIT to use double pointer than
-- return result, newpos.
--
-local bufp = reg.acucp;
local function check_offset(offset, len)
if offset == nil then
@@ -614,13 +618,13 @@ local function decode_unchecked(str, offset)
if type(str) == "string" then
offset = check_offset(offset, #str)
local buf = ffi.cast(char_ptr_t, str)
- bufp[0] = buf + offset - 1
+ local bufp = ffi.new('const unsigned char *[1]', buf + offset - 1)
local r = decode_r(bufp)
return r, bufp[0] - buf + 1
elseif ffi.istype(char_ptr_t, str) then
-- Note: ffi.istype() ignores the const qualifier, so both
-- (char *) and (const char *) buffers are valid.
- bufp[0] = str
+ local bufp = ffi.new('const unsigned char *[1]', str)
local r = decode_r(bufp)
return r, ffi.cast(ffi.typeof(str), bufp[0])
else
diff --git a/src/lua/socket.lua b/src/lua/socket.lua
index 3b2b13e19..cda62d171 100644
--- a/src/lua/socket.lua
+++ b/src/lua/socket.lua
@@ -10,8 +10,6 @@ local fiber = require('fiber')
local fio = require('fio')
local log = require('log')
local buffer = require('buffer')
-local reg1 = buffer.reg1
-local reg2 = buffer.reg2
local cord_ibuf_take = buffer.internal.cord_ibuf_take
local cord_ibuf_put = buffer.internal.cord_ibuf_put
local cord_ibuf_drop = buffer.internal.cord_ibuf_drop
@@ -478,9 +476,9 @@ local function socket_setsockopt(self, level, name, value)
end
if info.type == 1 then
- reg1.ai[0] = value
+ local ai = ffi.new('int[1]', value)
local res = ffi.C.setsockopt(fd,
- level, info.iname, reg1.ai, ffi.sizeof('int'))
+ level, info.iname, ai, ffi.sizeof('int'))
if res < 0 then
self._errno = boxerrno()
@@ -522,10 +520,8 @@ local function socket_getsockopt(self, level, name)
self._errno = nil
if info.type == 1 then
- local value = reg1.ai
- value[0] = 0
- local len = reg2.as
- len[0] = ffi.sizeof('int')
+ local value = ffi.new('int[1]')
+ local len = ffi.new('size_t[1]', ffi.sizeof('int'))
local res = ffi.C.getsockopt(fd, level, info.iname, value, len)
if res < 0 then
@@ -542,8 +538,7 @@ local function socket_getsockopt(self, level, name)
if info.type == 2 then
local ibuf = cord_ibuf_take()
local value = ibuf:alloc(256)
- local len = reg1.as
- len[0] = 256
+ local len = ffi.new('size_t[1]', 256)
local res = ffi.C.getsockopt(fd, level, info.iname, value, len)
if res < 0 then
self._errno = boxerrno()
@@ -569,8 +564,7 @@ local function socket_linger(self, active, timeout)
self._errno = nil
if active == nil then
local value = ffi.new('linger_t[1]')
- local len = reg1.as
- len[0] = ffi.sizeof('linger_t')
+ local len = ffi.new('size_t[1]', ffi.sizeof('linger_t'))
local res = ffi.C.getsockopt(fd, level, info.iname, value, len)
if res < 0 then
self._errno = boxerrno()
@@ -825,7 +819,7 @@ local function get_recv_size(self, size)
-- them using message peek.
local iflags = get_iflags(internal.SEND_FLAGS, {'MSG_PEEK'})
assert(iflags ~= nil)
- size = tonumber(ffi.C.recv(fd, reg1.ac, 1, iflags))
+ size = tonumber(ffi.C.recv(fd, ffi.new('char[1]'), 1, iflags))
-- Prevent race condition: proceed with the case when
-- a datagram of length > 0 has been arrived after the
-- getsockopt call above.
diff --git a/src/lua/string.lua b/src/lua/string.lua
index 105110b52..5b0036da4 100644
--- a/src/lua/string.lua
+++ b/src/lua/string.lua
@@ -16,8 +16,6 @@ ffi.cdef[[
]]
local c_char_ptr = ffi.typeof('const char *')
-local strip_newstart = buffer.reg1.aul
-local strip_newlen = buffer.reg2.aul
local memcmp = ffi.C.memcmp
local memmem = ffi.C.memmem
@@ -371,6 +369,8 @@ local function string_strip(inp, chars)
end
local casted_inp = c_char_ptr(inp)
+ local strip_newstart = ffi.new('unsigned long[1]')
+ local strip_newlen = ffi.new('unsigned long[1]')
ffi.C.string_strip_helper(inp, #inp, chars, #chars, true, true,
strip_newstart, strip_newlen)
return ffi.string(casted_inp + strip_newstart[0], strip_newlen[0])
@@ -392,6 +392,8 @@ local function string_lstrip(inp, chars)
end
local casted_inp = c_char_ptr(inp)
+ local strip_newstart = ffi.new('unsigned long[1]')
+ local strip_newlen = ffi.new('unsigned long[1]')
ffi.C.string_strip_helper(inp, #inp, chars, #chars, true, false,
strip_newstart, strip_newlen)
return ffi.string(casted_inp + strip_newstart[0], strip_newlen[0])
@@ -413,6 +415,8 @@ local function string_rstrip(inp, chars)
end
local casted_inp = c_char_ptr(inp)
+ local strip_newstart = ffi.new('unsigned long[1]')
+ local strip_newlen = ffi.new('unsigned long[1]')
ffi.C.string_strip_helper(inp, #inp, chars, #chars, false, true,
strip_newstart, strip_newlen)
return ffi.string(casted_inp + strip_newstart[0], strip_newlen[0])
diff --git a/src/lua/swim.lua b/src/lua/swim.lua
index 42b0d15ef..f61aa6b58 100644
--- a/src/lua/swim.lua
+++ b/src/lua/swim.lua
@@ -348,7 +348,7 @@ local function swim_member_is_dropped(m)
end
local function swim_member_payload_raw(ptr)
- local int = buffer.reg1.ai
+ local int = ffi.new('int[1]')
local cdata = capi.swim_member_payload(ptr, int)
return cdata, int[0]
end
diff --git a/test/app-tap/gh-5632-gc-buf-reuse.test.lua b/test/app-tap/gh-5632-gc-buf-reuse.test.lua
index bfa86615f..3b8138f11 100755
--- a/test/app-tap/gh-5632-gc-buf-reuse.test.lua
+++ b/test/app-tap/gh-5632-gc-buf-reuse.test.lua
@@ -11,6 +11,7 @@ local tap = require('tap')
local ffi = require('ffi')
local uuid = require('uuid')
local uri = require('uri')
+local msgpackffi = require('msgpackffi')
local function test_uuid(test)
test:plan(1)
@@ -99,9 +100,52 @@ local function test_uri(test)
test:ok(is_success, 'uri in gc')
end
+local function test_msgpackffi(test)
+ test:plan(1)
+
+ local mp_encode = msgpackffi.encode
+ local mp_decode = msgpackffi.decode
+ local gc_count = 100
+ local iter_count = 1000
+ local is_success = true
+ local data = {0, 1, 1000, 100000000, 'str', true, 1.1}
+
+ local function do_encode()
+ if not is_success then
+ return
+ end
+ local t = mp_encode(data)
+ t = mp_decode(t)
+ if #t ~= #data then
+ is_success = false
+ return
+ end
+ for i = 1, #t do
+ if t[i] ~= data[i] then
+ is_success = false
+ return
+ end
+ end
+ end
+
+ local function create_gc()
+ for i = 1, gc_count do
+ ffi.gc(ffi.new('char[1]'), do_encode)
+ end
+ end
+
+ for i = 1, iter_count do
+ create_gc()
+ do_encode()
+ end
+
+ test:ok(is_success, 'msgpackffi in gc')
+end
+
local test = tap.test('gh-5632-gc-buf-reuse')
-test:plan(2)
+test:plan(3)
test:test('uuid in __gc', test_uuid)
test:test('uri in __gc', test_uri)
+test:test('msgpackffi in __gc', test_msgpackffi)
os.exit(test:check() and 0 or 1)
diff --git a/test/app-tap/module_api.test.lua b/test/app-tap/module_api.test.lua
index f0a864e02..c945ac4bc 100755
--- a/test/app-tap/module_api.test.lua
+++ b/test/app-tap/module_api.test.lua
@@ -41,7 +41,7 @@ local function test_pushcdata(test, module)
end
local function test_buffers(test, module)
- test:plan(9)
+ test:plan(8)
local buffer = require('buffer')
local bufalloc = ffi.new('char[?]', 128)
@@ -53,7 +53,6 @@ local function test_buffers(test, module)
test:ok(not module.toibuf({}), 'toibuf of {}')
test:ok(not module.toibuf(1LL), 'toibuf of 1LL')
test:ok(not module.toibuf(box.NULL), 'toibuf of box.NULL')
- test:ok(not module.toibuf(buffer.reg1), 'toibuf of reg1')
test:ok(not module.toibuf(bufalloc), 'toibuf of allocated buffer')
test:ok(module.toibuf(ibuf_ptr), "toibuf of ibuf*")
test:ok(module.toibuf(ibuf), 'toibuf of ibuf')
diff --git a/test/app/buffer.result b/test/app/buffer.result
deleted file mode 100644
index 0934ae58f..000000000
--- a/test/app/buffer.result
+++ /dev/null
@@ -1,36 +0,0 @@
-test_run = require('test_run').new()
----
-...
-buffer = require('buffer')
----
-...
-ffi = require('ffi')
----
-...
--- Registers.
-reg1 = buffer.reg1
----
-...
-reg1.u16 = 100
----
-...
-u16 = ffi.new('uint16_t[1]')
----
-...
-ffi.copy(u16, reg1, 2)
----
-...
-u16[0]
----
-- 100
-...
-u16[0] = 200
----
-...
-ffi.copy(reg1, u16, 2)
----
-...
-reg1.u16
----
-- 200
-...
diff --git a/test/app/buffer.test.lua b/test/app/buffer.test.lua
deleted file mode 100644
index 1873e189e..000000000
--- a/test/app/buffer.test.lua
+++ /dev/null
@@ -1,14 +0,0 @@
-test_run = require('test_run').new()
-buffer = require('buffer')
-ffi = require('ffi')
-
--- Registers.
-reg1 = buffer.reg1
-reg1.u16 = 100
-u16 = ffi.new('uint16_t[1]')
-ffi.copy(u16, reg1, 2)
-u16[0]
-
-u16[0] = 200
-ffi.copy(reg1, u16, 2)
-reg1.u16
--
2.24.3 (Apple Git-128)
More information about the Tarantool-patches
mailing list