[Tarantool-patches] [PATCH 10/15] uri: replace static_alloc with ffi stash and ibuf
Vladislav Shpilevoy
v.shpilevoy at tarantool.org
Thu Mar 25 00:24:22 MSK 2021
static_alloc() appears not to be safe to use in Lua, because it
does not provide any ownership protection for the returned values.
The problem appears when something is allocated, then Lua GC
starts, and some __gc handlers might also use static_alloc(). In
Lua and in C - both lead to the buffer being corrupted in its
original usage place.
The patch is a part of activity of getting rid of static_alloc()
in Lua. It removes it from uri Lua module and makes it use the
new FFI stash feature, which helps to cache frequently used and
heavy to allocate FFI values.
In one place static_alloc() was used for an actual buffer - it was
replaced with cord_ibuf which is equally fast when preallocated.
ffi.new() for temporary struct uri is not used, because
- It produces a new GC object;
- ffi.new('struct uri') costs around 20ns while FFI stash
costs around 0.8ns. The hack with 'struct uri[1]' does not help
because size of uri is > 128 bytes;
- Without JIT ffi.new() costs about the same as the stash, not
better as well;
The patch makes uri perf a bit better in the places where
static_alloc() was used, because its cost was around 7ns for one
allocation.
(cherry picked from commit 7175b43e842fe42a04bba2b88006732f20bd7552)
---
src/lua/uri.lua | 22 ++++++--
test/app-tap/gh-5632-gc-buf-reuse.test.lua | 60 +++++++++++++++++++++-
2 files changed, 76 insertions(+), 6 deletions(-)
diff --git a/src/lua/uri.lua b/src/lua/uri.lua
index d2946cd2d..98f4e02ec 100644
--- a/src/lua/uri.lua
+++ b/src/lua/uri.lua
@@ -1,6 +1,7 @@
-- uri.lua (internal file)
local ffi = require('ffi')
+local buffer = require('buffer')
ffi.cdef[[
struct uri {
@@ -31,14 +32,19 @@ uri_format(char *str, size_t len, struct uri *uri, bool write_password);
]]
local builtin = ffi.C;
-
-local uribuf = ffi.new('struct uri')
+local uri_stash = buffer.ffi_stash_new('struct uri')
+local uri_stash_take = uri_stash.take
+local uri_stash_put = uri_stash.put
+local cord_ibuf_take = buffer.internal.cord_ibuf_take
+local cord_ibuf_put = buffer.internal.cord_ibuf_put
local function parse(str)
if str == nil then
error("Usage: uri.parse(string)")
end
+ local uribuf = uri_stash_take()
if builtin.uri_parse(uribuf, str) ~= 0 then
+ uri_stash_put(uribuf)
return nil
end
local result = {}
@@ -55,10 +61,12 @@ local function parse(str)
elseif uribuf.host_hint == 3 then
result.unix = result.service
end
+ uri_stash_put(uribuf)
return result
end
local function format(uri, write_password)
+ local uribuf = uri_stash_take()
uribuf.scheme = uri.scheme
uribuf.scheme_len = string.len(uri.scheme or '')
uribuf.login = uri.login
@@ -75,9 +83,13 @@ local function format(uri, write_password)
uribuf.query_len = string.len(uri.query or '')
uribuf.fragment = uri.fragment
uribuf.fragment_len = string.len(uri.fragment or '')
- local str = ffi.new('char[1024]')
- builtin.uri_format(str, 1024, uribuf, write_password and 1 or 0)
- return ffi.string(str)
+ local ibuf = cord_ibuf_take()
+ local str = ibuf:alloc(1024)
+ local len = builtin.uri_format(str, 1024, uribuf, write_password and 1 or 0)
+ uri_stash_put(uribuf)
+ str = ffi.string(str, len)
+ cord_ibuf_put(ibuf)
+ return str
end
return {
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 b09b1bf3e..81dafd36e 100755
--- a/test/app-tap/gh-5632-gc-buf-reuse.test.lua
+++ b/test/app-tap/gh-5632-gc-buf-reuse.test.lua
@@ -10,6 +10,7 @@
local tap = require('tap')
local ffi = require('ffi')
local uuid = require('uuid')
+local uri = require('uri')
local function test_uuid(test)
test:plan(1)
@@ -42,8 +43,65 @@ local function test_uuid(test)
test:ok(is_success, 'uuid in gc')
end
+local function test_uri(test)
+ test:plan(1)
+
+ local gc_count = 100
+ local iter_count = 1000
+ local port = 1
+ local ip = 1
+ local login = 1
+ local pass = 1
+ local is_success = true
+
+ local function uri_parse()
+ local loc_ip = ip
+ local loc_port = port
+ local loc_pass = pass
+ local loc_login = login
+
+ ip = ip + 1
+ port = port + 1
+ pass = pass + 1
+ login = login + 1
+ if port > 60000 then
+ port = 1
+ end
+ if ip > 255 then
+ ip = 1
+ end
+
+ loc_ip = string.format('127.0.0.%s', loc_ip)
+ loc_port = tostring(loc_port)
+ loc_pass = string.format('password%s', loc_pass)
+ loc_login = string.format('login%s', loc_login)
+ local host = string.format('%s:%s@%s:%s', loc_login, loc_pass,
+ loc_ip, loc_port)
+ local u = uri.parse(host)
+ if u.host ~= loc_ip or u.login ~= loc_login or u.service ~= loc_port or
+ u.password ~= loc_pass then
+ is_success = false
+ assert(false)
+ end
+ end
+
+ local function create_gc()
+ for _ = 1, gc_count do
+ ffi.gc(ffi.new('char[1]'), uri_parse)
+ end
+ end
+
+ for _ = 1, iter_count do
+ create_gc()
+ uri_parse()
+ end
+
+ test:ok(is_success, 'uri in gc')
+end
+
local test = tap.test('gh-5632-gc-buf-reuse')
-test:plan(1)
+test:plan(2)
test:test('uuid in __gc', test_uuid)
+test:test('uri in __gc', test_uri)
os.exit(test:check() and 0 or 1)
--
2.24.3 (Apple Git-128)
More information about the Tarantool-patches
mailing list