https://github.com/tarantool/tarantool/tree/vdavydov/net-box-optimization This patch set rewrites performance-critical parts of net.box (response dispatching and IO loop) in C. It shouldn't introduce any user-visible changes or changes in the logic, because the C version was basically created by rewriting Lua code line-by-line. The goal of this work is to improve performance of CPU-bound applications that use net.box, such as vshard.router. To ensure that this patch does meet the expectations, I ran a simple benchmark [tnt-bench.lua], which issues multiple concurrent requests in a loop. The test measures RPS per wall time (WALL) and processor time (PROC). Concurrency is implemented with either fibers or futures. There are a few test cases that issue different kinds of requests: - REPLACE({k, 'bar', i + k}) - UPDATE({k}, {{'=', 2, 'bar'}, {'=', 3, i + k}}) - SELECT({k}) - CALL('bench_func', {1, 2, 3, 'foo', 'bar'}) where i and k are integers and bench_func is defined as follows: function bench_func(...) return {...} end The test was run on my laptop (i5-10210U 1.60GHz) for the following Tarantool versions built with CMAKE_BUILD_TYPE=RelWithDebInfo: - master: 2.9.0-165-ga02cfe60cf23 - patched: master + this patch set - poc: master + [tarantool-net-box-call-in-c.patch] The latter is a proof-of-concept version that I created before starting to work on this patch set. The results are below. /// USING FIBERS (SYNCHRONOUS) /// ---------+-----------------------------++-----------------------------+ | KRPS (WALL TIME) || KRPS (PROC TIME) | +---------+---------+---------++---------+---------+---------+ | master | patched | poc || master | patched | poc | ---------+---------+---------+---------++---------+---------+---------+ REPLACE | 162.628 | 268.349 | N/A || 221.402 | 459.965 | N/A | UPDATE | 126.905 | 195.835 | N/A || 173.635 | 334.609 | N/A | SELECT | 187.742 | 353.043 | N/A || 207.605 | 427.147 | N/A | CALL | 163.700 | 290.717 | 375.412 || 213.560 | 481.349 | 761.238 | /// USING FUTURES (ASYNCHRONOUS) /// ---------+-----------------------------++-----------------------------+ | RPS (WALL TIME) || RPS (PROC TIME) | +---------+---------+---------++---------+---------+---------+ | master | patched | poc || master | patched | poc | ---------+---------+---------+---------++---------+---------+---------+ REPLACE | 191.529 | 249.810 | N/A || 277.648 | 413.360 | N/A | UPDATE | 155.116 | 173.850 | N/A || 231.603 | 273.624 | N/A | SELECT | 238.657 | 286.699 | N/A || 269.040 | 333.706 | N/A | CALL | 192.041 | 241.571 | N/A || 261.085 | 365.139 | N/A | So the patch set increases RPS of synchronous net.box.call, which is the primary method used by vshard.router, by about 75%. Other synchronous methods show the improvement between 50 and 90%. The requests per processor second ratio is doubled by the patch, which means that it also reduces CPU usage during the test - judging by KRPS[WALL]/KRPS[PROC] the ratio, it is decreased from 75% to 60%. Asynchronous calls don't show as much of an improvement as synchronous, because per each asynchronous call we still have to create a 'future' object in Lua. Still, the improvement is quite noticeable - 30% for REPLACE, 10% for UPDATE, 20% for SELECT, 25% for CALL. What is surprising is that the PoC version still outperforms the patched version by about 30% and shows even lower CPU usage (50% vs 60%). This is probably caused by the IO loop implementation. I'm going to look into that separately. Links: [tnt-bench.lua] https://gist.github.com/locker/7faeb39129a2421a85568c512288208f [tarantool-net-box-call-in-c.patch] https://gist.github.com/locker/cd357f9482bfd207ffe7df610c4b2fba For more information about net.box performance, see - C/C++ vs Net.Box Connector Performance https://docs.google.com/document/d/1v-d-qQ9zilOdDgDJZWTzs0cSJ9XVXLWRfoQnxNfYttc - vshard.router.call performance analysis https://docs.google.com/document/d/1VwMzs75Umi5IhFw-r54wj0b8s_d9WDCYFZ3lRMFzfB8 Vladimir Davydov (20): net.box: fix console connection breakage when request is discarded net.box: wake up wait_result callers when request is discarded net.box: do not check worker_fiber in request:result,is_ready net.box: remove decode_push from method_decoder table net.box: use decode_tuple instead of decode_get net.box: rename request.ctx to request.format net.box: use integer id instead of method name net.box: remove useless encode optimization net.box: rewrite request encoder in C lua/utils: make char ptr Lua CTIDs public net.box: rewrite response decoder in C net.box: rewrite error decoder in C net.box: rewrite send_and_recv_{iproto,console} in C net.box: rename netbox_{prepare,encode}_request to {begin,end} net.box: rewrite request implementation in C net.box: store next_request_id in C code net.box: rewrite console handlers in C net.box: rewrite iproto handlers in C net.box: merge new_id, new_request and encode_method net.box: do not create request object in Lua for sync requests src/box/lua/net_box.c | 1714 ++++++++++++++--- src/box/lua/net_box.lua | 733 ++----- src/lib/core/errinj.h | 1 + src/lua/utils.c | 4 +- src/lua/utils.h | 2 + test/box/access.result | 24 +- test/box/access.test.lua | 20 +- test/box/errinj.result | 1 + ...net.box_console_connections_gh-2677.result | 2 +- ...t.box_console_connections_gh-2677.test.lua | 2 +- .../net.box_discard_console_request.result | 62 + .../net.box_discard_console_request.test.lua | 19 + test/box/net.box_discard_gh-3107.result | 11 + test/box/net.box_discard_gh-3107.test.lua | 3 + .../net.box_incorrect_iterator_gh-841.result | 9 +- ...net.box_incorrect_iterator_gh-841.test.lua | 9 +- test/box/net.box_iproto_hangs_gh-3464.result | 2 +- .../box/net.box_iproto_hangs_gh-3464.test.lua | 2 +- .../net.box_long-poll_input_gh-3400.result | 13 +- .../net.box_long-poll_input_gh-3400.test.lua | 8 +- test/box/suite.ini | 2 +- 21 files changed, 1735 insertions(+), 908 deletions(-) create mode 100644 test/box/net.box_discard_console_request.result create mode 100644 test/box/net.box_discard_console_request.test.lua -- 2.25.1
If a timed-out console request is collected by the garbage collector before the response is processed, the connection will stop serving new requests. This happens because of the erroneous 'return' statement in console_sm. Fix it and add a test. --- src/box/lua/net_box.c | 1 + src/box/lua/net_box.lua | 20 +++--- src/lib/core/errinj.h | 1 + test/box/errinj.result | 1 + .../net.box_discard_console_request.result | 62 +++++++++++++++++++ .../net.box_discard_console_request.test.lua | 19 ++++++ test/box/suite.ini | 2 +- 7 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 test/box/net.box_discard_console_request.result create mode 100644 test/box/net.box_discard_console_request.test.lua diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 3f43872ca2e4..ed1df4a189d2 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -542,6 +542,7 @@ check_limit: } ev_tstamp deadline = ev_monotonic_now(loop()) + timeout; + ERROR_INJECT_YIELD(ERRINJ_NETBOX_IO_DELAY); revents = coio_wait(fd, EV_READ | (ibuf_used(send_buf) != 0 ? EV_WRITE : 0), timeout); luaL_testcancel(L); diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 3878abf21914..5fd8b96b079d 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -671,6 +671,17 @@ local function create_transport(host, port, user, password, callback, request.cond:broadcast() end + local function dispatch_response_console(rid, response) + local request = requests[rid] + if request == nil then -- nobody is waiting for the response + return + end + request.id = nil + requests[rid] = nil + request.response = response + request.cond:broadcast() + end + local function new_request_id() local id = next_request_id; next_request_id = next_id(id) @@ -771,14 +782,7 @@ local function create_transport(host, port, user, password, callback, if err then return error_sm(err, response) else - local request = requests[rid] - if request == nil then -- nobody is waiting for the response - return - end - request.id = nil - requests[rid] = nil - request.response = response - request.cond:broadcast() + dispatch_response_console(rid, response) return console_sm(next_id(rid)) end end diff --git a/src/lib/core/errinj.h b/src/lib/core/errinj.h index e492428071d9..3fe4c7c22cc8 100644 --- a/src/lib/core/errinj.h +++ b/src/lib/core/errinj.h @@ -154,6 +154,7 @@ struct errinj { _(ERRINJ_IPROTO_SINGLE_THREAD_STAT, ERRINJ_INT, {.iparam = -1}) \ _(ERRINJ_IPROTO_WRITE_ERROR_DELAY, ERRINJ_BOOL, {.bparam = false})\ _(ERRINJ_APPLIER_READ_TX_ROW_DELAY, ERRINJ_BOOL, {.bparam = false})\ + _(ERRINJ_NETBOX_IO_DELAY, ERRINJ_BOOL, {.bparam = false}) \ ENUM0(errinj_id, ERRINJ_LIST); extern struct errinj errinjs[]; diff --git a/test/box/errinj.result b/test/box/errinj.result index 44f86a54e7eb..adb682ac3a5c 100644 --- a/test/box/errinj.result +++ b/test/box/errinj.result @@ -63,6 +63,7 @@ evals - ERRINJ_IPROTO_WRITE_ERROR_DELAY: false - ERRINJ_LOG_ROTATE: false - ERRINJ_MEMTX_DELAY_GC: false + - ERRINJ_NETBOX_IO_DELAY: false - ERRINJ_PORT_DUMP: false - ERRINJ_RELAY_BREAK_LSN: -1 - ERRINJ_RELAY_EXIT_DELAY: 0 diff --git a/test/box/net.box_discard_console_request.result b/test/box/net.box_discard_console_request.result new file mode 100644 index 000000000000..e8da50a2f648 --- /dev/null +++ b/test/box/net.box_discard_console_request.result @@ -0,0 +1,62 @@ +-- test-run result file version 2 +test_run = require('test_run').new() + | --- + | ... +fio = require('fio') + | --- + | ... +net = require('net.box') + | --- + | ... +console = require('console') + | --- + | ... +errinj = box.error.injection + | --- + | ... + +console_sock_path = fio.pathjoin(fio.cwd(), 'console.sock') + | --- + | ... +_ = fio.unlink(console_sock_path) + | --- + | ... +s = console.listen(console_sock_path) + | --- + | ... +c = net.connect('unix/', console_sock_path, {console = true}) + | --- + | ... + +errinj.set('ERRINJ_NETBOX_IO_DELAY', true) + | --- + | - ok + | ... +c:eval('return', 0) -- timeout, but the request is still in flight + | --- + | - error: Timeout exceeded + | ... +collectgarbage('collect') -- force garbage collection of the request + | --- + | - 0 + | ... +errinj.set('ERRINJ_NETBOX_IO_DELAY', false) + | --- + | - ok + | ... +c:eval('return') -- ok + | --- + | - '--- + | + | ... + | + | ' + | ... + +c:close() + | --- + | ... +s:close() + | --- + | - true + | ... diff --git a/test/box/net.box_discard_console_request.test.lua b/test/box/net.box_discard_console_request.test.lua new file mode 100644 index 000000000000..52543e1d5649 --- /dev/null +++ b/test/box/net.box_discard_console_request.test.lua @@ -0,0 +1,19 @@ +test_run = require('test_run').new() +fio = require('fio') +net = require('net.box') +console = require('console') +errinj = box.error.injection + +console_sock_path = fio.pathjoin(fio.cwd(), 'console.sock') +_ = fio.unlink(console_sock_path) +s = console.listen(console_sock_path) +c = net.connect('unix/', console_sock_path, {console = true}) + +errinj.set('ERRINJ_NETBOX_IO_DELAY', true) +c:eval('return', 0) -- timeout, but the request is still in flight +collectgarbage('collect') -- force garbage collection of the request +errinj.set('ERRINJ_NETBOX_IO_DELAY', false) +c:eval('return') -- ok + +c:close() +s:close() diff --git a/test/box/suite.ini b/test/box/suite.ini index 5ac3979dbbf4..d9379cc0f28a 100644 --- a/test/box/suite.ini +++ b/test/box/suite.ini @@ -5,7 +5,7 @@ script = box.lua disabled = rtree_errinj.test.lua tuple_bench.test.lua long_run = huge_field_map_long.test.lua config = engine.cfg -release_disabled = errinj.test.lua errinj_index.test.lua rtree_errinj.test.lua upsert_errinj.test.lua iproto_stress.test.lua gh-4648-func-load-unload.test.lua gh-5645-several-iproto-threads.test.lua +release_disabled = errinj.test.lua errinj_index.test.lua rtree_errinj.test.lua upsert_errinj.test.lua iproto_stress.test.lua gh-4648-func-load-unload.test.lua gh-5645-several-iproto-threads.test.lua box/net.box_discard_console_request.test.lua lua_libs = lua/fifo.lua lua/utils.lua lua/bitset.lua lua/index_random_test.lua lua/push.lua lua/identifier.lua lua/txn_proxy.lua use_unix_sockets = True use_unix_sockets_iproto = True -- 2.25.1
request.discard() doesn't wake up fibers blocked in request.wait_result() as a result they hang until timeout. Fix this and add a test. --- src/box/lua/net_box.lua | 1 + test/box/net.box_discard_gh-3107.result | 11 +++++++++++ test/box/net.box_discard_gh-3107.test.lua | 3 +++ 3 files changed, 15 insertions(+) diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 5fd8b96b079d..cacb7473deb0 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -432,6 +432,7 @@ local function create_transport(host, port, user, password, callback, self.id = nil self.errno = box.error.PROC_LUA self.response = 'Response is discarded' + self.cond:broadcast() end end diff --git a/test/box/net.box_discard_gh-3107.result b/test/box/net.box_discard_gh-3107.result index 3498c9d5a9be..370a87c524b4 100644 --- a/test/box/net.box_discard_gh-3107.result +++ b/test/box/net.box_discard_gh-3107.result @@ -92,9 +92,20 @@ ret future = c:call('long_function', {1, 2, 3}, {is_async = true}) --- ... +ch = fiber.channel() +--- +... +_ = fiber.create(function() ch:put({future:wait_result()}) end) +--- +... future:discard() --- ... +ch:get(100) +--- +- - null + - Response is discarded +... finalize_long() --- ... diff --git a/test/box/net.box_discard_gh-3107.test.lua b/test/box/net.box_discard_gh-3107.test.lua index 71f08a411422..89f177fa811f 100644 --- a/test/box/net.box_discard_gh-3107.test.lua +++ b/test/box/net.box_discard_gh-3107.test.lua @@ -33,7 +33,10 @@ ret -- Test discard. -- future = c:call('long_function', {1, 2, 3}, {is_async = true}) +ch = fiber.channel() +_ = fiber.create(function() ch:put({future:wait_result()}) end) future:discard() +ch:get(100) finalize_long() future:result() future:wait_result(100) -- 2.25.1
request.id and request.errno are set appropriately whenever a connection is closed so there's no need to check if worker_fiber is running. We do not want to have this check in request methods so that we can move its implementation to C, where worker_fiber isn't (and won't be) available. --- src/box/lua/net_box.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index cacb7473deb0..fc8468ed8a01 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -295,7 +295,7 @@ local function create_transport(host, port, user, password, callback, -- dispatcher. -- function request_index:is_ready() - return self.id == nil or worker_fiber == nil + return self.id == nil end -- -- When a request is finished, a result can be got from a @@ -315,8 +315,6 @@ local function create_transport(host, port, user, password, callback, return nil, self.response elseif not self.id then return self.response - elseif not worker_fiber then - return nil, box.error.new(E_NO_CONNECTION) else return nil, box.error.new(box.error.PROC_LUA, 'Response is not ready') -- 2.25.1
No need to have it there, since we can call it directly. --- src/box/lua/net_box.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index fc8468ed8a01..9cb573f659b3 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -142,7 +142,6 @@ local method_decoder = { max = decode_get, count = decode_count, inject = decode_data, - push = decode_push, } local function decode_error(raw_data) @@ -662,8 +661,7 @@ local function create_transport(host, port, user, password, callback, request.id = nil else local msg - msg, real_end, request.errno = - method_decoder.push(body_rpos, body_end) + msg, real_end, request.errno = decode_push(body_rpos, body_end) assert(real_end == body_end, "invalid body length") request.on_push(request.on_push_ctx, msg) end -- 2.25.1
The only difference between the two is that the latter ensures that the response body has no more than one tuple. There's no point in this check, because get/min/max never return more than one tuple in Tarantool. Note, since decode_get was the only method returning an error, we don't need to set request.errno after calling a decoder anymore. --- src/box/lua/net_box.lua | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 9cb573f659b3..40bbf993a49a 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -73,13 +73,6 @@ local function decode_tuple(raw_data, raw_data_end, format) -- luacheck: no unus local response, raw_end = internal.decode_select(raw_data, nil, format) return response[1], raw_end end -local function decode_get(raw_data, raw_data_end, format) -- luacheck: no unused args - local body, raw_end = internal.decode_select(raw_data, nil, format) - if body[2] then - return nil, raw_end, box.error.MORE_THAN_ONE_TUPLE - end - return body[1], raw_end -end local function decode_count(raw_data) local response, raw_end = decode(raw_data) return response[IPROTO_DATA_KEY][1], raw_end @@ -137,9 +130,9 @@ local method_decoder = { execute = internal.decode_execute, prepare = internal.decode_prepare, unprepare = decode_nil, - get = decode_get, - min = decode_get, - max = decode_get, + get = decode_tuple, + min = decode_tuple, + max = decode_tuple, count = decode_count, inject = decode_data, } @@ -654,7 +647,7 @@ local function create_transport(host, port, user, password, callback, local real_end -- Decode xrow.body[DATA] to Lua objects if status == IPROTO_OK_KEY then - request.response, real_end, request.errno = + request.response, real_end = method_decoder[request.method](body_rpos, body_end, request.ctx) assert(real_end == body_end, "invalid body length") requests[id] = nil -- 2.25.1
Request context only stores tuple format or nil, which is used for decoding a response. Rename it appropriately. --- src/box/lua/net_box.lua | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 40bbf993a49a..0ac0c0375a45 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -523,7 +523,7 @@ local function create_transport(host, port, user, password, callback, -- @retval not nil Future object. -- local function perform_async_request(buffer, skip_header, method, on_push, - on_push_ctx, request_ctx, ...) + on_push_ctx, format, ...) if state ~= 'active' and state ~= 'fetch_schema' then local code = last_errno or E_NO_CONNECTION local msg = last_error or @@ -541,7 +541,7 @@ local function create_transport(host, port, user, password, callback, next_request_id = next_id(id) -- Request in most cases has maximum 10 members: -- method, buffer, skip_header, id, cond, errno, response, - -- on_push, on_push_ctx and ctx. + -- on_push, on_push_ctx and format. local request = setmetatable(table_new(0, 10), request_mt) request.method = method request.buffer = buffer @@ -551,7 +551,7 @@ local function create_transport(host, port, user, password, callback, requests[id] = request request.on_push = on_push request.on_push_ctx = on_push_ctx - request.ctx = request_ctx + request.format = format return request end @@ -561,10 +561,10 @@ local function create_transport(host, port, user, password, callback, -- @retval not nil Response object. -- local function perform_request(timeout, buffer, skip_header, method, - on_push, on_push_ctx, request_ctx, ...) + on_push, on_push_ctx, format, ...) local request, err = perform_async_request(buffer, skip_header, method, on_push, - on_push_ctx, request_ctx, ...) + on_push_ctx, format, ...) if not request then return nil, err end @@ -648,7 +648,8 @@ local function create_transport(host, port, user, password, callback, -- Decode xrow.body[DATA] to Lua objects if status == IPROTO_OK_KEY then request.response, real_end = - method_decoder[request.method](body_rpos, body_end, request.ctx) + method_decoder[request.method](body_rpos, body_end, + request.format) assert(real_end == body_end, "invalid body length") requests[id] = nil request.id = nil @@ -1148,7 +1149,7 @@ function remote_methods:wait_connected(timeout) return self._transport.wait_state('active', timeout) end -function remote_methods:_request(method, opts, request_ctx, ...) +function remote_methods:_request(method, opts, format, ...) local transport = self._transport local on_push, on_push_ctx, buffer, skip_header, deadline -- Extract options, set defaults, check if the request is @@ -1162,7 +1163,7 @@ function remote_methods:_request(method, opts, request_ctx, ...) end local res, err = transport.perform_async_request(buffer, skip_header, method, - table.insert, {}, request_ctx, + table.insert, {}, format, ...) if err then box.error(err) @@ -1191,7 +1192,7 @@ function remote_methods:_request(method, opts, request_ctx, ...) end local res, err = transport.perform_request(timeout, buffer, skip_header, method, on_push, on_push_ctx, - request_ctx, ...) + format, ...) if err then box.error(err) end -- 2.25.1
We need this to port performance-critical parts of net.box to C, where it's easier and more efficient to look up encoders/decoders by integer id rather than by string method name. --- src/box/lua/net_box.lua | 158 +++++++++++------- test/box/access.result | 24 ++- test/box/access.test.lua | 20 ++- ...net.box_console_connections_gh-2677.result | 2 +- ...t.box_console_connections_gh-2677.test.lua | 2 +- .../net.box_incorrect_iterator_gh-841.result | 9 +- ...net.box_incorrect_iterator_gh-841.test.lua | 9 +- test/box/net.box_iproto_hangs_gh-3464.result | 2 +- .../box/net.box_iproto_hangs_gh-3464.test.lua | 2 +- .../net.box_long-poll_input_gh-3400.result | 13 +- .../net.box_long-poll_input_gh-3400.test.lua | 8 +- 11 files changed, 161 insertions(+), 88 deletions(-) diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 0ac0c0375a45..76cfd40e3bee 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -51,6 +51,27 @@ local E_TIMEOUT = box.error.TIMEOUT local E_PROC_LUA = box.error.PROC_LUA local E_NO_SUCH_SPACE = box.error.NO_SUCH_SPACE +-- Method types used internally by net.box. +local M_PING = 0 +local M_CALL_16 = 1 +local M_CALL_17 = 2 +local M_EVAL = 3 +local M_INSERT = 4 +local M_REPLACE = 5 +local M_DELETE = 6 +local M_UPDATE = 7 +local M_UPSERT = 8 +local M_SELECT = 9 +local M_EXECUTE = 10 +local M_PREPARE = 11 +local M_UNPREPARE = 12 +local M_GET = 13 +local M_MIN = 14 +local M_MAX = 15 +local M_COUNT = 16 +-- Injects raw data into connection. Used by console and tests. +local M_INJECT = 17 + ffi.cdef[[ struct error * error_unpack_unsafe(const char **data); @@ -91,25 +112,24 @@ local function version_at_least(peer_version_id, major, minor, patch) end local method_encoder = { - ping = internal.encode_ping, - call_16 = internal.encode_call_16, - call_17 = internal.encode_call, - eval = internal.encode_eval, - insert = internal.encode_insert, - replace = internal.encode_replace, - delete = internal.encode_delete, - update = internal.encode_update, - upsert = internal.encode_upsert, - select = internal.encode_select, - execute = internal.encode_execute, - prepare = internal.encode_prepare, - unprepare = internal.encode_prepare, - get = internal.encode_select, - min = internal.encode_select, - max = internal.encode_select, - count = internal.encode_call, - -- inject raw data into connection, used by console and tests - inject = function(buf, id, bytes) -- luacheck: no unused args + [M_PING] = internal.encode_ping, + [M_CALL_16] = internal.encode_call_16, + [M_CALL_17] = internal.encode_call, + [M_EVAL] = internal.encode_eval, + [M_INSERT] = internal.encode_insert, + [M_REPLACE] = internal.encode_replace, + [M_DELETE] = internal.encode_delete, + [M_UPDATE] = internal.encode_update, + [M_UPSERT] = internal.encode_upsert, + [M_SELECT] = internal.encode_select, + [M_EXECUTE] = internal.encode_execute, + [M_PREPARE] = internal.encode_prepare, + [M_UNPREPARE] = internal.encode_prepare, + [M_GET] = internal.encode_select, + [M_MIN] = internal.encode_select, + [M_MAX] = internal.encode_select, + [M_COUNT] = internal.encode_call, + [M_INJECT] = function(buf, id, bytes) -- luacheck: no unused args local ptr = buf:reserve(#bytes) ffi.copy(ptr, bytes, #bytes) buf.wpos = ptr + #bytes @@ -117,24 +137,24 @@ local method_encoder = { } local method_decoder = { - ping = decode_nil, - call_16 = internal.decode_select, - call_17 = decode_data, - eval = decode_data, - insert = decode_tuple, - replace = decode_tuple, - delete = decode_tuple, - update = decode_tuple, - upsert = decode_nil, - select = internal.decode_select, - execute = internal.decode_execute, - prepare = internal.decode_prepare, - unprepare = decode_nil, - get = decode_tuple, - min = decode_tuple, - max = decode_tuple, - count = decode_count, - inject = decode_data, + [M_PING] = decode_nil, + [M_CALL_16] = internal.decode_select, + [M_CALL_17] = decode_data, + [M_EVAL] = decode_data, + [M_INSERT] = decode_tuple, + [M_REPLACE] = decode_tuple, + [M_DELETE] = decode_tuple, + [M_UPDATE] = decode_tuple, + [M_UPSERT] = decode_nil, + [M_SELECT] = internal.decode_select, + [M_EXECUTE] = internal.decode_execute, + [M_PREPARE] = internal.decode_prepare, + [M_UNPREPARE] = decode_nil, + [M_GET] = decode_tuple, + [M_MIN] = decode_tuple, + [M_MAX] = decode_tuple, + [M_COUNT] = decode_count, + [M_INJECT] = decode_data, } local function decode_error(raw_data) @@ -1208,7 +1228,7 @@ end function remote_methods:ping(opts) check_remote_arg(self, 'ping') - return (pcall(self._request, self, 'ping', opts)) + return (pcall(self._request, self, M_PING, opts)) end function remote_methods:reload_schema() @@ -1219,14 +1239,14 @@ end -- @deprecated since 1.7.4 function remote_methods:call_16(func_name, ...) check_remote_arg(self, 'call') - return (self:_request('call_16', nil, nil, tostring(func_name), {...})) + return (self:_request(M_CALL_16, nil, nil, tostring(func_name), {...})) end function remote_methods:call(func_name, args, opts) check_remote_arg(self, 'call') check_call_args(args) args = args or {} - local res = self:_request('call_17', opts, nil, tostring(func_name), args) + local res = self:_request(M_CALL_17, opts, nil, tostring(func_name), args) if type(res) ~= 'table' or opts and opts.is_async then return res end @@ -1236,14 +1256,14 @@ end -- @deprecated since 1.7.4 function remote_methods:eval_16(code, ...) check_remote_arg(self, 'eval') - return unpack((self:_request('eval', nil, nil, code, {...}))) + return unpack((self:_request(M_EVAL, nil, nil, code, {...}))) end function remote_methods:eval(code, args, opts) check_remote_arg(self, 'eval') check_eval_args(args) args = args or {} - local res = self:_request('eval', opts, nil, code, args) + local res = self:_request(M_EVAL, opts, nil, code, args) if type(res) ~= 'table' or opts and opts.is_async then return res end @@ -1255,7 +1275,7 @@ function remote_methods:execute(query, parameters, sql_opts, netbox_opts) if sql_opts ~= nil then box.error(box.error.UNSUPPORTED, "execute", "options") end - return self:_request('execute', netbox_opts, nil, query, parameters or {}, + return self:_request(M_EXECUTE, netbox_opts, nil, query, parameters or {}, sql_opts or {}) end @@ -1267,7 +1287,7 @@ function remote_methods:prepare(query, parameters, sql_opts, netbox_opts) -- lua if sql_opts ~= nil then box.error(box.error.UNSUPPORTED, "prepare", "options") end - return self:_request('prepare', netbox_opts, nil, query) + return self:_request(M_PREPARE, netbox_opts, nil, query) end function remote_methods:unprepare(query, parameters, sql_opts, netbox_opts) @@ -1278,7 +1298,7 @@ function remote_methods:unprepare(query, parameters, sql_opts, netbox_opts) if sql_opts ~= nil then box.error(box.error.UNSUPPORTED, "unprepare", "options") end - return self:_request('unprepare', netbox_opts, nil, query, parameters or {}, + return self:_request(M_UNPREPARE, netbox_opts, nil, query, parameters or {}, sql_opts or {}) end @@ -1436,11 +1456,11 @@ function console_methods:eval(line, timeout) end if self.protocol == 'Binary' then local loader = 'return require("console").eval(...)' - res, err = pr(timeout, nil, false, 'eval', nil, nil, nil, loader, + res, err = pr(timeout, nil, false, M_EVAL, nil, nil, nil, loader, {line}) else assert(self.protocol == 'Lua console') - res, err = pr(timeout, nil, false, 'inject', nil, nil, nil, + res, err = pr(timeout, nil, false, M_INJECT, nil, nil, nil, line..'$EOF$\n') end if err then @@ -1460,12 +1480,14 @@ space_metatable = function(remote) function methods:insert(tuple, opts) check_space_arg(self, 'insert') - return remote:_request('insert', opts, self._format_cdata, self.id, tuple) + return remote:_request(M_INSERT, opts, self._format_cdata, self.id, + tuple) end function methods:replace(tuple, opts) check_space_arg(self, 'replace') - return remote:_request('replace', opts, self._format_cdata, self.id, tuple) + return remote:_request(M_REPLACE, opts, self._format_cdata, self.id, + tuple) end function methods:select(key, opts) @@ -1485,8 +1507,8 @@ space_metatable = function(remote) function methods:upsert(key, oplist, opts) check_space_arg(self, 'upsert') - return nothing_or_data(remote:_request('upsert', opts, nil, self.id, key, - oplist)) + return nothing_or_data(remote:_request(M_UPSERT, opts, nil, self.id, + key, oplist)) end function methods:get(key, opts) @@ -1515,7 +1537,7 @@ index_metatable = function(remote) local iterator = check_iterator_type(opts, key_is_nil) local offset = tonumber(opts and opts.offset) or 0 local limit = tonumber(opts and opts.limit) or 0xFFFFFFFF - return (remote:_request('select', opts, self.space._format_cdata, + return (remote:_request(M_SELECT, opts, self.space._format_cdata, self.space.id, self.id, iterator, offset, limit, key)) end @@ -1525,7 +1547,7 @@ index_metatable = function(remote) if opts and opts.buffer then error("index:get() doesn't support `buffer` argument") end - return nothing_or_data(remote:_request('get', opts, + return nothing_or_data(remote:_request(M_GET, opts, self.space._format_cdata, self.space.id, self.id, box.index.EQ, 0, 2, key)) @@ -1536,7 +1558,7 @@ index_metatable = function(remote) if opts and opts.buffer then error("index:min() doesn't support `buffer` argument") end - return nothing_or_data(remote:_request('min', opts, + return nothing_or_data(remote:_request(M_MIN, opts, self.space._format_cdata, self.space.id, self.id, box.index.GE, 0, 1, key)) @@ -1547,7 +1569,7 @@ index_metatable = function(remote) if opts and opts.buffer then error("index:max() doesn't support `buffer` argument") end - return nothing_or_data(remote:_request('max', opts, + return nothing_or_data(remote:_request(M_MAX, opts, self.space._format_cdata, self.space.id, self.id, box.index.LE, 0, 1, key)) @@ -1560,19 +1582,19 @@ index_metatable = function(remote) end local code = string.format('box.space.%s.index.%s:count', self.space.name, self.name) - return remote:_request('count', opts, nil, code, { key, opts }) + return remote:_request(M_COUNT, opts, nil, code, { key, opts }) end function methods:delete(key, opts) check_index_arg(self, 'delete') - return nothing_or_data(remote:_request('delete', opts, + return nothing_or_data(remote:_request(M_DELETE, opts, self.space._format_cdata, self.space.id, self.id, key)) end function methods:update(key, oplist, opts) check_index_arg(self, 'update') - return nothing_or_data(remote:_request('update', opts, + return nothing_or_data(remote:_request(M_UPDATE, opts, self.space._format_cdata, self.space.id, self.id, key, oplist)) @@ -1587,6 +1609,26 @@ local this_module = { new = connect, -- Tarantool < 1.7.1 compatibility, wrap = wrap, establish_connection = establish_connection, + _method = { -- for tests + ping = M_PING, + call_16 = M_CALL_16, + call_17 = M_CALL_17, + eval = M_EVAL, + insert = M_INSERT, + replace = M_REPLACE, + delete = M_DELETE, + update = M_UPDATE, + upsert = M_UPSERT, + select = M_SELECT, + execute = M_EXECUTE, + prepare = M_PREPARE, + unprepare = M_UNPREPARE, + get = M_GET, + min = M_MIN, + max = M_MAX, + count = M_COUNT, + inject = M_INJECT, + } } function this_module.timeout(timeout, ...) diff --git a/test/box/access.result b/test/box/access.result index 1a8730f1a2d9..712cd68f84fd 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -4,6 +4,9 @@ env = require('test_run') test_run = env.new() --- ... +net = require('net.box') +--- +... session = box.session --- ... @@ -304,7 +307,7 @@ LISTEN ~= nil --- - true ... -c = (require 'net.box').connect(LISTEN.host, LISTEN.service) +c = net.connect(LISTEN.host, LISTEN.service) --- ... c:call('nosuchfunction') @@ -350,6 +353,9 @@ box.snapshot() - ok ... test_run:cmd('restart server default') +net = require('net.box') +--- +... box.schema.user.drop('testus') --- ... @@ -899,18 +905,18 @@ box.schema.func.drop(name) LISTEN = require('uri').parse(box.cfg.listen) --- ... -c = (require 'net.box').connect(LISTEN.host, LISTEN.service) +c = net.connect(LISTEN.host, LISTEN.service) --- ... -c:_request("select", nil, nil, 1, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) +c:_request(net._method.select, nil, nil, 1, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) --- - error: Space '1' does not exist ... -c:_request("select", nil, nil, 65537, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) +c:_request(net._method.select, nil, nil, 65537, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) --- - error: Space '65537' does not exist ... -c:_request("select", nil, nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) +c:_request(net._method.select, nil, nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) --- - error: Space '4294967295' does not exist ... @@ -1124,7 +1130,7 @@ session.su("test") --- - error: Session access to universe '' is denied for user 'test' ... -c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test", password="pass"}) +c = net.connect(LISTEN.host, LISTEN.service, {user="test", password="pass"}) --- ... c.state @@ -1320,7 +1326,7 @@ euid, auid box.session.su("admin") --- ... -c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) +c = net.connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) --- ... function func() end @@ -1379,7 +1385,7 @@ box.session.su("admin") box.schema.user.revoke("test_user", "session", "universe") --- ... -c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) +c = net.connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) --- ... obj_type, obj_name, op_type @@ -1834,7 +1840,7 @@ seq = box.schema.sequence.create("test") box.schema.func.create("func") --- ... -c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user='tester', password = '123'}) +c = net.connect(LISTEN.host, LISTEN.service, {user='tester', password = '123'}) --- ... box.session.su("tester", s.select, s) diff --git a/test/box/access.test.lua b/test/box/access.test.lua index 2bf772b7b38e..6060475d1426 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -1,5 +1,6 @@ env = require('test_run') test_run = env.new() +net = require('net.box') session = box.session -- user id for a Lua session is admin - 1 @@ -138,7 +139,7 @@ box.schema.user.drop('Петя_Иванов') -- gh-300: misleading error message if a function does not exist LISTEN = require('uri').parse(box.cfg.listen) LISTEN ~= nil -c = (require 'net.box').connect(LISTEN.host, LISTEN.service) +c = net.connect(LISTEN.host, LISTEN.service) c:call('nosuchfunction') function nosuchfunction() end @@ -155,6 +156,7 @@ box.schema.user.grant('testus', 'write', 'space', 'admin_space') s:drop() box.snapshot() test_run:cmd('restart server default') +net = require('net.box') box.schema.user.drop('testus') -- ------------------------------------------------------------ -- a test case for gh-289 @@ -348,10 +350,10 @@ box.schema.func.drop(name) -- Verify that when trying to access a non-existing or -- very large space id, no crash occurs. LISTEN = require('uri').parse(box.cfg.listen) -c = (require 'net.box').connect(LISTEN.host, LISTEN.service) -c:_request("select", nil, nil, 1, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) -c:_request("select", nil, nil, 65537, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) -c:_request("select", nil, nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) +c = net.connect(LISTEN.host, LISTEN.service) +c:_request(net._method.select, nil, nil, 1, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) +c:_request(net._method.select, nil, nil, 65537, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) +c:_request(net._method.select, nil, nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) c:close() session = box.session @@ -429,7 +431,7 @@ box.schema.user.disable("test") -- test double disable is a no op box.schema.user.disable("test") session.su("test") -c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test", password="pass"}) +c = net.connect(LISTEN.host, LISTEN.service, {user="test", password="pass"}) c.state c.error @@ -499,7 +501,7 @@ seq:set(1) obj_type, obj_name, op_type euid, auid box.session.su("admin") -c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) +c = net.connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) function func() end st, e = pcall(c.call, c, func) obj_type, op_type @@ -514,7 +516,7 @@ obj_type, obj_name, op_type euid, auid box.session.su("admin") box.schema.user.revoke("test_user", "session", "universe") -c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) +c = net.connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) obj_type, obj_name, op_type euid, auid box.session.on_access_denied(nil, access_denied_trigger) @@ -710,7 +712,7 @@ s = box.schema.space.create("test") _ = s:create_index("primary", {parts={1, "unsigned"}}) seq = box.schema.sequence.create("test") box.schema.func.create("func") -c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user='tester', password = '123'}) +c = net.connect(LISTEN.host, LISTEN.service, {user='tester', password = '123'}) box.session.su("tester", s.select, s) box.session.su("tester", seq.set, seq, 1) diff --git a/test/box/net.box_console_connections_gh-2677.result b/test/box/net.box_console_connections_gh-2677.result index c9116e5d487a..f45aa0b56646 100644 --- a/test/box/net.box_console_connections_gh-2677.result +++ b/test/box/net.box_console_connections_gh-2677.result @@ -74,7 +74,7 @@ c.space.test:delete{1} -- -- Break a connection to test reconnect_after. -- -_ = c._transport.perform_request(nil, nil, false, 'inject', nil, nil, nil, '\x80') +_ = c._transport.perform_request(nil, nil, false, net._method.inject, nil, nil, nil, '\x80') --- ... while not c:is_connected() do fiber.sleep(0.01) end diff --git a/test/box/net.box_console_connections_gh-2677.test.lua b/test/box/net.box_console_connections_gh-2677.test.lua index c6c9ea84677d..40d099e708a3 100644 --- a/test/box/net.box_console_connections_gh-2677.test.lua +++ b/test/box/net.box_console_connections_gh-2677.test.lua @@ -30,7 +30,7 @@ c.space.test:delete{1} -- -- Break a connection to test reconnect_after. -- -_ = c._transport.perform_request(nil, nil, false, 'inject', nil, nil, nil, '\x80') +_ = c._transport.perform_request(nil, nil, false, net._method.inject, nil, nil, nil, '\x80') while not c:is_connected() do fiber.sleep(0.01) end c:ping() diff --git a/test/box/net.box_incorrect_iterator_gh-841.result b/test/box/net.box_incorrect_iterator_gh-841.result index debfd3e4cf39..fbd2a7700e5b 100644 --- a/test/box/net.box_incorrect_iterator_gh-841.result +++ b/test/box/net.box_incorrect_iterator_gh-841.result @@ -16,11 +16,14 @@ test_run:cmd("setopt delimiter ';'") - true ... function x_select(cn, space_id, index_id, iterator, offset, limit, key, opts) - local ret = cn:_request('select', opts, nil, space_id, index_id, iterator, - offset, limit, key) + local ret = cn:_request(remote._method.select, opts, nil, space_id, + index_id, iterator, offset, limit, key) return ret end -function x_fatal(cn) cn._transport.perform_request(nil, nil, false, 'inject', nil, nil, nil, '\x80') end +function x_fatal(cn) + cn._transport.perform_request(nil, nil, false, remote._method.inject, + nil, nil, nil, '\x80') +end test_run:cmd("setopt delimiter ''"); --- ... diff --git a/test/box/net.box_incorrect_iterator_gh-841.test.lua b/test/box/net.box_incorrect_iterator_gh-841.test.lua index cd431a57a46a..1d24f9f56a9b 100644 --- a/test/box/net.box_incorrect_iterator_gh-841.test.lua +++ b/test/box/net.box_incorrect_iterator_gh-841.test.lua @@ -5,11 +5,14 @@ test_run:cmd("push filter ".."'\\.lua.*:[0-9]+: ' to '.lua...\"]:<line>: '") test_run:cmd("setopt delimiter ';'") function x_select(cn, space_id, index_id, iterator, offset, limit, key, opts) - local ret = cn:_request('select', opts, nil, space_id, index_id, iterator, - offset, limit, key) + local ret = cn:_request(remote._method.select, opts, nil, space_id, + index_id, iterator, offset, limit, key) return ret end -function x_fatal(cn) cn._transport.perform_request(nil, nil, false, 'inject', nil, nil, nil, '\x80') end +function x_fatal(cn) + cn._transport.perform_request(nil, nil, false, remote._method.inject, + nil, nil, nil, '\x80') +end test_run:cmd("setopt delimiter ''"); LISTEN = require('uri').parse(box.cfg.listen) diff --git a/test/box/net.box_iproto_hangs_gh-3464.result b/test/box/net.box_iproto_hangs_gh-3464.result index d425bf78c3ed..3b5458c9a251 100644 --- a/test/box/net.box_iproto_hangs_gh-3464.result +++ b/test/box/net.box_iproto_hangs_gh-3464.result @@ -17,7 +17,7 @@ c = net:connect(box.cfg.listen) data = msgpack.encode(18400000000000000000)..'aaaaaaa' --- ... -c._transport.perform_request(nil, nil, false, 'inject', nil, nil, nil, data) +c._transport.perform_request(nil, nil, false, net._method.inject, nil, nil, nil, data) --- - null - Peer closed diff --git a/test/box/net.box_iproto_hangs_gh-3464.test.lua b/test/box/net.box_iproto_hangs_gh-3464.test.lua index 77551e415c1f..a7c41ae7680a 100644 --- a/test/box/net.box_iproto_hangs_gh-3464.test.lua +++ b/test/box/net.box_iproto_hangs_gh-3464.test.lua @@ -8,6 +8,6 @@ net = require('net.box') -- c = net:connect(box.cfg.listen) data = msgpack.encode(18400000000000000000)..'aaaaaaa' -c._transport.perform_request(nil, nil, false, 'inject', nil, nil, nil, data) +c._transport.perform_request(nil, nil, false, net._method.inject, nil, nil, nil, data) c:close() test_run:grep_log('default', 'too big packet size in the header') ~= nil diff --git a/test/box/net.box_long-poll_input_gh-3400.result b/test/box/net.box_long-poll_input_gh-3400.result index 062bd563a20a..52df89d533e9 100644 --- a/test/box/net.box_long-poll_input_gh-3400.result +++ b/test/box/net.box_long-poll_input_gh-3400.result @@ -1,3 +1,6 @@ +test_run = require('test_run').new() +--- +... fiber = require 'fiber' --- ... @@ -24,7 +27,15 @@ c:ping() -- new attempts to read any data - the connection is closed -- already. -- -f = fiber.create(c._transport.perform_request, nil, nil, false, 'call_17', nil, nil, nil, 'long', {}) c._transport.perform_request(nil, nil, false, 'inject', nil, nil, nil, '\x80') +test_run:cmd("setopt delimiter ';'") +--- +- true +... +f = fiber.create(c._transport.perform_request, nil, nil, false, + net._method.call_17, nil, nil, nil, 'long', {}) +c._transport.perform_request(nil, nil, false, net._method.inject, + nil, nil, nil, '\x80') +test_run:cmd("setopt delimiter ''"); --- ... while f:status() ~= 'dead' do fiber.sleep(0.01) end diff --git a/test/box/net.box_long-poll_input_gh-3400.test.lua b/test/box/net.box_long-poll_input_gh-3400.test.lua index bc9db1e69af2..99e821a128b6 100644 --- a/test/box/net.box_long-poll_input_gh-3400.test.lua +++ b/test/box/net.box_long-poll_input_gh-3400.test.lua @@ -1,3 +1,4 @@ +test_run = require('test_run').new() fiber = require 'fiber' net = require('net.box') @@ -14,6 +15,11 @@ c:ping() -- new attempts to read any data - the connection is closed -- already. -- -f = fiber.create(c._transport.perform_request, nil, nil, false, 'call_17', nil, nil, nil, 'long', {}) c._transport.perform_request(nil, nil, false, 'inject', nil, nil, nil, '\x80') +test_run:cmd("setopt delimiter ';'") +f = fiber.create(c._transport.perform_request, nil, nil, false, + net._method.call_17, nil, nil, nil, 'long', {}) +c._transport.perform_request(nil, nil, false, net._method.inject, + nil, nil, nil, '\x80') +test_run:cmd("setopt delimiter ''"); while f:status() ~= 'dead' do fiber.sleep(0.01) end c:close() -- 2.25.1
luamp_encode copies its argument to the top of Lua stack unless it's already on the top so netbox_encode_{update,upsert} encode key/tuple and ops in reverse order to avoid the copy. There's no much point in it, because copying an argument in Lua stack costs nearly nothing - it's just a pointer copy. I ran a simple net.box test doing upserts and saw no difference with and without this optimization. Let's remove it, because it's easier to rewrite parts of net.box in C without it. --- src/box/lua/net_box.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index ed1df4a189d2..8952efb7bb39 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -366,15 +366,13 @@ netbox_encode_update(lua_State *L) mpstream_encode_uint(&stream, IPROTO_INDEX_BASE); mpstream_encode_uint(&stream, 1); - /* encode in reverse order for speedup - see luamp_encode() code */ + /* encode key */ + mpstream_encode_uint(&stream, IPROTO_KEY); + luamp_convert_key(L, cfg, &stream, 5); + /* encode ops */ mpstream_encode_uint(&stream, IPROTO_TUPLE); luamp_encode_tuple(L, cfg, &stream, 6); - lua_pop(L, 1); /* ops */ - - /* encode key */ - mpstream_encode_uint(&stream, IPROTO_KEY); - luamp_convert_key(L, cfg, &stream, 5); netbox_encode_request(&stream, svp); return 0; @@ -402,16 +400,14 @@ netbox_encode_upsert(lua_State *L) mpstream_encode_uint(&stream, IPROTO_INDEX_BASE); mpstream_encode_uint(&stream, 1); - /* encode in reverse order for speedup - see luamp_encode() code */ - /* encode ops */ - mpstream_encode_uint(&stream, IPROTO_OPS); - luamp_encode_tuple(L, cfg, &stream, 5); - lua_pop(L, 1); /* ops */ - /* encode tuple */ mpstream_encode_uint(&stream, IPROTO_TUPLE); luamp_encode_tuple(L, cfg, &stream, 4); + /* encode ops */ + mpstream_encode_uint(&stream, IPROTO_OPS); + luamp_encode_tuple(L, cfg, &stream, 5); + netbox_encode_request(&stream, svp); return 0; } -- 2.25.1
This patch moves method_encoder table from Lua to C. This is a step towards rewriting performance-critical parts of net.box in C. --- src/box/lua/net_box.c | 345 +++++++++++++++++++++++----------------- src/box/lua/net_box.lua | 41 ++--- 2 files changed, 208 insertions(+), 178 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 8952efb7bb39..49030aabea69 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -52,16 +52,34 @@ #define cfg luaL_msgpack_default +enum netbox_method { + NETBOX_PING = 0, + NETBOX_CALL_16 = 1, + NETBOX_CALL_17 = 2, + NETBOX_EVAL = 3, + NETBOX_INSERT = 4, + NETBOX_REPLACE = 5, + NETBOX_DELETE = 6, + NETBOX_UPDATE = 7, + NETBOX_UPSERT = 8, + NETBOX_SELECT = 9, + NETBOX_EXECUTE = 10, + NETBOX_PREPARE = 11, + NETBOX_UNPREPARE = 12, + NETBOX_GET = 13, + NETBOX_MIN = 14, + NETBOX_MAX = 15, + NETBOX_COUNT = 16, + NETBOX_INJECT = 17, + netbox_method_MAX +}; + static inline size_t -netbox_prepare_request(lua_State *L, struct mpstream *stream, uint32_t r_type) +netbox_prepare_request(struct mpstream *stream, uint64_t sync, + enum iproto_type type) { - struct ibuf *ibuf = (struct ibuf *) lua_topointer(L, 1); - uint64_t sync = luaL_touint64(L, 2); - - mpstream_init(stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, - luamp_error, L); - /* Remember initial size of ibuf (see netbox_encode_request()) */ + struct ibuf *ibuf = (struct ibuf *) stream->ctx; size_t used = ibuf_used(ibuf); /* Reserve and skip space for fixheader */ @@ -76,7 +94,7 @@ netbox_prepare_request(lua_State *L, struct mpstream *stream, uint32_t r_type) mpstream_encode_uint(stream, sync); mpstream_encode_uint(stream, IPROTO_REQUEST_TYPE); - mpstream_encode_uint(stream, r_type); + mpstream_encode_uint(stream, type); /* Caller should remember how many bytes was used in ibuf */ return used; @@ -108,16 +126,15 @@ netbox_encode_request(struct mpstream *stream, size_t initial_size) mp_store_u32(fixheader, total_size - fixheader_size); } -static int -netbox_encode_ping(lua_State *L) +static void +netbox_encode_ping(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 2) - return luaL_error(L, "Usage: netbox.encode_ping(ibuf, sync)"); - + (void) idx; struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_PING); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_PING); netbox_encode_request(&stream, svp); - return 0; } static int @@ -127,9 +144,13 @@ netbox_encode_auth(lua_State *L) return luaL_error(L, "Usage: netbox.encode_update(ibuf, sync, " "user, password, greeting)"); } + struct ibuf *ibuf = (struct ibuf *) lua_topointer(L, 1); + uint64_t sync = luaL_touint64(L, 2); struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_AUTH); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_AUTH); size_t user_len; const char *user = lua_tolstring(L, 3, &user_len); @@ -157,91 +178,83 @@ netbox_encode_auth(lua_State *L) return 0; } -static int -netbox_encode_call_impl(lua_State *L, enum iproto_type type) +static void +netbox_encode_call_impl(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync, + enum iproto_type type) { - if (lua_gettop(L) < 4) { - return luaL_error(L, "Usage: netbox.encode_call(ibuf, sync, " - "function_name, args)"); - } - + /* Lua stack at idx: function_name, args */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, type); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, type); mpstream_encode_map(&stream, 2); /* encode proc name */ size_t name_len; - const char *name = lua_tolstring(L, 3, &name_len); + const char *name = lua_tolstring(L, idx, &name_len); mpstream_encode_uint(&stream, IPROTO_FUNCTION_NAME); mpstream_encode_strn(&stream, name, name_len); /* encode args */ mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, 4); + luamp_encode_tuple(L, cfg, &stream, idx + 1); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_call_16(lua_State *L) +static void +netbox_encode_call_16(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - return netbox_encode_call_impl(L, IPROTO_CALL_16); + netbox_encode_call_impl(L, idx, ibuf, sync, IPROTO_CALL_16); } -static int -netbox_encode_call(lua_State *L) +static void +netbox_encode_call(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - return netbox_encode_call_impl(L, IPROTO_CALL); + netbox_encode_call_impl(L, idx, ibuf, sync, IPROTO_CALL); } -static int -netbox_encode_eval(lua_State *L) +static void +netbox_encode_eval(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 4) { - return luaL_error(L, "Usage: netbox.encode_eval(ibuf, sync, " - "expr, args)"); - } - + /* Lua stack at idx: expr, args */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_EVAL); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_EVAL); mpstream_encode_map(&stream, 2); /* encode expr */ size_t expr_len; - const char *expr = lua_tolstring(L, 3, &expr_len); + const char *expr = lua_tolstring(L, idx, &expr_len); mpstream_encode_uint(&stream, IPROTO_EXPR); mpstream_encode_strn(&stream, expr, expr_len); /* encode args */ mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, 4); + luamp_encode_tuple(L, cfg, &stream, idx + 1); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_select(lua_State *L) +static void +netbox_encode_select(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 8) { - return luaL_error(L, "Usage netbox.encode_select(ibuf, sync, " - "space_id, index_id, iterator, offset, " - "limit, key)"); - } - + /* Lua stack at idx: space_id, index_id, iterator, offset, limit, key */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_SELECT); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_SELECT); mpstream_encode_map(&stream, 6); - uint32_t space_id = lua_tonumber(L, 3); - uint32_t index_id = lua_tonumber(L, 4); - int iterator = lua_tointeger(L, 5); - uint32_t offset = lua_tonumber(L, 6); - uint32_t limit = lua_tonumber(L, 7); + uint32_t space_id = lua_tonumber(L, idx); + uint32_t index_id = lua_tonumber(L, idx + 1); + int iterator = lua_tointeger(L, idx + 2); + uint32_t offset = lua_tonumber(L, idx + 3); + uint32_t limit = lua_tonumber(L, idx + 4); /* encode space_id */ mpstream_encode_uint(&stream, IPROTO_SPACE_ID); @@ -265,100 +278,93 @@ netbox_encode_select(lua_State *L) /* encode key */ mpstream_encode_uint(&stream, IPROTO_KEY); - luamp_convert_key(L, cfg, &stream, 8); + luamp_convert_key(L, cfg, &stream, idx + 5); netbox_encode_request(&stream, svp); - return 0; } -static inline int -netbox_encode_insert_or_replace(lua_State *L, uint32_t reqtype) +static void +netbox_encode_insert_or_replace(lua_State *L, int idx, struct ibuf *ibuf, + uint64_t sync, enum iproto_type type) { - if (lua_gettop(L) < 4) { - return luaL_error(L, "Usage: netbox.encode_insert(ibuf, sync, " - "space_id, tuple)"); - } + /* Lua stack at idx: space_id, tuple */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, reqtype); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, type); mpstream_encode_map(&stream, 2); /* encode space_id */ - uint32_t space_id = lua_tonumber(L, 3); + uint32_t space_id = lua_tonumber(L, idx); mpstream_encode_uint(&stream, IPROTO_SPACE_ID); mpstream_encode_uint(&stream, space_id); /* encode args */ mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, 4); + luamp_encode_tuple(L, cfg, &stream, idx + 1); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_insert(lua_State *L) +static void +netbox_encode_insert(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - return netbox_encode_insert_or_replace(L, IPROTO_INSERT); + netbox_encode_insert_or_replace(L, idx, ibuf, sync, IPROTO_INSERT); } -static int -netbox_encode_replace(lua_State *L) +static void +netbox_encode_replace(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - return netbox_encode_insert_or_replace(L, IPROTO_REPLACE); + netbox_encode_insert_or_replace(L, idx, ibuf, sync, IPROTO_REPLACE); } -static int -netbox_encode_delete(lua_State *L) +static void +netbox_encode_delete(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 5) { - return luaL_error(L, "Usage: netbox.encode_delete(ibuf, sync, " - "space_id, index_id, key)"); - } - + /* Lua stack at idx: space_id, index_id, key */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_DELETE); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_DELETE); mpstream_encode_map(&stream, 3); /* encode space_id */ - uint32_t space_id = lua_tonumber(L, 3); + uint32_t space_id = lua_tonumber(L, idx); mpstream_encode_uint(&stream, IPROTO_SPACE_ID); mpstream_encode_uint(&stream, space_id); /* encode space_id */ - uint32_t index_id = lua_tonumber(L, 4); + uint32_t index_id = lua_tonumber(L, idx + 1); mpstream_encode_uint(&stream, IPROTO_INDEX_ID); mpstream_encode_uint(&stream, index_id); /* encode key */ mpstream_encode_uint(&stream, IPROTO_KEY); - luamp_convert_key(L, cfg, &stream, 5); + luamp_convert_key(L, cfg, &stream, idx + 2); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_update(lua_State *L) +static void +netbox_encode_update(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 6) { - return luaL_error(L, "Usage: netbox.encode_update(ibuf, sync, " - "space_id, index_id, key, ops)"); - } - + /* Lua stack at idx: space_id, index_id, key, ops */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_UPDATE); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_UPDATE); mpstream_encode_map(&stream, 5); /* encode space_id */ - uint32_t space_id = lua_tonumber(L, 3); + uint32_t space_id = lua_tonumber(L, idx); mpstream_encode_uint(&stream, IPROTO_SPACE_ID); mpstream_encode_uint(&stream, space_id); /* encode index_id */ - uint32_t index_id = lua_tonumber(L, 4); + uint32_t index_id = lua_tonumber(L, idx + 1); mpstream_encode_uint(&stream, IPROTO_INDEX_ID); mpstream_encode_uint(&stream, index_id); @@ -368,31 +374,28 @@ netbox_encode_update(lua_State *L) /* encode key */ mpstream_encode_uint(&stream, IPROTO_KEY); - luamp_convert_key(L, cfg, &stream, 5); + luamp_convert_key(L, cfg, &stream, idx + 2); /* encode ops */ mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, 6); + luamp_encode_tuple(L, cfg, &stream, idx + 3); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_upsert(lua_State *L) +static void +netbox_encode_upsert(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) != 5) { - return luaL_error(L, "Usage: netbox.encode_upsert(ibuf, sync, " - "space_id, tuple, ops)"); - } - + /* Lua stack at idx: space_id, tuple, ops */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_UPSERT); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_UPSERT); mpstream_encode_map(&stream, 4); /* encode space_id */ - uint32_t space_id = lua_tonumber(L, 3); + uint32_t space_id = lua_tonumber(L, idx); mpstream_encode_uint(&stream, IPROTO_SPACE_ID); mpstream_encode_uint(&stream, space_id); @@ -402,14 +405,13 @@ netbox_encode_upsert(lua_State *L) /* encode tuple */ mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, 4); + luamp_encode_tuple(L, cfg, &stream, idx + 1); /* encode ops */ mpstream_encode_uint(&stream, IPROTO_OPS); - luamp_encode_tuple(L, cfg, &stream, 5); + luamp_encode_tuple(L, cfg, &stream, idx + 2); netbox_encode_request(&stream, svp); - return 0; } static int @@ -556,61 +558,123 @@ handle_error: return 2; } -static int -netbox_encode_execute(lua_State *L) +static void +netbox_encode_execute(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 5) - return luaL_error(L, "Usage: netbox.encode_execute(ibuf, "\ - "sync, query, parameters, options)"); + /* Lua stack at idx: query, parameters, options */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_EXECUTE); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_EXECUTE); mpstream_encode_map(&stream, 3); - if (lua_type(L, 3) == LUA_TNUMBER) { - uint32_t query_id = lua_tointeger(L, 3); + if (lua_type(L, idx) == LUA_TNUMBER) { + uint32_t query_id = lua_tointeger(L, idx); mpstream_encode_uint(&stream, IPROTO_STMT_ID); mpstream_encode_uint(&stream, query_id); } else { size_t len; - const char *query = lua_tolstring(L, 3, &len); + const char *query = lua_tolstring(L, idx, &len); mpstream_encode_uint(&stream, IPROTO_SQL_TEXT); mpstream_encode_strn(&stream, query, len); } mpstream_encode_uint(&stream, IPROTO_SQL_BIND); - luamp_encode_tuple(L, cfg, &stream, 4); + luamp_encode_tuple(L, cfg, &stream, idx + 1); mpstream_encode_uint(&stream, IPROTO_OPTIONS); - luamp_encode_tuple(L, cfg, &stream, 5); + luamp_encode_tuple(L, cfg, &stream, idx + 2); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_prepare(lua_State *L) +static void +netbox_encode_prepare(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 3) - return luaL_error(L, "Usage: netbox.encode_prepare(ibuf, "\ - "sync, query)"); + /* Lua stack at idx: query */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_PREPARE); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_PREPARE); mpstream_encode_map(&stream, 1); - if (lua_type(L, 3) == LUA_TNUMBER) { - uint32_t query_id = lua_tointeger(L, 3); + if (lua_type(L, idx) == LUA_TNUMBER) { + uint32_t query_id = lua_tointeger(L, idx); mpstream_encode_uint(&stream, IPROTO_STMT_ID); mpstream_encode_uint(&stream, query_id); } else { size_t len; - const char *query = lua_tolstring(L, 3, &len); + const char *query = lua_tolstring(L, idx, &len); mpstream_encode_uint(&stream, IPROTO_SQL_TEXT); mpstream_encode_strn(&stream, query, len); }; netbox_encode_request(&stream, svp); +} + +static void +netbox_encode_unprepare(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +{ + /* Lua stack at idx: query, parameters, options */ + netbox_encode_prepare(L, idx, ibuf, sync); +} + +static void +netbox_encode_inject(struct lua_State *L, int idx, struct ibuf *ibuf, + uint64_t sync) +{ + /* Lua stack at idx: bytes */ + (void) sync; + size_t len; + const char *data = lua_tolstring(L, idx, &len); + void *wpos = ibuf_alloc(ibuf, len); + if (wpos == NULL) + luaL_error(L, "out of memory"); + memcpy(wpos, data, len); +} + +/* + * Encodes a request for the specified method. + * + * Takes three mandatory arguments: + * - method: a value from the netbox_method enumeration + * - ibuf: buffer to write the result to + * - sync: value of the IPROTO_SYNC key + * + * Other arguments are method-specific. + */ +static int +netbox_encode_method(struct lua_State *L) +{ + typedef void (*method_encoder_f)(struct lua_State *L, int idx, + struct ibuf *ibuf, uint64_t sync); + static method_encoder_f method_encoder[] = { + [NETBOX_PING] = netbox_encode_ping, + [NETBOX_CALL_16] = netbox_encode_call_16, + [NETBOX_CALL_17] = netbox_encode_call, + [NETBOX_EVAL] = netbox_encode_eval, + [NETBOX_INSERT] = netbox_encode_insert, + [NETBOX_REPLACE] = netbox_encode_replace, + [NETBOX_DELETE] = netbox_encode_delete, + [NETBOX_UPDATE] = netbox_encode_update, + [NETBOX_UPSERT] = netbox_encode_upsert, + [NETBOX_SELECT] = netbox_encode_select, + [NETBOX_EXECUTE] = netbox_encode_execute, + [NETBOX_PREPARE] = netbox_encode_prepare, + [NETBOX_UNPREPARE] = netbox_encode_unprepare, + [NETBOX_GET] = netbox_encode_select, + [NETBOX_MIN] = netbox_encode_select, + [NETBOX_MAX] = netbox_encode_select, + [NETBOX_COUNT] = netbox_encode_call, + [NETBOX_INJECT] = netbox_encode_inject, + }; + enum netbox_method method = lua_tointeger(L, 1); + assert(method < netbox_method_MAX); + struct ibuf *ibuf = (struct ibuf *) lua_topointer(L, 2); + uint64_t sync = luaL_touint64(L, 3); + method_encoder[method](L, 4, ibuf, sync); return 0; } @@ -885,19 +949,8 @@ int luaopen_net_box(struct lua_State *L) { static const luaL_Reg net_box_lib[] = { - { "encode_ping", netbox_encode_ping }, - { "encode_call_16", netbox_encode_call_16 }, - { "encode_call", netbox_encode_call }, - { "encode_eval", netbox_encode_eval }, - { "encode_select", netbox_encode_select }, - { "encode_insert", netbox_encode_insert }, - { "encode_replace", netbox_encode_replace }, - { "encode_delete", netbox_encode_delete }, - { "encode_update", netbox_encode_update }, - { "encode_upsert", netbox_encode_upsert }, - { "encode_execute", netbox_encode_execute}, - { "encode_prepare", netbox_encode_prepare}, { "encode_auth", netbox_encode_auth }, + { "encode_method", netbox_encode_method }, { "decode_greeting",netbox_decode_greeting }, { "communicate", netbox_communicate }, { "decode_select", netbox_decode_select }, diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 76cfd40e3bee..bb844184fa01 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -24,7 +24,7 @@ local check_primary_index = box.internal.check_primary_index local communicate = internal.communicate local encode_auth = internal.encode_auth -local encode_select = internal.encode_select +local encode_method = internal.encode_method local decode_greeting = internal.decode_greeting local TIMEOUT_INFINITY = 500 * 365 * 86400 @@ -111,31 +111,6 @@ local function version_at_least(peer_version_id, major, minor, patch) return peer_version_id >= version_id(major, minor, patch) end -local method_encoder = { - [M_PING] = internal.encode_ping, - [M_CALL_16] = internal.encode_call_16, - [M_CALL_17] = internal.encode_call, - [M_EVAL] = internal.encode_eval, - [M_INSERT] = internal.encode_insert, - [M_REPLACE] = internal.encode_replace, - [M_DELETE] = internal.encode_delete, - [M_UPDATE] = internal.encode_update, - [M_UPSERT] = internal.encode_upsert, - [M_SELECT] = internal.encode_select, - [M_EXECUTE] = internal.encode_execute, - [M_PREPARE] = internal.encode_prepare, - [M_UNPREPARE] = internal.encode_prepare, - [M_GET] = internal.encode_select, - [M_MIN] = internal.encode_select, - [M_MAX] = internal.encode_select, - [M_COUNT] = internal.encode_call, - [M_INJECT] = function(buf, id, bytes) -- luacheck: no unused args - local ptr = buf:reserve(#bytes) - ffi.copy(ptr, bytes, #bytes) - buf.wpos = ptr + #bytes - end -} - local method_decoder = { [M_PING] = decode_nil, [M_CALL_16] = internal.decode_select, @@ -557,7 +532,7 @@ local function create_transport(host, port, user, password, callback, worker_fiber:wakeup() end local id = next_request_id - method_encoder[method](send_buf, id, ...) + encode_method(method, send_buf, id, ...) next_request_id = next_id(id) -- Request in most cases has maximum 10 members: -- method, buffer, skip_header, id, cond, errno, response, @@ -770,7 +745,7 @@ local function create_transport(host, port, user, password, callback, log.warn("Netbox text protocol support is deprecated since 1.10, ".. "please use require('console').connect() instead") local setup_delimiter = 'require("console").delimiter("$EOF$")\n' - method_encoder.inject(send_buf, nil, setup_delimiter) + encode_method(M_INJECT, send_buf, nil, setup_delimiter) local err, response = send_and_recv_console() if err then return error_sm(err, response) @@ -830,14 +805,16 @@ local function create_transport(host, port, user, password, callback, local select3_id local response = {} -- fetch everything from space _vspace, 2 = ITER_ALL - encode_select(send_buf, select1_id, VSPACE_ID, 0, 2, 0, 0xFFFFFFFF, nil) + encode_method(M_SELECT, send_buf, select1_id, VSPACE_ID, 0, 2, 0, + 0xFFFFFFFF, nil) -- fetch everything from space _vindex, 2 = ITER_ALL - encode_select(send_buf, select2_id, VINDEX_ID, 0, 2, 0, 0xFFFFFFFF, nil) + encode_method(M_SELECT, send_buf, select2_id, VINDEX_ID, 0, 2, 0, + 0xFFFFFFFF, nil) -- fetch everything from space _vcollation, 2 = ITER_ALL if peer_has_vcollation then select3_id = new_request_id() - encode_select(send_buf, select3_id, VCOLLATION_ID, 0, 2, 0, - 0xFFFFFFFF, nil) + encode_method(M_SELECT, send_buf, select3_id, VCOLLATION_ID, + 0, 2, 0, 0xFFFFFFFF, nil) end schema_version = nil -- any schema_version will do provided that -- 2.25.1
We'll need them to reimplement parts of net.box in C. --- src/lua/utils.c | 4 ++-- src/lua/utils.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lua/utils.c b/src/lua/utils.c index 34cec0eed928..ebc19206c856 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -44,8 +44,8 @@ static int luaT_newthread_ref = LUA_NOREF; static uint32_t CTID_STRUCT_IBUF; static uint32_t CTID_STRUCT_IBUF_PTR; -static uint32_t CTID_CHAR_PTR; -static uint32_t CTID_CONST_CHAR_PTR; +uint32_t CTID_CHAR_PTR; +uint32_t CTID_CONST_CHAR_PTR; uint32_t CTID_UUID; void * diff --git a/src/lua/utils.h b/src/lua/utils.h index 947d9240bf2f..969edca4528f 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -68,6 +68,8 @@ struct tt_uuid; */ extern struct lua_State *tarantool_L; +extern uint32_t CTID_CHAR_PTR; +extern uint32_t CTID_CONST_CHAR_PTR; extern uint32_t CTID_UUID; struct tt_uuid * -- 2.25.1
This patch moves method_decoder table from Lua to C. This is a step towards rewriting performance-critical parts of net.box in C. --- src/box/lua/net_box.c | 235 +++++++++++++++++++++++++++++++--------- src/box/lua/net_box.lua | 43 +------- 2 files changed, 189 insertions(+), 89 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 49030aabea69..c0c3725e5350 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -678,6 +678,79 @@ netbox_encode_method(struct lua_State *L) return 0; } +/** + * This function handles a response that is supposed to have an empty body + * (e.g. IPROTO_PING result). It doesn't decode anything per se. Instead it + * simply pushes nil to Lua stack and advances the data ptr to data_end. + */ +static void +netbox_decode_nil(struct lua_State *L, const char **data, + const char *data_end, struct tuple_format *format) +{ + (void) format; + *data = data_end; + lua_pushnil(L); +} + +/** + * This helper skips a MessagePack map header and IPROTO_DATA key so that + * *data points to the actual response content. + */ +static void +netbox_skip_to_data(const char **data) +{ + assert(mp_typeof(**data) == MP_MAP); + uint32_t map_size = mp_decode_map(data); + /* Until 2.0 body has no keys except DATA. */ + assert(map_size == 1); + (void) map_size; + uint32_t key = mp_decode_uint(data); + assert(key == IPROTO_DATA); + (void) key; +} + +/** + * Decode Tarantool response body consisting of single + * IPROTO_DATA key into Lua table. + * @param L Lua stack to push result on. + * @param data MessagePack. + * @retval Lua table + */ +static void +netbox_decode_table(struct lua_State *L, const char **data, + const char *data_end, struct tuple_format *format) +{ + (void) data_end; + (void) format; + netbox_skip_to_data(data); + luamp_decode(L, cfg, data); +} + +/** + * Same as netbox_decode_table, but only decodes the first element of the + * table, skipping the rest. Used to decode index.count() call result. + * @param L Lua stack to push result on. + * @param data MessagePack. + * @retval count or nil. + */ +static void +netbox_decode_value(struct lua_State *L, const char **data, + const char *data_end, struct tuple_format *format) +{ + (void) data_end; + (void) format; + netbox_skip_to_data(data); + uint32_t count = mp_decode_array(data); + for (uint32_t i = 0; i < count; ++i) { + if (i == 0) + luamp_decode(L, cfg, data); + else + mp_next(data); + } + if (count == 0) + lua_pushnil(L); +} + /** * Decode IPROTO_DATA into tuples array. * @param L Lua stack to push result on. @@ -704,31 +777,45 @@ netbox_decode_data(struct lua_State *L, const char **data, /** * Decode Tarantool response body consisting of single * IPROTO_DATA key into array of tuples. - * @param Lua stack[1] Raw MessagePack pointer. - * @retval Tuples array and position of the body end. + * @param L Lua stack to push result on. + * @param data MessagePack. + * @retval Tuples array. */ -static int -netbox_decode_select(struct lua_State *L) +static void +netbox_decode_select(struct lua_State *L, const char **data, + const char *data_end, struct tuple_format *format) { - uint32_t ctypeid; - assert(lua_gettop(L) == 3); - struct tuple_format *format; - if (lua_type(L, 3) == LUA_TCDATA) - format = lbox_check_tuple_format(L, 3); - else - format = tuple_format_runtime; - const char *data = *(const char **)luaL_checkcdata(L, 1, &ctypeid); - assert(mp_typeof(*data) == MP_MAP); - uint32_t map_size = mp_decode_map(&data); - /* Until 2.0 body has no keys except DATA. */ - assert(map_size == 1); - (void) map_size; - uint32_t key = mp_decode_uint(&data); - assert(key == IPROTO_DATA); - (void) key; - netbox_decode_data(L, &data, format); - *(const char **)luaL_pushcdata(L, ctypeid) = data; - return 2; + (void) data_end; + netbox_skip_to_data(data); + netbox_decode_data(L, data, format); +} + +/** + * Same as netbox_decode_select, but only decodes the first tuple of the array, + * skipping the rest. + * @param L Lua stack to push result on. + * @param data MessagePack. + * @retval Tuple or nil. + */ +static void +netbox_decode_tuple(struct lua_State *L, const char **data, + const char *data_end, struct tuple_format *format) +{ + (void) data_end; + netbox_skip_to_data(data); + uint32_t count = mp_decode_array(data); + for (uint32_t i = 0; i < count; ++i) { + const char *begin = *data; + mp_next(data); + if (i > 0) + continue; + struct tuple *tuple = box_tuple_new(format, begin, *data); + if (tuple == NULL) + luaT_error(L); + luaT_pushtuple(L, tuple); + } + if (count == 0) + lua_pushnil(L); } /** Decode optional (i.e. may be present in response) metadata fields. */ @@ -847,28 +934,29 @@ netbox_decode_sql_info(struct lua_State *L, const char **data) } } -static int -netbox_decode_execute(struct lua_State *L) +static void +netbox_decode_execute(struct lua_State *L, const char **data, + const char *data_end, struct tuple_format *format) { - uint32_t ctypeid; - const char *data = *(const char **)luaL_checkcdata(L, 1, &ctypeid); - assert(mp_typeof(*data) == MP_MAP); - uint32_t map_size = mp_decode_map(&data); + (void) data_end; + (void) format; + assert(mp_typeof(**data) == MP_MAP); + uint32_t map_size = mp_decode_map(data); int rows_index = 0, meta_index = 0, info_index = 0; for (uint32_t i = 0; i < map_size; ++i) { - uint32_t key = mp_decode_uint(&data); + uint32_t key = mp_decode_uint(data); switch(key) { case IPROTO_DATA: - netbox_decode_data(L, &data, tuple_format_runtime); + netbox_decode_data(L, data, tuple_format_runtime); rows_index = i - map_size; break; case IPROTO_METADATA: - netbox_decode_metadata(L, &data); + netbox_decode_metadata(L, data); meta_index = i - map_size; break; default: assert(key == IPROTO_SQL_INFO); - netbox_decode_sql_info(L, &data); + netbox_decode_sql_info(L, data); info_index = i - map_size; break; } @@ -885,42 +973,41 @@ netbox_decode_execute(struct lua_State *L) assert(meta_index == 0); assert(rows_index == 0); } - *(const char **)luaL_pushcdata(L, ctypeid) = data; - return 2; } -static int -netbox_decode_prepare(struct lua_State *L) +static void +netbox_decode_prepare(struct lua_State *L, const char **data, + const char *data_end, struct tuple_format *format) { - uint32_t ctypeid; - const char *data = *(const char **)luaL_checkcdata(L, 1, &ctypeid); - assert(mp_typeof(*data) == MP_MAP); - uint32_t map_size = mp_decode_map(&data); + (void) data_end; + (void) format; + assert(mp_typeof(**data) == MP_MAP); + uint32_t map_size = mp_decode_map(data); int stmt_id_idx = 0, meta_idx = 0, bind_meta_idx = 0, bind_count_idx = 0; uint32_t stmt_id = 0; for (uint32_t i = 0; i < map_size; ++i) { - uint32_t key = mp_decode_uint(&data); + uint32_t key = mp_decode_uint(data); switch(key) { case IPROTO_STMT_ID: { - stmt_id = mp_decode_uint(&data); + stmt_id = mp_decode_uint(data); luaL_pushuint64(L, stmt_id); stmt_id_idx = i - map_size; break; } case IPROTO_METADATA: { - netbox_decode_metadata(L, &data); + netbox_decode_metadata(L, data); meta_idx = i - map_size; break; } case IPROTO_BIND_METADATA: { - netbox_decode_metadata(L, &data); + netbox_decode_metadata(L, data); bind_meta_idx = i - map_size; break; } default: { assert(key == IPROTO_BIND_COUNT); - uint32_t bind_count = mp_decode_uint(&data); + uint32_t bind_count = mp_decode_uint(data); luaL_pushuint64(L, bind_count); bind_count_idx = i - map_size; break; @@ -940,8 +1027,58 @@ netbox_decode_prepare(struct lua_State *L) lua_pushvalue(L, meta_idx - 1); lua_setfield(L, -2, "metadata"); } +} - *(const char **)luaL_pushcdata(L, ctypeid) = data; +/* + * Decodes a response body for the specified method. Pushes the result and the + * end of the decoded data to Lua stack. + * + * Takes the following arguments: + * - method: a value from the netbox_method enumeration + * - data: pointer to the data to decode (char ptr) + * - data_end: pointer to the end of the data (char ptr) + * - format: tuple format to use for decoding the body or nil + */ +static int +netbox_decode_method(struct lua_State *L) +{ + typedef void (*method_decoder_f)(struct lua_State *L, const char **data, + const char *data_end, + struct tuple_format *format); + static method_decoder_f method_decoder[] = { + [NETBOX_PING] = netbox_decode_nil, + [NETBOX_CALL_16] = netbox_decode_select, + [NETBOX_CALL_17] = netbox_decode_table, + [NETBOX_EVAL] = netbox_decode_table, + [NETBOX_INSERT] = netbox_decode_tuple, + [NETBOX_REPLACE] = netbox_decode_tuple, + [NETBOX_DELETE] = netbox_decode_tuple, + [NETBOX_UPDATE] = netbox_decode_tuple, + [NETBOX_UPSERT] = netbox_decode_nil, + [NETBOX_SELECT] = netbox_decode_select, + [NETBOX_EXECUTE] = netbox_decode_execute, + [NETBOX_PREPARE] = netbox_decode_prepare, + [NETBOX_UNPREPARE] = netbox_decode_nil, + [NETBOX_GET] = netbox_decode_tuple, + [NETBOX_MIN] = netbox_decode_tuple, + [NETBOX_MAX] = netbox_decode_tuple, + [NETBOX_COUNT] = netbox_decode_value, + [NETBOX_INJECT] = netbox_decode_table, + }; + enum netbox_method method = lua_tointeger(L, 1); + assert(method < netbox_method_MAX); + uint32_t ctypeid; + const char *data = *(const char **)luaL_checkcdata(L, 2, &ctypeid); + assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); + const char *data_end = *(const char **)luaL_checkcdata(L, 3, &ctypeid); + assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); + struct tuple_format *format; + if (!lua_isnil(L, 4)) + format = lbox_check_tuple_format(L, 4); + else + format = tuple_format_runtime; + method_decoder[method](L, &data, data_end, format); + *(const char **)luaL_pushcdata(L, CTID_CONST_CHAR_PTR) = data; return 2; } @@ -952,10 +1089,8 @@ luaopen_net_box(struct lua_State *L) { "encode_auth", netbox_encode_auth }, { "encode_method", netbox_encode_method }, { "decode_greeting",netbox_decode_greeting }, + { "decode_method", netbox_decode_method }, { "communicate", netbox_communicate }, - { "decode_select", netbox_decode_select }, - { "decode_execute", netbox_decode_execute }, - { "decode_prepare", netbox_decode_prepare }, { NULL, NULL} }; /* luaL_register_module polutes _G */ diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index bb844184fa01..9e41d6c0844b 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -26,6 +26,7 @@ local communicate = internal.communicate local encode_auth = internal.encode_auth local encode_method = internal.encode_method local decode_greeting = internal.decode_greeting +local decode_method = internal.decode_method local TIMEOUT_INFINITY = 500 * 365 * 86400 local VSPACE_ID = 281 @@ -83,21 +84,6 @@ error_unref(struct error *e); -- utility tables local is_final_state = {closed = 1, error = 1} -local function decode_nil(raw_data, raw_data_end) -- luacheck: no unused args - return nil, raw_data_end -end -local function decode_data(raw_data) - local response, raw_end = decode(raw_data) - return response[IPROTO_DATA_KEY], raw_end -end -local function decode_tuple(raw_data, raw_data_end, format) -- luacheck: no unused args - local response, raw_end = internal.decode_select(raw_data, nil, format) - return response[1], raw_end -end -local function decode_count(raw_data) - local response, raw_end = decode(raw_data) - return response[IPROTO_DATA_KEY][1], raw_end -end local function decode_push(raw_data) local response, raw_end = decode(raw_data) return response[IPROTO_DATA_KEY][1], raw_end @@ -111,27 +97,6 @@ local function version_at_least(peer_version_id, major, minor, patch) return peer_version_id >= version_id(major, minor, patch) end -local method_decoder = { - [M_PING] = decode_nil, - [M_CALL_16] = internal.decode_select, - [M_CALL_17] = decode_data, - [M_EVAL] = decode_data, - [M_INSERT] = decode_tuple, - [M_REPLACE] = decode_tuple, - [M_DELETE] = decode_tuple, - [M_UPDATE] = decode_tuple, - [M_UPSERT] = decode_nil, - [M_SELECT] = internal.decode_select, - [M_EXECUTE] = internal.decode_execute, - [M_PREPARE] = internal.decode_prepare, - [M_UNPREPARE] = decode_nil, - [M_GET] = decode_tuple, - [M_MIN] = decode_tuple, - [M_MAX] = decode_tuple, - [M_COUNT] = decode_count, - [M_INJECT] = decode_data, -} - local function decode_error(raw_data) local ptr = ffi.new('const char *[1]', raw_data) local err = ffi.C.error_unpack_unsafe(ptr) @@ -642,9 +607,9 @@ local function create_transport(host, port, user, password, callback, local real_end -- Decode xrow.body[DATA] to Lua objects if status == IPROTO_OK_KEY then - request.response, real_end = - method_decoder[request.method](body_rpos, body_end, - request.format) + request.response, real_end = decode_method(request.method, + body_rpos, body_end, + request.format) assert(real_end == body_end, "invalid body length") requests[id] = nil request.id = nil -- 2.25.1
This patch moves response_decoder table (which decodes only errors despite its name) from Lua to C. This is a step towards rewriting performance-critical parts of net.box in C. --- src/box/lua/net_box.c | 60 +++++++++++++++++++++++++++++++++++++++ src/box/lua/net_box.lua | 62 ++--------------------------------------- 2 files changed, 62 insertions(+), 60 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index c0c3725e5350..e88db6323afa 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -40,6 +40,8 @@ #include "box/xrow.h" #include "box/tuple.h" #include "box/execute.h" +#include "box/error.h" +#include "box/mp_error.h" #include "lua/msgpack.h" #include <base64.h> @@ -1082,6 +1084,63 @@ netbox_decode_method(struct lua_State *L) return 2; } +/* + * Decodes an error from raw data and pushes it to Lua stack. Takes a pointer + * to the data (char ptr) and an error code. + */ +static int +netbox_decode_error(struct lua_State *L) +{ + uint32_t ctypeid; + const char **data = luaL_checkcdata(L, 1, &ctypeid); + assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); + uint32_t errcode = lua_tointeger(L, 2); + struct error *error = NULL; + assert(mp_typeof(**data) == MP_MAP); + uint32_t map_size = mp_decode_map(data); + for (uint32_t i = 0; i < map_size; ++i) { + uint32_t key = mp_decode_uint(data); + if (key == IPROTO_ERROR) { + if (error != NULL) + error_unref(error); + error = error_unpack_unsafe(data); + if (error == NULL) + return luaT_error(L); + error_ref(error); + /* + * IPROTO_ERROR comprises error encoded with + * IPROTO_ERROR_24, so we may ignore content + * of that key. + */ + break; + } else if (key == IPROTO_ERROR_24) { + if (error != NULL) + error_unref(error); + const char *reason = ""; + uint32_t reason_len = 0; + if (mp_typeof(**data) == MP_STR) + reason = mp_decode_str(data, &reason_len); + box_error_raise(errcode, "%.*s", reason_len, reason); + error = box_error_last(); + error_ref(error); + continue; + } + mp_next(data); /* skip value */ + } + if (error == NULL) { + /* + * Error body is missing in the response. + * Set the error code without a 'reason' message + */ + box_error_raise(errcode, ""); + error = box_error_last(); + error_ref(error); + } + luaT_pusherror(L, error); + error_unref(error); + return 1; +} + int luaopen_net_box(struct lua_State *L) { @@ -1090,6 +1149,7 @@ luaopen_net_box(struct lua_State *L) { "encode_method", netbox_encode_method }, { "decode_greeting",netbox_decode_greeting }, { "decode_method", netbox_decode_method }, + { "decode_error", netbox_decode_error }, { "communicate", netbox_communicate }, { NULL, NULL} }; diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 9e41d6c0844b..d7394b088752 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -27,6 +27,7 @@ local encode_auth = internal.encode_auth local encode_method = internal.encode_method local decode_greeting = internal.decode_greeting local decode_method = internal.decode_method +local decode_error = internal.decode_error local TIMEOUT_INFINITY = 500 * 365 * 86400 local VSPACE_ID = 281 @@ -73,14 +74,6 @@ local M_COUNT = 16 -- Injects raw data into connection. Used by console and tests. local M_INJECT = 17 -ffi.cdef[[ -struct error * -error_unpack_unsafe(const char **data); - -void -error_unref(struct error *e); -]] - -- utility tables local is_final_state = {closed = 1, error = 1} @@ -97,29 +90,6 @@ local function version_at_least(peer_version_id, major, minor, patch) return peer_version_id >= version_id(major, minor, patch) end -local function decode_error(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 - -- From FFI it is returned as 'struct error *', which is - -- not considered equal to 'const struct error &', and is - -- is not accepted by functions like box.error(). Need to - -- cast explicitly. - err = ffi.cast('const struct error &', err) - err = ffi.gc(err, ffi.C.error_unref) - else - -- Error unpacker installs fail reason into diag. - box.error() - end - return err, ptr[0] -end - -local response_decoder = { - [IPROTO_ERROR_24] = decode, - [IPROTO_ERROR] = decode_error, -} - local function next_id(id) return band(id + 1, 0x7FFFFFFF) end -- @@ -538,42 +508,14 @@ local function create_transport(host, port, user, password, callback, return end local status = hdr[IPROTO_STATUS_KEY] - local body local body_len = body_end - body_rpos if status > IPROTO_CHUNK_KEY then -- Handle errors requests[id] = nil request.id = nil - local map_len, key - map_len, body_rpos = decode_map_header(body_rpos, body_len) - -- Reserve for 2 keys and 2 array indexes. Because no - -- any guarantees how Lua will decide to save the - -- sparse table. - body = table_new(2, 2) - for _ = 1, map_len do - key, body_rpos = decode(body_rpos) - local rdec = response_decoder[key] - if rdec then - body[key], body_rpos = rdec(body_rpos) - else - _, body_rpos = decode(body_rpos) - end - end - assert(body_end == body_rpos, "invalid xrow length") request.errno = band(status, IPROTO_ERRNO_MASK) - -- IPROTO_ERROR comprises error encoded with - -- IPROTO_ERROR_24, so we may ignore content of that - -- key. - if body[IPROTO_ERROR] ~= nil then - request.response = body[IPROTO_ERROR] - assert(type(request.response) == 'cdata') - else - request.response = box.error.new({ - code = request.errno, - reason = body[IPROTO_ERROR_24] - }) - end + request.response = decode_error(body_rpos, request.errno) request.cond:broadcast() return end -- 2.25.1
Basically, this patch converts the above function line-by-line from Lua to C. This is a step towards rewriting performance-critical parts of net.box in C. Note, Lua code expects send_and_recv functions return errno, errmsg on error and nil, ... on success while it's more convenient to return an error object from C code so we do the conversion in Lua wrappers. --- src/box/lua/net_box.c | 155 ++++++++++++++++++++++++++++------------ src/box/lua/net_box.lua | 46 +++--------- 2 files changed, 121 insertions(+), 80 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index e88db6323afa..12d82738a050 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -451,9 +451,9 @@ netbox_decode_greeting(lua_State *L) } /** - * communicate(fd, send_buf, recv_buf, limit_or_boundary, timeout) - * -> errno, error - * -> nil, limit/boundary_pos + * Reads data from the given socket until the limit or boundary is reached. + * Returns 0 and sets *limit_or_boundary_pos to limit/boundary_pos on success. + * On error returns -1 and sets diag. * * The need for this function arises from not wanting to * have more than one watcher for a single fd, and thus issue @@ -465,62 +465,45 @@ netbox_decode_greeting(lua_State *L) * interaction. */ static int -netbox_communicate(lua_State *L) +netbox_communicate(int fd, struct ibuf *send_buf, struct ibuf *recv_buf, + size_t limit, const void *boundary, size_t boundary_len, + double timeout, size_t *limit_or_boundary_pos) { - uint32_t fd = lua_tonumber(L, 1); const int NETBOX_READAHEAD = 16320; - struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); - struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 3); - - /* limit or boundary */ - size_t limit = SIZE_MAX; - const void *boundary = NULL; - size_t boundary_len; - - if (lua_type(L, 4) == LUA_TSTRING) - boundary = lua_tolstring(L, 4, &boundary_len); - else - limit = lua_tonumber(L, 4); - - /* timeout */ - ev_tstamp timeout = TIMEOUT_INFINITY; - if (lua_type(L, 5) == LUA_TNUMBER) - timeout = lua_tonumber(L, 5); if (timeout < 0) { - lua_pushinteger(L, ER_TIMEOUT); - lua_pushstring(L, "Timeout exceeded"); - return 2; + diag_set(ClientError, ER_TIMEOUT); + return -1; } int revents = COIO_READ; while (true) { /* reader serviced first */ check_limit: if (ibuf_used(recv_buf) >= limit) { - lua_pushnil(L); - lua_pushinteger(L, (lua_Integer)limit); - return 2; + *limit_or_boundary_pos = limit; + return 0; } const char *p; if (boundary != NULL && (p = memmem( recv_buf->rpos, ibuf_used(recv_buf), boundary, boundary_len)) != NULL) { - lua_pushnil(L); - lua_pushinteger(L, (lua_Integer)( - p - recv_buf->rpos)); - return 2; + *limit_or_boundary_pos = p - recv_buf->rpos; + return 0; } while (revents & COIO_READ) { void *p = ibuf_reserve(recv_buf, NETBOX_READAHEAD); - if (p == NULL) - luaL_error(L, "out of memory"); + if (p == NULL) { + diag_set(OutOfMemory, NETBOX_READAHEAD, + "ibuf", "recv_buf"); + return -1; + } ssize_t rc = recv( fd, recv_buf->wpos, ibuf_unused(recv_buf), 0); if (rc == 0) { - lua_pushinteger(L, ER_NO_CONNECTION); - lua_pushstring(L, "Peer closed"); - return 2; + box_error_raise(ER_NO_CONNECTION, + "Peer closed"); + return -1; } if (rc > 0) { recv_buf->wpos += rc; goto check_limit; @@ -545,19 +528,100 @@ check_limit: ERROR_INJECT_YIELD(ERRINJ_NETBOX_IO_DELAY); revents = coio_wait(fd, EV_READ | (ibuf_used(send_buf) != 0 ? EV_WRITE : 0), timeout); - luaL_testcancel(L); + if (fiber_is_cancelled()) { + diag_set(FiberIsCancelled); + return -1; + } timeout = deadline - ev_monotonic_now(loop()); timeout = MAX(0.0, timeout); if (revents == 0 && timeout == 0.0) { - lua_pushinteger(L, ER_TIMEOUT); - lua_pushstring(L, "Timeout exceeded"); - return 2; + diag_set(ClientError, ER_TIMEOUT); + return -1; } } handle_error: - lua_pushinteger(L, ER_NO_CONNECTION); - lua_pushstring(L, strerror(errno)); - return 2; + box_error_raise(ER_NO_CONNECTION, "%s", strerror(errno)); + return -1; +} + +/* + * Sends and receives data over an iproto connection. + * Takes socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. + * On success returns header (table), body_rpos (char *), body_end (char *). + * On error returns nil, error. + */ +static int +netbox_send_and_recv_iproto(lua_State *L) +{ + int fd = lua_tointeger(L, 1); + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 3); + double timeout = (!lua_isnoneornil(L, 4) ? + lua_tonumber(L, 4) : TIMEOUT_INFINITY); + while (true) { + size_t required; + size_t data_len = ibuf_used(recv_buf); + size_t fixheader_size = mp_sizeof_uint(UINT32_MAX); + if (data_len < fixheader_size) { + required = fixheader_size; + } else { + /* PWN! insufficient input validation */ + const char *bufpos = recv_buf->rpos; + const char *rpos = bufpos; + size_t len = mp_decode_uint(&rpos); + required = (rpos - bufpos) + len; + if (data_len >= required) { + const char *body_end = rpos + len; + const char *body_rpos = rpos; + luamp_decode(L, cfg, &body_rpos); + *(const char **)luaL_pushcdata( + L, CTID_CONST_CHAR_PTR) = body_rpos; + *(const char **)luaL_pushcdata( + L, CTID_CONST_CHAR_PTR) = body_end; + recv_buf->rpos = (char *)body_end; + return 3; + } + } + size_t unused; + double deadline = fiber_clock() + timeout; + if (netbox_communicate(fd, send_buf, recv_buf, + /*limit=*/required, + /*boundary=*/NULL, + /*boundary_len=*/0, + timeout, &unused) != 0) { + luaL_testcancel(L); + return luaT_push_nil_and_error(L); + } + timeout = deadline - fiber_clock(); + } +} + +/* + * Sends and receives data over a console connection. + * Takes socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. + * On success returns response (string). + * On error returns nil, error. + */ +static int +netbox_send_and_recv_console(lua_State *L) +{ + int fd = lua_tointeger(L, 1); + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 3); + double timeout = (!lua_isnoneornil(L, 4) ? + lua_tonumber(L, 4) : TIMEOUT_INFINITY); + const char delim[] = "\n...\n"; + size_t delim_len = sizeof(delim) - 1; + size_t delim_pos; + if (netbox_communicate(fd, send_buf, recv_buf, /*limit=*/SIZE_MAX, + delim, delim_len, timeout, &delim_pos) != 0) { + + luaL_testcancel(L); + return luaT_push_nil_and_error(L); + } + lua_pushlstring(L, recv_buf->rpos, delim_pos + delim_len); + recv_buf->rpos += delim_pos + delim_len; + return 1; } static void @@ -1150,7 +1214,8 @@ luaopen_net_box(struct lua_State *L) { "decode_greeting",netbox_decode_greeting }, { "decode_method", netbox_decode_method }, { "decode_error", netbox_decode_error }, - { "communicate", netbox_communicate }, + { "send_and_recv_iproto", netbox_send_and_recv_iproto }, + { "send_and_recv_console", netbox_send_and_recv_console }, { NULL, NULL} }; /* luaL_register_module polutes _G */ diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index d7394b088752..0ad6cac022f2 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -22,7 +22,6 @@ local check_index_arg = box.internal.check_index_arg local check_space_arg = box.internal.check_space_arg local check_primary_index = box.internal.check_primary_index -local communicate = internal.communicate local encode_auth = internal.encode_auth local encode_method = internal.encode_method local decode_greeting = internal.decode_greeting @@ -582,46 +581,23 @@ local function create_transport(host, port, user, password, callback, end -- IO (WORKER FIBER) -- - local function send_and_recv(limit_or_boundary, timeout) - return communicate(connection:fd(), send_buf, recv_buf, - limit_or_boundary, timeout) - end - local function send_and_recv_iproto(timeout) - local data_len = recv_buf.wpos - recv_buf.rpos - local required - if data_len < 5 then - required = 5 - else - -- PWN! insufficient input validation - local bufpos = recv_buf.rpos - local len, rpos = decode(bufpos) - required = (rpos - bufpos) + len - if data_len >= required then - local body_end = rpos + len - local hdr, body_rpos = decode(rpos) - recv_buf.rpos = body_end - return nil, hdr, body_rpos, body_end - end + local hdr, body_rpos, body_end = internal.send_and_recv_iproto( + connection:fd(), send_buf, recv_buf, timeout) + if not hdr then + local err = body_rpos + return err.code, err.message end - local deadline = fiber_clock() + (timeout or TIMEOUT_INFINITY) - local err, extra = send_and_recv(required, timeout) - if err then - return err, extra - end - return send_and_recv_iproto(max(0, deadline - fiber_clock())) + return nil, hdr, body_rpos, body_end end local function send_and_recv_console(timeout) - local delim = '\n...\n' - local err, delim_pos = send_and_recv(delim, timeout) - if err then - return err, delim_pos - else - local response = ffi.string(recv_buf.rpos, delim_pos + #delim) - recv_buf.rpos = recv_buf.rpos + delim_pos + #delim - return nil, response + local response, err = internal.send_and_recv_console( + connection:fd(), send_buf, recv_buf, timeout) + if not response then + return err.code, err.message end + return nil, response end -- PROTOCOL STATE MACHINE (WORKER FIBER) -- -- 2.25.1
netbox_encode_request is a confusing name - the function doesn't encode a request, it just writes the actual request size to the fix header. Let's rename the two functions to netbox_begin_request and netbox_end_request to emphasize that they are used to start and finish request encoding. Another reason to do that now is that in the following patches we will be moving the request table implementation from Lua to C so having a "request" in the function names would be confusing. --- src/box/lua/net_box.c | 52 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 12d82738a050..122d69e9219e 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -77,10 +77,10 @@ enum netbox_method { }; static inline size_t -netbox_prepare_request(struct mpstream *stream, uint64_t sync, - enum iproto_type type) +netbox_begin_encode(struct mpstream *stream, uint64_t sync, + enum iproto_type type) { - /* Remember initial size of ibuf (see netbox_encode_request()) */ + /* Remember initial size of ibuf (see netbox_end_encode()) */ struct ibuf *ibuf = (struct ibuf *) stream->ctx; size_t used = ibuf_used(ibuf); @@ -103,7 +103,7 @@ netbox_prepare_request(struct mpstream *stream, uint64_t sync, } static inline void -netbox_encode_request(struct mpstream *stream, size_t initial_size) +netbox_end_encode(struct mpstream *stream, size_t initial_size) { mpstream_flush(stream); @@ -135,8 +135,8 @@ netbox_encode_ping(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, IPROTO_PING); - netbox_encode_request(&stream, svp); + size_t svp = netbox_begin_encode(&stream, sync, IPROTO_PING); + netbox_end_encode(&stream, svp); } static int @@ -152,7 +152,7 @@ netbox_encode_auth(lua_State *L) struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, IPROTO_AUTH); + size_t svp = netbox_begin_encode(&stream, sync, IPROTO_AUTH); size_t user_len; const char *user = lua_tolstring(L, 3, &user_len); @@ -176,7 +176,7 @@ netbox_encode_auth(lua_State *L) mpstream_encode_strn(&stream, scramble, SCRAMBLE_SIZE); } - netbox_encode_request(&stream, svp); + netbox_end_encode(&stream, svp); return 0; } @@ -188,7 +188,7 @@ netbox_encode_call_impl(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync, struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, type); + size_t svp = netbox_begin_encode(&stream, sync, type); mpstream_encode_map(&stream, 2); @@ -202,7 +202,7 @@ netbox_encode_call_impl(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync, mpstream_encode_uint(&stream, IPROTO_TUPLE); luamp_encode_tuple(L, cfg, &stream, idx + 1); - netbox_encode_request(&stream, svp); + netbox_end_encode(&stream, svp); } static void @@ -224,7 +224,7 @@ netbox_encode_eval(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, IPROTO_EVAL); + size_t svp = netbox_begin_encode(&stream, sync, IPROTO_EVAL); mpstream_encode_map(&stream, 2); @@ -238,7 +238,7 @@ netbox_encode_eval(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) mpstream_encode_uint(&stream, IPROTO_TUPLE); luamp_encode_tuple(L, cfg, &stream, idx + 1); - netbox_encode_request(&stream, svp); + netbox_end_encode(&stream, svp); } static void @@ -248,7 +248,7 @@ netbox_encode_select(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, IPROTO_SELECT); + size_t svp = netbox_begin_encode(&stream, sync, IPROTO_SELECT); mpstream_encode_map(&stream, 6); @@ -282,7 +282,7 @@ netbox_encode_select(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) mpstream_encode_uint(&stream, IPROTO_KEY); luamp_convert_key(L, cfg, &stream, idx + 5); - netbox_encode_request(&stream, svp); + netbox_end_encode(&stream, svp); } static void @@ -293,7 +293,7 @@ netbox_encode_insert_or_replace(lua_State *L, int idx, struct ibuf *ibuf, struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, type); + size_t svp = netbox_begin_encode(&stream, sync, type); mpstream_encode_map(&stream, 2); @@ -306,7 +306,7 @@ netbox_encode_insert_or_replace(lua_State *L, int idx, struct ibuf *ibuf, mpstream_encode_uint(&stream, IPROTO_TUPLE); luamp_encode_tuple(L, cfg, &stream, idx + 1); - netbox_encode_request(&stream, svp); + netbox_end_encode(&stream, svp); } static void @@ -328,7 +328,7 @@ netbox_encode_delete(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, IPROTO_DELETE); + size_t svp = netbox_begin_encode(&stream, sync, IPROTO_DELETE); mpstream_encode_map(&stream, 3); @@ -346,7 +346,7 @@ netbox_encode_delete(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) mpstream_encode_uint(&stream, IPROTO_KEY); luamp_convert_key(L, cfg, &stream, idx + 2); - netbox_encode_request(&stream, svp); + netbox_end_encode(&stream, svp); } static void @@ -356,7 +356,7 @@ netbox_encode_update(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, IPROTO_UPDATE); + size_t svp = netbox_begin_encode(&stream, sync, IPROTO_UPDATE); mpstream_encode_map(&stream, 5); @@ -382,7 +382,7 @@ netbox_encode_update(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) mpstream_encode_uint(&stream, IPROTO_TUPLE); luamp_encode_tuple(L, cfg, &stream, idx + 3); - netbox_encode_request(&stream, svp); + netbox_end_encode(&stream, svp); } static void @@ -392,7 +392,7 @@ netbox_encode_upsert(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, IPROTO_UPSERT); + size_t svp = netbox_begin_encode(&stream, sync, IPROTO_UPSERT); mpstream_encode_map(&stream, 4); @@ -413,7 +413,7 @@ netbox_encode_upsert(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) mpstream_encode_uint(&stream, IPROTO_OPS); luamp_encode_tuple(L, cfg, &stream, idx + 2); - netbox_encode_request(&stream, svp); + netbox_end_encode(&stream, svp); } static int @@ -631,7 +631,7 @@ netbox_encode_execute(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, IPROTO_EXECUTE); + size_t svp = netbox_begin_encode(&stream, sync, IPROTO_EXECUTE); mpstream_encode_map(&stream, 3); @@ -652,7 +652,7 @@ netbox_encode_execute(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) mpstream_encode_uint(&stream, IPROTO_OPTIONS); luamp_encode_tuple(L, cfg, &stream, idx + 2); - netbox_encode_request(&stream, svp); + netbox_end_encode(&stream, svp); } static void @@ -662,7 +662,7 @@ netbox_encode_prepare(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - size_t svp = netbox_prepare_request(&stream, sync, IPROTO_PREPARE); + size_t svp = netbox_begin_encode(&stream, sync, IPROTO_PREPARE); mpstream_encode_map(&stream, 1); @@ -677,7 +677,7 @@ netbox_encode_prepare(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) mpstream_encode_strn(&stream, query, len); }; - netbox_encode_request(&stream, svp); + netbox_end_encode(&stream, svp); } static void -- 2.25.1
This patch turns 'request' object and 'requests' table into userdata and rewrites all their methods in C. Needed to rewrite performance-critical parts of net.box in C. --- src/box/lua/net_box.c | 685 +++++++++++++++++++++++++++++++++++++--- src/box/lua/net_box.lua | 259 +-------------- 2 files changed, 657 insertions(+), 287 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 122d69e9219e..044f7d337ca7 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -46,7 +46,9 @@ #include "lua/msgpack.h" #include <base64.h> +#include "assoc.h" #include "coio.h" +#include "fiber_cond.h" #include "box/errcode.h" #include "lua/fiber.h" #include "mpstream/mpstream.h" @@ -76,6 +78,253 @@ enum netbox_method { netbox_method_MAX }; +struct netbox_registry { + /* sync -> netbox_request */ + struct mh_i64ptr_t *requests; +}; + +struct netbox_request { + enum netbox_method method; + /* + * Unique identifier needed for matching the request with its response. + * Used as a key in the registry. + */ + uint64_t sync; + /* + * The registry this request belongs to or NULL if the request has been + * completed. + */ + struct netbox_registry *registry; + /* Format used for decoding the response (ref incremented). */ + struct tuple_format *format; + /* Signaled when the response is received. */ + struct fiber_cond cond; + /* + * A user-provided buffer to which the response body should be copied. + * If NULL, the response will be decoded to Lua stack. + */ + struct ibuf *buffer; + /* + * Lua reference to the buffer. Used to prevent garbage collection in + * case the user discards the request. + */ + int buffer_ref; + /* + * Whether to skip MessagePack map header and IPROTO_DATA key when + * copying the response body to a user-provided buffer. Ignored if + * buffer is not set. + */ + bool skip_header; + /* Lua references to on_push trigger and its context. */ + int on_push_ref; + int on_push_ctx_ref; + /* + * Reference to the request result or LUA_NOREF if the response hasn't + * been received yet. If the response was decoded to a user-provided + * buffer, the result stores the length of the decoded data. + */ + int result_ref; + /* + * Error if the request failed (ref incremented). NULL on success or if + * the response hasn't been received yet. + */ + struct error *error; + +}; + +static const char netbox_registry_typename[] = "net.box.registry"; +static const char netbox_request_typename[] = "net.box.request"; + +static void +netbox_request_create(struct netbox_request *request) +{ + request->method = netbox_method_MAX; + request->sync = -1; + request->registry = NULL; + request->format = NULL; + fiber_cond_create(&request->cond); + request->buffer = NULL; + request->buffer_ref = LUA_REFNIL; + request->skip_header = false; + request->on_push_ref = LUA_REFNIL; + request->on_push_ctx_ref = LUA_REFNIL; + request->result_ref = LUA_NOREF; + request->error = NULL; +} + +static void +netbox_request_destroy(struct netbox_request *request) +{ + assert(request->registry == NULL); + if (request->format != NULL) + tuple_format_unref(request->format); + fiber_cond_destroy(&request->cond); + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->buffer_ref); + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->on_push_ref); + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->on_push_ctx_ref); + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->result_ref); + if (request->error != NULL) + error_unref(request->error); +} + +/* + * Adds a request to a registry. There must not be a request with the same id + * (sync) in the registry. Returns -1 if out of memory. + */ +static int +netbox_request_register(struct netbox_request *request, + struct netbox_registry *registry) +{ + assert(request->registry == NULL); + struct mh_i64ptr_t *h = registry->requests; + struct mh_i64ptr_node_t node = { request->sync, request }; + struct mh_i64ptr_node_t *old_node = NULL; + if (mh_i64ptr_put(h, &node, &old_node, NULL) == mh_end(h)) { + diag_set(OutOfMemory, 0, "mhash", "netbox_registry"); + return -1; + } + assert(old_node == NULL); + request->registry = registry; + return 0; +} + +/* + * Unregisters a previously registered request. Does nothing if the request has + * already been unregistered or has never been registered. + */ +static void +netbox_request_unregister(struct netbox_request *request) +{ + struct netbox_registry *registry = request->registry; + if (registry == NULL) + return; + request->registry = NULL; + struct mh_i64ptr_t *h = registry->requests; + mh_int_t k = mh_i64ptr_find(h, request->sync, NULL); + assert(k != mh_end(h)); + assert(mh_i64ptr_node(h, k)->val == request); + mh_i64ptr_del(h, k, NULL); +} + +static bool +netbox_request_is_ready(struct netbox_request *request) +{ + return request->registry == NULL; +} + +static void +netbox_request_signal(struct netbox_request *request) +{ + fiber_cond_broadcast(&request->cond); +} + +static void +netbox_request_complete(struct netbox_request *request) +{ + netbox_request_unregister(request); + netbox_request_signal(request); +} + +/* + * Waits on netbox_request::cond. Subtracts the wait time from the timeout. + * Returns false on timeout or if the fiber was cancelled. + */ +static bool +netbox_request_wait(struct netbox_request *request, double *timeout) +{ + double ts = ev_monotonic_now(loop()); + int rc = fiber_cond_wait_timeout(&request->cond, *timeout); + *timeout -= ev_monotonic_now(loop()) - ts; + return rc == 0; +} + +static void +netbox_request_set_result(struct netbox_request *request, int result_ref) +{ + assert(request->result_ref == LUA_NOREF); + request->result_ref = result_ref; +} + +static void +netbox_request_set_error(struct netbox_request *request, struct error *error) +{ + assert(request->error == NULL); + request->error = error; + error_ref(error); +} + +/* + * Pushes the result or error to Lua stack. See the comment to request.result() + * for more information about the values pushed. + */ +static int +netbox_request_push_result(struct netbox_request *request, struct lua_State *L) +{ + if (!netbox_request_is_ready(request)) { + diag_set(ClientError, ER_PROC_LUA, "Response is not ready"); + return luaT_push_nil_and_error(L); + } + if (request->error != NULL) { + assert(request->result_ref == LUA_NOREF); + diag_set_error(diag_get(), request->error); + return luaT_push_nil_and_error(L); + } else { + assert(request->result_ref != LUA_NOREF); + lua_rawgeti(L, LUA_REGISTRYINDEX, request->result_ref); + } + return 1; +} + +static int +netbox_registry_create(struct netbox_registry *registry) +{ + registry->requests = mh_i64ptr_new(); + if (registry->requests == NULL) { + diag_set(OutOfMemory, 0, "mhash", "netbox_registry"); + return -1; + } + return 0; +} + +static void +netbox_registry_destroy(struct netbox_registry *registry) +{ + struct mh_i64ptr_t *h = registry->requests; + assert(mh_size(h) == 0); + mh_i64ptr_delete(h); +} + +/* + * Looks up a request by id (sync). Returns NULL if not found. + */ +static struct netbox_request * +netbox_registry_lookup(struct netbox_registry *registry, uint64_t sync) +{ + struct mh_i64ptr_t *h = registry->requests; + mh_int_t k = mh_i64ptr_find(h, sync, NULL); + if (k == mh_end(h)) + return NULL; + return mh_i64ptr_node(h, k)->val; +} + +/* + * Completes all requests in the registry with the given error and cleans up + * the hash. Called when the associated connection is closed. + */ +static void +netbox_registry_reset(struct netbox_registry *registry, struct error *error) +{ + struct mh_i64ptr_t *h = registry->requests; + mh_int_t k; + mh_foreach(h, k) { + struct netbox_request *request = mh_i64ptr_node(h, k)->val; + request->registry = NULL; + netbox_request_set_error(request, error); + netbox_request_signal(request); + } + mh_i64ptr_clear(h); +} + static inline size_t netbox_begin_encode(struct mpstream *stream, uint64_t sync, enum iproto_type type) @@ -1096,17 +1345,13 @@ netbox_decode_prepare(struct lua_State *L, const char **data, } /* - * Decodes a response body for the specified method. Pushes the result and the - * end of the decoded data to Lua stack. - * - * Takes the following arguments: - * - method: a value from the netbox_method enumeration - * - data: pointer to the data to decode (char ptr) - * - data_end: pointer to the end of the data (char ptr) - * - format: tuple format to use for decoding the body or nil + * Decodes a response body for the specified method and pushes the result to + * Lua stack. */ -static int -netbox_decode_method(struct lua_State *L) +static void +netbox_decode_method(struct lua_State *L, enum netbox_method method, + const char **data, const char *data_end, + struct tuple_format *format) { typedef void (*method_decoder_f)(struct lua_State *L, const char **data, const char *data_end, @@ -1131,34 +1376,16 @@ netbox_decode_method(struct lua_State *L) [NETBOX_COUNT] = netbox_decode_value, [NETBOX_INJECT] = netbox_decode_table, }; - enum netbox_method method = lua_tointeger(L, 1); - assert(method < netbox_method_MAX); - uint32_t ctypeid; - const char *data = *(const char **)luaL_checkcdata(L, 2, &ctypeid); - assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); - const char *data_end = *(const char **)luaL_checkcdata(L, 3, &ctypeid); - assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); - struct tuple_format *format; - if (!lua_isnil(L, 4)) - format = lbox_check_tuple_format(L, 4); - else - format = tuple_format_runtime; - method_decoder[method](L, &data, data_end, format); - *(const char **)luaL_pushcdata(L, CTID_CONST_CHAR_PTR) = data; - return 2; + method_decoder[method](L, data, data_end, format); } /* - * Decodes an error from raw data and pushes it to Lua stack. Takes a pointer - * to the data (char ptr) and an error code. + * Decodes an error from raw data. On success returns the decoded error object + * with ref counter incremented. On failure returns NULL. */ -static int -netbox_decode_error(struct lua_State *L) +static struct error * +netbox_decode_error(const char **data, uint32_t errcode) { - uint32_t ctypeid; - const char **data = luaL_checkcdata(L, 1, &ctypeid); - assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); - uint32_t errcode = lua_tointeger(L, 2); struct error *error = NULL; assert(mp_typeof(**data) == MP_MAP); uint32_t map_size = mp_decode_map(data); @@ -1169,7 +1396,7 @@ netbox_decode_error(struct lua_State *L) error_unref(error); error = error_unpack_unsafe(data); if (error == NULL) - return luaT_error(L); + return NULL; error_ref(error); /* * IPROTO_ERROR comprises error encoded with @@ -1200,22 +1427,404 @@ netbox_decode_error(struct lua_State *L) error = box_error_last(); error_ref(error); } - luaT_pusherror(L, error); - error_unref(error); + return error; +} + +static inline struct netbox_registry * +luaT_check_netbox_registry(struct lua_State *L, int idx) +{ + return luaL_checkudata(L, idx, netbox_registry_typename); +} + +static int +luaT_netbox_registry_gc(struct lua_State *L) +{ + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); + netbox_registry_destroy(registry); + return 0; +} + +static int +luaT_netbox_registry_reset(struct lua_State *L) +{ + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); + struct error *error = luaL_checkerror(L, 2); + netbox_registry_reset(registry, error); + return 0; +} + +static inline struct netbox_request * +luaT_check_netbox_request(struct lua_State *L, int idx) +{ + return luaL_checkudata(L, idx, netbox_request_typename); +} + +static int +luaT_netbox_request_gc(struct lua_State *L) +{ + struct netbox_request *request = luaT_check_netbox_request(L, 1); + netbox_request_unregister(request); + netbox_request_destroy(request); + return 0; +} + +/* + * Returns true if the response was received for the given request. + */ +static int +luaT_netbox_request_is_ready(struct lua_State *L) +{ + struct netbox_request *request = luaT_check_netbox_request(L, 1); + lua_pushboolean(L, netbox_request_is_ready(request)); return 1; } +/* + * Obtains the result of the given request. + * + * Returns: + * - nil, error if the response failed or not ready + * - response body (table) if the response is ready and buffer is nil + * - body length in bytes if the response was written to the buffer + */ +static int +luaT_netbox_request_result(struct lua_State *L) +{ + struct netbox_request *request = luaT_check_netbox_request(L, 1); + return netbox_request_push_result(request, L); +} + +/* + * Waits until the response is received for the given request and obtains the + * result. Takes an optional timeout argument. + * + * See the comment to request.result() for the return value format. + */ +static int +luaT_netbox_request_wait_result(struct lua_State *L) +{ + struct netbox_request *request = luaT_check_netbox_request(L, 1); + double timeout = TIMEOUT_INFINITY; + if (!lua_isnoneornil(L, 2)) { + if (lua_type(L, 2) != LUA_TNUMBER || + (timeout = lua_tonumber(L, 2)) < 0) + luaL_error(L, "Usage: future:wait_result(timeout)"); + } + while (!netbox_request_is_ready(request)) { + if (!netbox_request_wait(request, &timeout)) { + luaL_testcancel(L); + diag_set(ClientError, ER_TIMEOUT); + return luaT_push_nil_and_error(L); + } + } + return netbox_request_push_result(request, L); +} + +/* + * Makes the connection forget about the given request. When the response is + * received, it will be ignored. It reduces the size of the request registry + * speeding up other requests. + */ +static int +luaT_netbox_request_discard(struct lua_State *L) +{ + struct netbox_request *request = luaT_check_netbox_request(L, 1); + if (!netbox_request_is_ready(request)) { + diag_set(ClientError, ER_PROC_LUA, "Response is discarded"); + netbox_request_set_error(request, diag_last_error(diag_get())); + netbox_request_complete(request); + } + return 0; +} + +/* + * Gets the next message or the final result. Takes the index of the last + * returned message as a second argument (the first argument is ignored). + * The request and timeout are passed as up-values (see request.pairs()). + * + * On success returns the index of the current message (used by the iterator + * implementation to continue iteration) and an object, which is either the + * message pushed with box.session.push() or the final response. If there's no + * more messages left for the request, returns nil, nil. + * + * On error returns box.NULL, error. We return box.NULL instead of nil to + * distinguish end of iteration from error when this function is called in + * `for k, v in future:pairs()`, because nil is treated by Lua as end of + * iteration marker. + */ +static int +luaT_netbox_request_iterator_next(struct lua_State *L) +{ + struct netbox_request *request = luaT_check_netbox_request( + L, lua_upvalueindex(1)); + double timeout = lua_tonumber(L, lua_upvalueindex(2)); + if (luaL_isnull(L, 2)) { + /* The previous call returned an error. */ + goto stop; + } + int i = lua_tointeger(L, 2) + 1; + /* + * In the async mode (and this is the async mode, because 'future' + * objects are not available to the user in the sync mode), on_push_ctx + * refers to a table that contains received messages. We iterate over + * the content of the table. + */ + lua_rawgeti(L, LUA_REGISTRYINDEX, request->on_push_ctx_ref); + int messages_idx = lua_gettop(L); + assert(lua_istable(L, messages_idx)); + int message_count = lua_objlen(L, messages_idx); +retry: + if (i <= message_count) { + lua_pushinteger(L, i); + lua_rawgeti(L, messages_idx, i); + return 2; + } + if (netbox_request_is_ready(request)) { + /* + * After all the messages are iterated, `i` is equal to + * #messages + 1. After we return the response, `i` becomes + * #messages + 2. It is the trigger to finish the iteration. + */ + if (i > message_count + 1) + goto stop; + int n = netbox_request_push_result(request, L); + if (n == 2) + goto error; + /* Success. Return i, response. */ + assert(n == 1); + lua_pushinteger(L, i); + lua_insert(L, -2); + return 2; + } + int old_message_count = message_count; + do { + if (netbox_request_wait(request, &timeout) != 0) { + luaL_testcancel(L); + diag_set(ClientError, ER_TIMEOUT); + luaT_push_nil_and_error(L); + goto error; + } + message_count = lua_objlen(L, messages_idx); + } while (!netbox_request_is_ready(request) && + message_count == old_message_count); + goto retry; +stop: + lua_pushnil(L); + lua_pushnil(L); + return 2; +error: + /* + * When we get here, the top two elements on the stack are nil, error. + * We need to replace nil with box.NULL. + */ + luaL_pushnull(L); + lua_replace(L, -3); + return 2; +} + +static int +luaT_netbox_request_pairs(struct lua_State *L) +{ + if (!lua_isnoneornil(L, 2)) { + if (lua_type(L, 2) != LUA_TNUMBER || lua_tonumber(L, 2) < 0) + luaL_error(L, "Usage: future:pairs(timeout)"); + } else { + if (lua_isnil(L, 2)) + lua_pop(L, 1); + lua_pushnumber(L, TIMEOUT_INFINITY); + } + lua_pushcclosure(L, luaT_netbox_request_iterator_next, 2); + lua_pushnil(L); + lua_pushinteger(L, 0); + return 3; +} + +/* + * Creates a request registry object (userdata) and pushes it to Lua stack. + */ +static int +netbox_new_registry(struct lua_State *L) +{ + struct netbox_registry *registry = lua_newuserdata( + L, sizeof(*registry)); + if (netbox_registry_create(registry) != 0) + luaT_error(L); + luaL_getmetatable(L, netbox_registry_typename); + lua_setmetatable(L, -2); + return 1; +} + +/* + * Creates a request object (userdata) and pushes it to Lua stack. + * + * Takes the following arguments: + * - requests: registry to register the new request with + * - id: id (sync) to assign to the new request + * - buffer: buffer (ibuf) to write the result to or nil + * - skip_header: whether to skip header when writing the result to the buffer + * - method: a value from the netbox_method enumeration + * - on_push: on_push trigger function + * - on_push_ctx: on_push trigger function argument + * - format: tuple format to use for decoding the body or nil + */ +static int +netbox_new_request(struct lua_State *L) +{ + struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); + netbox_request_create(request); + luaL_getmetatable(L, netbox_request_typename); + lua_setmetatable(L, -2); + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); + request->sync = luaL_touint64(L, 2); + request->buffer = (struct ibuf *) lua_topointer(L, 3); + lua_pushvalue(L, 3); + request->buffer_ref = luaL_ref(L, LUA_REGISTRYINDEX); + request->skip_header = lua_toboolean(L, 4); + request->method = lua_tointeger(L, 5); + assert(request->method < netbox_method_MAX); + lua_pushvalue(L, 6); + request->on_push_ref = luaL_ref(L, LUA_REGISTRYINDEX); + lua_pushvalue(L, 7); + request->on_push_ctx_ref = luaL_ref(L, LUA_REGISTRYINDEX); + if (!lua_isnil(L, 8)) + request->format = lbox_check_tuple_format(L, 8); + else + request->format = tuple_format_runtime; + tuple_format_ref(request->format); + if (netbox_request_register(request, registry) != 0) + luaT_error(L); + return 1; +} + +/* + * Given a request registry, request id (sync), status, and a pointer to a + * response body, decodes the response and either completes the request or + * invokes the on-push trigger, depending on the status. + */ +static int +netbox_dispatch_response_iproto(struct lua_State *L) +{ + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); + uint64_t sync = luaL_touint64(L, 2); + enum iproto_type status = lua_tointeger(L, 3); + uint32_t ctypeid; + const char *data = *(const char **)luaL_checkcdata(L, 4, &ctypeid); + assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); + const char *data_end = *(const char **)luaL_checkcdata(L, 5, &ctypeid); + assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); + struct netbox_request *request = netbox_registry_lookup(registry, sync); + if (request == NULL) { + /* Nobody is waiting for the response. */ + return 0; + } + if (status > IPROTO_CHUNK) { + /* Handle errors. */ + struct error *error = netbox_decode_error( + &data, status & (IPROTO_TYPE_ERROR - 1)); + if (error == NULL) + return luaT_error(L); + netbox_request_set_error(request, error); + error_unref(error); + netbox_request_complete(request); + return 0; + } + if (request->buffer != NULL) { + /* Copy xrow.body to user-provided buffer. */ + if (request->skip_header) + netbox_skip_to_data(&data); + size_t data_len = data_end - data; + void *wpos = ibuf_alloc(request->buffer, data_len); + if (wpos == NULL) + luaL_error(L, "out of memory"); + memcpy(wpos, data, data_len); + lua_pushinteger(L, data_len); + } else { + /* Decode xrow.body[DATA] to Lua objects. */ + if (status == IPROTO_OK) { + netbox_decode_method(L, request->method, &data, + data_end, request->format); + } else { + netbox_decode_value(L, &data, data_end, + request->format); + } + assert(data == data_end); + } + if (status == IPROTO_OK) { + /* + * We received the final response and pushed it to Lua stack. + * Store a reference to it in the request, remove the request + * from the registry, and wake up waiters. + */ + netbox_request_set_result(request, + luaL_ref(L, LUA_REGISTRYINDEX)); + netbox_request_complete(request); + } else { + /* We received a push. Invoke on_push trigger. */ + lua_rawgeti(L, LUA_REGISTRYINDEX, request->on_push_ref); + lua_rawgeti(L, LUA_REGISTRYINDEX, request->on_push_ctx_ref); + /* Push the received message as the second argument. */ + lua_pushvalue(L, -3); + lua_call(L, 2, 0); + netbox_request_signal(request); + } + return 0; +} + +/* + * Given a request registry, request id (sync), and a response string, assigns + * the response to the request and completes it. + */ +static int +netbox_dispatch_response_console(struct lua_State *L) +{ + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); + uint64_t sync = luaL_touint64(L, 2); + struct netbox_request *request = netbox_registry_lookup(registry, sync); + if (request == NULL) { + /* Nobody is waiting for the response. */ + return 0; + } + /* + * The response is the last argument of this function so it's already + * on the top of the Lua stack. + */ + netbox_request_set_result(request, luaL_ref(L, LUA_REGISTRYINDEX)); + netbox_request_complete(request); + return 0; +} + int luaopen_net_box(struct lua_State *L) { + static const struct luaL_Reg netbox_registry_meta[] = { + { "__gc", luaT_netbox_registry_gc }, + { "reset", luaT_netbox_registry_reset }, + { NULL, NULL } + }; + luaL_register_type(L, netbox_registry_typename, netbox_registry_meta); + + static const struct luaL_Reg netbox_request_meta[] = { + { "__gc", luaT_netbox_request_gc }, + { "is_ready", luaT_netbox_request_is_ready }, + { "result", luaT_netbox_request_result }, + { "wait_result", luaT_netbox_request_wait_result }, + { "discard", luaT_netbox_request_discard }, + { "pairs", luaT_netbox_request_pairs }, + { NULL, NULL } + }; + luaL_register_type(L, netbox_request_typename, netbox_request_meta); + static const luaL_Reg net_box_lib[] = { { "encode_auth", netbox_encode_auth }, { "encode_method", netbox_encode_method }, { "decode_greeting",netbox_decode_greeting }, - { "decode_method", netbox_decode_method }, - { "decode_error", netbox_decode_error }, { "send_and_recv_iproto", netbox_send_and_recv_iproto }, { "send_and_recv_console", netbox_send_and_recv_console }, + { "new_registry", netbox_new_registry }, + { "new_request", netbox_new_request }, + { "dispatch_response_iproto", netbox_dispatch_response_iproto }, + { "dispatch_response_console", + netbox_dispatch_response_console }, { NULL, NULL} }; /* luaL_register_module polutes _G */ diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 0ad6cac022f2..4bc66940ea2a 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -14,9 +14,7 @@ local max = math.max local fiber_clock = fiber.clock local fiber_self = fiber.self local decode = msgpack.decode_unchecked -local decode_map_header = msgpack.decode_map_header -local table_new = require('table.new') local check_iterator_type = box.internal.check_iterator_type local check_index_arg = box.internal.check_index_arg local check_space_arg = box.internal.check_space_arg @@ -25,8 +23,6 @@ local check_primary_index = box.internal.check_primary_index local encode_auth = internal.encode_auth local encode_method = internal.encode_method local decode_greeting = internal.decode_greeting -local decode_method = internal.decode_method -local decode_error = internal.decode_error local TIMEOUT_INFINITY = 500 * 365 * 86400 local VSPACE_ID = 281 @@ -42,13 +38,10 @@ local IPROTO_DATA_KEY = 0x30 local IPROTO_ERROR_24 = 0x31 local IPROTO_ERROR = 0x52 local IPROTO_GREETING_SIZE = 128 -local IPROTO_CHUNK_KEY = 128 -local IPROTO_OK_KEY = 0 -- select errors from box.error local E_UNKNOWN = box.error.UNKNOWN local E_NO_CONNECTION = box.error.NO_CONNECTION -local E_TIMEOUT = box.error.TIMEOUT local E_PROC_LUA = box.error.PROC_LUA local E_NO_SUCH_SPACE = box.error.NO_SUCH_SPACE @@ -191,172 +184,20 @@ local function create_transport(host, port, user, password, callback, local last_error local state_cond = fiber.cond() -- signaled when the state changes - -- Async requests currently 'in flight', keyed by a request - -- id. Value refs are weak hence if a client dies - -- unexpectedly, GC cleans the mess. + -- The registry stores requests that are currently 'in flight' + -- for this connection. -- Async request can not be timed out completely. Instead a -- user must decide when he does not want to wait for -- response anymore. -- Sync requests are implemented as async call + immediate -- wait for a result. - local requests = setmetatable({}, { __mode = 'v' }) + local requests = internal.new_registry() local next_request_id = 1 local worker_fiber local send_buf = buffer.ibuf(buffer.READAHEAD) local recv_buf = buffer.ibuf(buffer.READAHEAD) - -- - -- Async request metamethods. - -- - local request_index = {} - -- - -- When an async request is finalized (with ok or error - no - -- matter), its 'id' field is nullified by a response - -- dispatcher. - -- - function request_index:is_ready() - return self.id == nil - end - -- - -- When a request is finished, a result can be got from a - -- future object anytime. - -- @retval result, nil Success, the response is returned. - -- @retval nil, error Error occured. - -- - function request_index:result() - if self.errno then - if type(self.response) ~= 'cdata' then - -- Error could be set by the connection state - -- machine, and it is usually a string explaining - -- a reason. - self.response = box.error.new({code = self.errno, - reason = self.response}) - end - return nil, self.response - elseif not self.id then - return self.response - else - return nil, box.error.new(box.error.PROC_LUA, - 'Response is not ready') - end - end - -- - -- Get the next message or the final result. - -- @param iterator Iterator object. - -- @param i Index to get a next message from. - -- - -- @retval nil, nil The request is finished. - -- @retval i + 1, object A message/response and its index. - -- @retval box.NULL, error An error occured. When this - -- function is called in 'for k, v in future:pairs()', - -- `k` becomes box.NULL, and `v` becomes error object. - -- On error the key becomes exactly box.NULL instead - -- of nil, because nil is treated by Lua as iteration - -- end marker. Nil does not participate in iteration, - -- and does not allow to continue it. - -- - local function request_iterator_next(iterator, i) - if i == box.NULL then - return nil, nil - else - i = i + 1 - end - local request = iterator.request - local messages = request.on_push_ctx - ::retry:: - if i <= #messages then - return i, messages[i] - end - if request:is_ready() then - -- After all the messages are iterated, `i` is equal - -- to #messages + 1. After response reading `i` - -- becomes #messages + 2. It is the trigger to finish - -- the iteration. - if i > #messages + 1 then - return nil, nil - end - local response, err = request:result() - if err then - return box.NULL, err - end - return i, response - end - local old_message_count = #messages - local timeout = iterator.timeout - repeat - local ts = fiber_clock() - request.cond:wait(timeout) - timeout = timeout - (fiber_clock() - ts) - if request:is_ready() or old_message_count ~= #messages then - goto retry - end - until timeout <= 0 - return box.NULL, box.error.new(E_TIMEOUT) - end - -- - -- Iterate over all messages, received by a request. @Sa - -- request_iterator_next for details what to expect in `for` - -- key/value pairs. - -- @param timeout One iteration timeout. - -- @retval next() callback, iterator, zero key. - -- - function request_index:pairs(timeout) - if timeout then - if type(timeout) ~= 'number' or timeout < 0 then - error('Usage: future:pairs(timeout)') - end - else - timeout = TIMEOUT_INFINITY - end - local iterator = {request = self, timeout = timeout} - return request_iterator_next, iterator, 0 - end - -- - -- Wait for a response or error max timeout seconds. - -- @param timeout Max seconds to wait. - -- @retval result, nil Success, the response is returned. - -- @retval nil, error Error occured. - -- - function request_index:wait_result(timeout) - if timeout then - if type(timeout) ~= 'number' or timeout < 0 then - error('Usage: future:wait_result(timeout)') - end - else - timeout = TIMEOUT_INFINITY - end - if not self:is_ready() then - -- When a response is ready before timeout, the - -- waiting client is waked up prematurely. - while timeout > 0 and not self:is_ready() do - local ts = fiber.clock() - self.cond:wait(timeout) - timeout = timeout - (fiber.clock() - ts) - end - if not self:is_ready() then - return nil, box.error.new(E_TIMEOUT) - end - end - return self:result() - end - -- - -- Make a connection forget about the response. When it will - -- be received, it will be ignored. It reduces size of - -- requests table speeding up other requests. - -- - function request_index:discard() - if self.id then - requests[self.id] = nil - self.id = nil - self.errno = box.error.PROC_LUA - self.response = 'Response is discarded' - self.cond:broadcast() - end - end - - local request_mt = { __index = request_index } - -- STATE SWITCHING -- local function set_state(new_state, new_errno, new_error) state = new_state @@ -366,13 +207,8 @@ local function create_transport(host, port, user, password, callback, state_cond:broadcast() if state == 'error' or state == 'error_reconnect' or state == 'closed' then - for _, request in pairs(requests) do - request.id = nil - request.errno = new_errno - request.response = new_error - request.cond:broadcast() - end - requests = {} + requests:reset(box.error.new({code = new_errno, + reason = new_error})) end end @@ -468,20 +304,8 @@ local function create_transport(host, port, user, password, callback, local id = next_request_id encode_method(method, send_buf, id, ...) next_request_id = next_id(id) - -- Request in most cases has maximum 10 members: - -- method, buffer, skip_header, id, cond, errno, response, - -- on_push, on_push_ctx and format. - local request = setmetatable(table_new(0, 10), request_mt) - request.method = method - request.buffer = buffer - request.skip_header = skip_header - request.id = id - request.cond = fiber.cond() - requests[id] = request - request.on_push = on_push - request.on_push_ctx = on_push_ctx - request.format = format - return request + return internal.new_request(requests, id, buffer, skip_header, method, + on_push, on_push_ctx, format) end -- @@ -502,76 +326,13 @@ local function create_transport(host, port, user, password, callback, local function dispatch_response_iproto(hdr, body_rpos, body_end) local id = hdr[IPROTO_SYNC_KEY] - local request = requests[id] - if request == nil then -- nobody is waiting for the response - return - end local status = hdr[IPROTO_STATUS_KEY] - local body_len = body_end - body_rpos - - if status > IPROTO_CHUNK_KEY then - -- Handle errors - requests[id] = nil - request.id = nil - request.errno = band(status, IPROTO_ERRNO_MASK) - request.response = decode_error(body_rpos, request.errno) - request.cond:broadcast() - return - end - - local buffer = request.buffer - if buffer ~= nil then - -- Copy xrow.body to user-provided buffer - if request.skip_header then - -- Skip {[IPROTO_DATA_KEY] = ...} wrapper. - local map_len, key - map_len, body_rpos = decode_map_header(body_rpos, body_len) - assert(map_len == 1) - key, body_rpos = decode(body_rpos) - assert(key == IPROTO_DATA_KEY) - body_len = body_end - body_rpos - end - local wpos = buffer:alloc(body_len) - ffi.copy(wpos, body_rpos, body_len) - body_len = tonumber(body_len) - if status == IPROTO_OK_KEY then - request.response = body_len - requests[id] = nil - request.id = nil - else - request.on_push(request.on_push_ctx, body_len) - end - request.cond:broadcast() - return - end - - local real_end - -- Decode xrow.body[DATA] to Lua objects - if status == IPROTO_OK_KEY then - request.response, real_end = decode_method(request.method, - body_rpos, body_end, - request.format) - assert(real_end == body_end, "invalid body length") - requests[id] = nil - request.id = nil - else - local msg - msg, real_end, request.errno = decode_push(body_rpos, body_end) - assert(real_end == body_end, "invalid body length") - request.on_push(request.on_push_ctx, msg) - end - request.cond:broadcast() + internal.dispatch_response_iproto(requests, id, status, + body_rpos, body_end) end local function dispatch_response_console(rid, response) - local request = requests[rid] - if request == nil then -- nobody is waiting for the response - return - end - request.id = nil - requests[rid] = nil - request.response = response - request.cond:broadcast() + internal.dispatch_response_console(requests, rid, response) end local function new_request_id() -- 2.25.1
Needed to rewrite performance-critical parts of net.box in C, e.g. iproto_schema_sm. --- src/box/lua/net_box.c | 31 +++++++++++++++++++++++++++++++ src/box/lua/net_box.lua | 24 +++++++----------------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 044f7d337ca7..1a615797d485 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -79,6 +79,8 @@ enum netbox_method { }; struct netbox_registry { + /* Next request id. */ + uint64_t next_sync; /* sync -> netbox_request */ struct mh_i64ptr_t *requests; }; @@ -278,6 +280,7 @@ netbox_request_push_result(struct netbox_request *request, struct lua_State *L) static int netbox_registry_create(struct netbox_registry *registry) { + registry->next_sync = 1; registry->requests = mh_i64ptr_new(); if (registry->requests == NULL) { diag_set(OutOfMemory, 0, "mhash", "netbox_registry"); @@ -1444,6 +1447,32 @@ luaT_netbox_registry_gc(struct lua_State *L) return 0; } +/* Allocates a new id (sync). */ +static int +luaT_netbox_registry_new_id(struct lua_State *L) +{ + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); + luaL_pushuint64(L, registry->next_sync++); + return 1; +} + +/* + * Returns the next id (sync) without reserving it. + * If called with an argument, returns the id following its value. + */ +static int +luaT_netbox_registry_next_id(struct lua_State *L) +{ + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); + uint64_t next_sync; + if (lua_isnoneornil(L, 2)) + next_sync = registry->next_sync; + else + next_sync = luaL_touint64(L, 2) + 1; + luaL_pushuint64(L, next_sync); + return 1; +} + static int luaT_netbox_registry_reset(struct lua_State *L) { @@ -1798,6 +1827,8 @@ luaopen_net_box(struct lua_State *L) { static const struct luaL_Reg netbox_registry_meta[] = { { "__gc", luaT_netbox_registry_gc }, + { "new_id", luaT_netbox_registry_new_id }, + { "next_id", luaT_netbox_registry_next_id }, { "reset", luaT_netbox_registry_reset }, { NULL, NULL } }; diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 4bc66940ea2a..0643477cbc9c 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -82,8 +82,6 @@ local function version_at_least(peer_version_id, major, minor, patch) return peer_version_id >= version_id(major, minor, patch) end -local function next_id(id) return band(id + 1, 0x7FFFFFFF) end - -- -- Connect to a remote server, do handshake. -- @param host Hostname. @@ -192,7 +190,6 @@ local function create_transport(host, port, user, password, callback, -- Sync requests are implemented as async call + immediate -- wait for a result. local requests = internal.new_registry() - local next_request_id = 1 local worker_fiber local send_buf = buffer.ibuf(buffer.READAHEAD) @@ -301,9 +298,8 @@ local function create_transport(host, port, user, password, callback, if send_buf:size() == 0 then worker_fiber:wakeup() end - local id = next_request_id + local id = requests:new_id() encode_method(method, send_buf, id, ...) - next_request_id = next_id(id) return internal.new_request(requests, id, buffer, skip_header, method, on_push, on_push_ctx, format) end @@ -335,12 +331,6 @@ local function create_transport(host, port, user, password, callback, internal.dispatch_response_console(requests, rid, response) end - local function new_request_id() - local id = next_request_id; - next_request_id = next_id(id) - return id - end - -- IO (WORKER FIBER) -- local function send_and_recv_iproto(timeout) local hdr, body_rpos, body_end = internal.send_and_recv_iproto( @@ -396,7 +386,7 @@ local function create_transport(host, port, user, password, callback, elseif response ~= '---\n...\n' then return error_sm(E_NO_CONNECTION, 'Unexpected response') end - local rid = next_request_id + local rid = requests:next_id() set_state('active') return console_sm(rid) elseif greeting.protocol == 'Binary' then @@ -413,7 +403,7 @@ local function create_transport(host, port, user, password, callback, return error_sm(err, response) else dispatch_response_console(rid, response) - return console_sm(next_id(rid)) + return console_sm(requests:next_id(rid)) end end @@ -423,7 +413,7 @@ local function create_transport(host, port, user, password, callback, set_state('fetch_schema') return iproto_schema_sm() end - encode_auth(send_buf, new_request_id(), user, password, salt) + encode_auth(send_buf, requests:new_id(), user, password, salt) local err, hdr, body_rpos = send_and_recv_iproto() if err then return error_sm(err, hdr) @@ -444,8 +434,8 @@ local function create_transport(host, port, user, password, callback, -- _vcollation view was added in 2.2.0-389-g3e3ef182f local peer_has_vcollation = version_at_least(greeting.version_id, 2, 2, 1) - local select1_id = new_request_id() - local select2_id = new_request_id() + local select1_id = requests:new_id() + local select2_id = requests:new_id() local select3_id local response = {} -- fetch everything from space _vspace, 2 = ITER_ALL @@ -456,7 +446,7 @@ local function create_transport(host, port, user, password, callback, 0xFFFFFFFF, nil) -- fetch everything from space _vcollation, 2 = ITER_ALL if peer_has_vcollation then - select3_id = new_request_id() + select3_id = requests:new_id() encode_method(M_SELECT, send_buf, select3_id, VCOLLATION_ID, 0, 2, 0, 0xFFFFFFFF, nil) end -- 2.25.1
Strictly speaking, it's not necessary, because console performance is not a problem. We do this for consistency with iproto. --- src/box/lua/net_box.c | 135 ++++++++++++++++++++++++++-------------- src/box/lua/net_box.lua | 49 +++++---------- 2 files changed, 106 insertions(+), 78 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 1a615797d485..85a45c54b979 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -850,30 +850,25 @@ netbox_send_and_recv_iproto(lua_State *L) /* * Sends and receives data over a console connection. - * Takes socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. - * On success returns response (string). - * On error returns nil, error. + * Returns a pointer to a response string and its len. + * On error returns NULL. */ -static int -netbox_send_and_recv_console(lua_State *L) +static const char * +netbox_send_and_recv_console(int fd, struct ibuf *send_buf, + struct ibuf *recv_buf, double timeout, + size_t *response_len) { - int fd = lua_tointeger(L, 1); - struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); - struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 3); - double timeout = (!lua_isnoneornil(L, 4) ? - lua_tonumber(L, 4) : TIMEOUT_INFINITY); const char delim[] = "\n...\n"; size_t delim_len = sizeof(delim) - 1; size_t delim_pos; if (netbox_communicate(fd, send_buf, recv_buf, /*limit=*/SIZE_MAX, delim, delim_len, timeout, &delim_pos) != 0) { - - luaL_testcancel(L); - return luaT_push_nil_and_error(L); + return NULL; } - lua_pushlstring(L, recv_buf->rpos, delim_pos + delim_len); + const char *response = recv_buf->rpos; recv_buf->rpos += delim_pos + delim_len; - return 1; + *response_len = delim_pos + delim_len; + return response; } static void @@ -1456,23 +1451,6 @@ luaT_netbox_registry_new_id(struct lua_State *L) return 1; } -/* - * Returns the next id (sync) without reserving it. - * If called with an argument, returns the id following its value. - */ -static int -luaT_netbox_registry_next_id(struct lua_State *L) -{ - struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); - uint64_t next_sync; - if (lua_isnoneornil(L, 2)) - next_sync = registry->next_sync; - else - next_sync = luaL_touint64(L, 2) + 1; - luaL_pushuint64(L, next_sync); - return 1; -} - static int luaT_netbox_registry_reset(struct lua_State *L) { @@ -1802,24 +1780,93 @@ netbox_dispatch_response_iproto(struct lua_State *L) /* * Given a request registry, request id (sync), and a response string, assigns * the response to the request and completes it. + * + * Lua stack is used for temporarily storing the response string before getting + * a reference to it. */ -static int -netbox_dispatch_response_console(struct lua_State *L) +static void +netbox_dispatch_response_console(struct lua_State *L, + struct netbox_registry *registry, + uint64_t sync, const char *response, + size_t response_len) { - struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); - uint64_t sync = luaL_touint64(L, 2); struct netbox_request *request = netbox_registry_lookup(registry, sync); if (request == NULL) { /* Nobody is waiting for the response. */ - return 0; + return; } - /* - * The response is the last argument of this function so it's already - * on the top of the Lua stack. - */ + lua_pushlstring(L, response, response_len); netbox_request_set_result(request, luaL_ref(L, LUA_REGISTRYINDEX)); netbox_request_complete(request); +} + +/* + * Sets up console delimiter. Should be called before serving any requests. + * Takes socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. + * Returns none on success, error on failure. + */ +static int +netbox_console_setup(struct lua_State *L) +{ + static const char setup_delimiter_cmd[] = + "require('console').delimiter('$EOF$')\n"; + static const size_t setup_delimiter_cmd_len = + sizeof(setup_delimiter_cmd) - 1; + static const char ok_response[] = "---\n...\n"; + static const size_t ok_response_len = sizeof(ok_response) - 1; + int fd = lua_tointeger(L, 1); + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 3); + double timeout = (!lua_isnoneornil(L, 4) ? + lua_tonumber(L, 4) : TIMEOUT_INFINITY); + void *wpos = ibuf_alloc(send_buf, setup_delimiter_cmd_len); + if (wpos == NULL) + return luaL_error(L, "out of memory"); + memcpy(wpos, setup_delimiter_cmd, setup_delimiter_cmd_len); + size_t response_len; + const char *response = netbox_send_and_recv_console( + fd, send_buf, recv_buf, timeout, &response_len); + if (response == NULL) { + luaL_testcancel(L); + goto error; + } + if (strncmp(response, ok_response, ok_response_len) != 0) { + box_error_raise(ER_NO_CONNECTION, "Unexpected response"); + goto error; + } return 0; +error: + luaT_pusherror(L, box_error_last()); + return 1; +} + +/* + * Processes console requests in a loop until an error. + * Takes request registry, socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. + * Returns the error that broke the loop. + */ +static int +netbox_console_loop(struct lua_State *L) +{ + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); + int fd = lua_tointeger(L, 2); + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 3); + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 4); + double timeout = (!lua_isnoneornil(L, 5) ? + lua_tonumber(L, 5) : TIMEOUT_INFINITY); + uint64_t sync = registry->next_sync; + while (true) { + size_t response_len; + const char *response = netbox_send_and_recv_console( + fd, send_buf, recv_buf, timeout, &response_len); + if (response == NULL) { + luaL_testcancel(L); + luaT_pusherror(L, box_error_last()); + return 1; + } + netbox_dispatch_response_console(L, registry, sync++, + response, response_len); + } } int @@ -1828,7 +1875,6 @@ luaopen_net_box(struct lua_State *L) static const struct luaL_Reg netbox_registry_meta[] = { { "__gc", luaT_netbox_registry_gc }, { "new_id", luaT_netbox_registry_new_id }, - { "next_id", luaT_netbox_registry_next_id }, { "reset", luaT_netbox_registry_reset }, { NULL, NULL } }; @@ -1850,12 +1896,11 @@ luaopen_net_box(struct lua_State *L) { "encode_method", netbox_encode_method }, { "decode_greeting",netbox_decode_greeting }, { "send_and_recv_iproto", netbox_send_and_recv_iproto }, - { "send_and_recv_console", netbox_send_and_recv_console }, { "new_registry", netbox_new_registry }, { "new_request", netbox_new_request }, { "dispatch_response_iproto", netbox_dispatch_response_iproto }, - { "dispatch_response_console", - netbox_dispatch_response_console }, + { "console_setup", netbox_console_setup }, + { "console_loop", netbox_console_loop }, { NULL, NULL} }; /* luaL_register_module polutes _G */ diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 0643477cbc9c..0a21c1341117 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -327,10 +327,6 @@ local function create_transport(host, port, user, password, callback, body_rpos, body_end) end - local function dispatch_response_console(rid, response) - internal.dispatch_response_console(requests, rid, response) - end - -- IO (WORKER FIBER) -- local function send_and_recv_iproto(timeout) local hdr, body_rpos, body_end = internal.send_and_recv_iproto( @@ -342,22 +338,14 @@ local function create_transport(host, port, user, password, callback, return nil, hdr, body_rpos, body_end end - local function send_and_recv_console(timeout) - local response, err = internal.send_and_recv_console( - connection:fd(), send_buf, recv_buf, timeout) - if not response then - return err.code, err.message - end - return nil, response - end - -- PROTOCOL STATE MACHINE (WORKER FIBER) -- -- -- The sm is implemented as a collection of functions performing -- tail-recursive calls to each other. Yep, Lua optimizes -- such calls, and yep, this is the canonical way to implement -- a state machine in Lua. - local console_sm, iproto_auth_sm, iproto_schema_sm, iproto_sm, error_sm + local console_setup_sm, console_sm, iproto_auth_sm, iproto_schema_sm, + iproto_sm, error_sm -- -- Protocol_sm is a core function of netbox. It calls all @@ -376,19 +364,7 @@ local function create_transport(host, port, user, password, callback, end -- @deprecated since 1.10 if greeting.protocol == 'Lua console' then - log.warn("Netbox text protocol support is deprecated since 1.10, ".. - "please use require('console').connect() instead") - local setup_delimiter = 'require("console").delimiter("$EOF$")\n' - encode_method(M_INJECT, send_buf, nil, setup_delimiter) - local err, response = send_and_recv_console() - if err then - return error_sm(err, response) - elseif response ~= '---\n...\n' then - return error_sm(E_NO_CONNECTION, 'Unexpected response') - end - local rid = requests:next_id() - set_state('active') - return console_sm(rid) + return console_setup_sm() elseif greeting.protocol == 'Binary' then return iproto_auth_sm(greeting.salt) else @@ -397,14 +373,21 @@ local function create_transport(host, port, user, password, callback, end end - console_sm = function(rid) - local err, response = send_and_recv_console() + console_setup_sm = function() + log.warn("Netbox text protocol support is deprecated since 1.10, ".. + "please use require('console').connect() instead") + local err = internal.console_setup(connection:fd(), send_buf, recv_buf) if err then - return error_sm(err, response) - else - dispatch_response_console(rid, response) - return console_sm(requests:next_id(rid)) + return error_sm(err.code, err.message) end + set_state('active') + return console_sm() + end + + console_sm = function() + local err = internal.console_loop(requests, connection:fd(), + send_buf, recv_buf) + return error_sm(err.code, err.message) end iproto_auth_sm = function(salt) -- 2.25.1
Those are performance-critical paths. Moving them to C should speed up overall net.box performance. --- src/box/lua/net_box.c | 373 +++++++++++++++++++++++++++------------- src/box/lua/net_box.lua | 141 +++------------ 2 files changed, 277 insertions(+), 237 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 85a45c54b979..fde2a8772890 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -41,7 +41,7 @@ #include "box/tuple.h" #include "box/execute.h" #include "box/error.h" -#include "box/mp_error.h" +#include "box/schema_def.h" #include "lua/msgpack.h" #include <base64.h> @@ -53,6 +53,7 @@ #include "lua/fiber.h" #include "mpstream/mpstream.h" #include "misc.h" /* lbox_check_tuple_format() */ +#include "version.h" #define cfg luaL_msgpack_default @@ -137,6 +138,13 @@ struct netbox_request { static const char netbox_registry_typename[] = "net.box.registry"; static const char netbox_request_typename[] = "net.box.request"; +/* Passed to mpstream_init() to set a boolean flag on error. */ +static void +mpstream_error_handler(void *error_ctx) +{ + *(bool *)error_ctx = true; +} + static void netbox_request_create(struct netbox_request *request) { @@ -391,30 +399,22 @@ netbox_encode_ping(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) netbox_end_encode(&stream, svp); } +/* + * Encodes an authorization request and writes it to the provided buffer. + * Returns -1 on memory allocation error. + */ static int -netbox_encode_auth(lua_State *L) +netbox_encode_auth(struct ibuf *ibuf, uint64_t sync, + const char *user, size_t user_len, + const char *password, size_t password_len, + const char *salt) { - if (lua_gettop(L) < 5) { - return luaL_error(L, "Usage: netbox.encode_update(ibuf, sync, " - "user, password, greeting)"); - } - struct ibuf *ibuf = (struct ibuf *) lua_topointer(L, 1); - uint64_t sync = luaL_touint64(L, 2); - + bool is_error = false; struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, - luamp_error, L); + mpstream_error_handler, &is_error); size_t svp = netbox_begin_encode(&stream, sync, IPROTO_AUTH); - size_t user_len; - const char *user = lua_tolstring(L, 3, &user_len); - size_t password_len; - const char *password = lua_tolstring(L, 4, &password_len); - size_t salt_len; - const char *salt = lua_tolstring(L, 5, &salt_len); - if (salt_len < SCRAMBLE_SIZE) - return luaL_error(L, "Invalid salt"); - /* Adapted from xrow_encode_auth() */ mpstream_encode_map(&stream, password != NULL ? 2 : 1); mpstream_encode_uint(&stream, IPROTO_USER_NAME); @@ -429,7 +429,30 @@ netbox_encode_auth(lua_State *L) } netbox_end_encode(&stream, svp); - return 0; + return is_error ? -1 : 0; +} + +/* + * Encodes a SELECT(*) request and writes it to the provided buffer. + * Returns -1 on memory allocation error. + */ +static int +netbox_encode_select_all(struct ibuf *ibuf, uint64_t sync, uint32_t space_id) +{ + bool is_error = false; + struct mpstream stream; + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + mpstream_error_handler, &is_error); + size_t svp = netbox_begin_encode(&stream, sync, IPROTO_SELECT); + mpstream_encode_map(&stream, 3); + mpstream_encode_uint(&stream, IPROTO_SPACE_ID); + mpstream_encode_uint(&stream, space_id); + mpstream_encode_uint(&stream, IPROTO_LIMIT); + mpstream_encode_uint(&stream, UINT32_MAX); + mpstream_encode_uint(&stream, IPROTO_KEY); + mpstream_encode_array(&stream, 0); + netbox_end_encode(&stream, svp); + return is_error ? -1 : 0; } static void @@ -798,18 +821,14 @@ handle_error: /* * Sends and receives data over an iproto connection. - * Takes socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. - * On success returns header (table), body_rpos (char *), body_end (char *). - * On error returns nil, error. + * Returns 0 and a decoded response header on success. + * On error returns -1. */ -static int -netbox_send_and_recv_iproto(lua_State *L) +int +netbox_send_and_recv_iproto(int fd, struct ibuf *send_buf, + struct ibuf *recv_buf, double timeout, + struct xrow_header *hdr) { - int fd = lua_tointeger(L, 1); - struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); - struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 3); - double timeout = (!lua_isnoneornil(L, 4) ? - lua_tonumber(L, 4) : TIMEOUT_INFINITY); while (true) { size_t required; size_t data_len = ibuf_used(recv_buf); @@ -817,21 +836,16 @@ netbox_send_and_recv_iproto(lua_State *L) if (data_len < fixheader_size) { required = fixheader_size; } else { - /* PWN! insufficient input validation */ const char *bufpos = recv_buf->rpos; const char *rpos = bufpos; size_t len = mp_decode_uint(&rpos); required = (rpos - bufpos) + len; if (data_len >= required) { const char *body_end = rpos + len; - const char *body_rpos = rpos; - luamp_decode(L, cfg, &body_rpos); - *(const char **)luaL_pushcdata( - L, CTID_CONST_CHAR_PTR) = body_rpos; - *(const char **)luaL_pushcdata( - L, CTID_CONST_CHAR_PTR) = body_end; recv_buf->rpos = (char *)body_end; - return 3; + return xrow_header_decode( + hdr, &rpos, body_end, + /*end_is_exact=*/true); } } size_t unused; @@ -841,8 +855,7 @@ netbox_send_and_recv_iproto(lua_State *L) /*boundary=*/NULL, /*boundary_len=*/0, timeout, &unused) != 0) { - luaL_testcancel(L); - return luaT_push_nil_and_error(L); + return -1; } timeout = deadline - fiber_clock(); } @@ -1377,57 +1390,6 @@ netbox_decode_method(struct lua_State *L, enum netbox_method method, method_decoder[method](L, data, data_end, format); } -/* - * Decodes an error from raw data. On success returns the decoded error object - * with ref counter incremented. On failure returns NULL. - */ -static struct error * -netbox_decode_error(const char **data, uint32_t errcode) -{ - struct error *error = NULL; - assert(mp_typeof(**data) == MP_MAP); - uint32_t map_size = mp_decode_map(data); - for (uint32_t i = 0; i < map_size; ++i) { - uint32_t key = mp_decode_uint(data); - if (key == IPROTO_ERROR) { - if (error != NULL) - error_unref(error); - error = error_unpack_unsafe(data); - if (error == NULL) - return NULL; - error_ref(error); - /* - * IPROTO_ERROR comprises error encoded with - * IPROTO_ERROR_24, so we may ignore content - * of that key. - */ - break; - } else if (key == IPROTO_ERROR_24) { - if (error != NULL) - error_unref(error); - const char *reason = ""; - uint32_t reason_len = 0; - if (mp_typeof(**data) == MP_STR) - reason = mp_decode_str(data, &reason_len); - box_error_raise(errcode, "%.*s", reason_len, reason); - error = box_error_last(); - error_ref(error); - continue; - } - mp_next(data); /* skip value */ - } - if (error == NULL) { - /* - * Error body is missing in the response. - * Set the error code without a 'reason' message - */ - box_error_raise(errcode, ""); - error = box_error_last(); - error_ref(error); - } - return error; -} - static inline struct netbox_registry * luaT_check_netbox_registry(struct lua_State *L, int idx) { @@ -1704,37 +1666,35 @@ netbox_new_request(struct lua_State *L) } /* - * Given a request registry, request id (sync), status, and a pointer to a - * response body, decodes the response and either completes the request or - * invokes the on-push trigger, depending on the status. + * Given a request registry and a response header, decodes the response and + * either completes the request or invokes the on-push trigger, depending on + * the status. + * + * Lua stack is used for temporarily storing the response table before getting + * a reference to it and executing the on-push trigger. */ -static int -netbox_dispatch_response_iproto(struct lua_State *L) +static void +netbox_dispatch_response_iproto(struct lua_State *L, + struct netbox_registry *registry, + struct xrow_header *hdr) { - struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); - uint64_t sync = luaL_touint64(L, 2); - enum iproto_type status = lua_tointeger(L, 3); - uint32_t ctypeid; - const char *data = *(const char **)luaL_checkcdata(L, 4, &ctypeid); - assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); - const char *data_end = *(const char **)luaL_checkcdata(L, 5, &ctypeid); - assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); - struct netbox_request *request = netbox_registry_lookup(registry, sync); + struct netbox_request *request = netbox_registry_lookup(registry, + hdr->sync); if (request == NULL) { /* Nobody is waiting for the response. */ - return 0; + return; } + enum iproto_type status = hdr->type; if (status > IPROTO_CHUNK) { /* Handle errors. */ - struct error *error = netbox_decode_error( - &data, status & (IPROTO_TYPE_ERROR - 1)); - if (error == NULL) - return luaT_error(L); + xrow_decode_error(hdr); + struct error *error = box_error_last(); netbox_request_set_error(request, error); - error_unref(error); netbox_request_complete(request); - return 0; + return; } + const char *data = hdr->body[0].iov_base; + const char *data_end = data + hdr->body[0].iov_len; if (request->buffer != NULL) { /* Copy xrow.body to user-provided buffer. */ if (request->skip_header) @@ -1774,7 +1734,6 @@ netbox_dispatch_response_iproto(struct lua_State *L) lua_call(L, 2, 0); netbox_request_signal(request); } - return 0; } /* @@ -1800,6 +1759,188 @@ netbox_dispatch_response_console(struct lua_State *L, netbox_request_complete(request); } +/* + * Performs an authorization request for an iproto connection. + * Takes user, password, salt, request registry, socket fd, + * send_buf (ibuf), recv_buf (ibuf), timeout. + * Returns schema_version on success, nil and error on failure. + */ +static int +netbox_iproto_auth(struct lua_State *L) +{ + size_t user_len; + const char *user = lua_tolstring(L, 1, &user_len); + size_t password_len; + const char *password = lua_tolstring(L, 2, &password_len); + size_t salt_len; + const char *salt = lua_tolstring(L, 3, &salt_len); + if (salt_len < SCRAMBLE_SIZE) + return luaL_error(L, "Invalid salt"); + struct netbox_registry *registry = luaT_check_netbox_registry(L, 4); + int fd = lua_tointeger(L, 5); + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 6); + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 7); + double timeout = (!lua_isnoneornil(L, 8) ? + lua_tonumber(L, 8) : TIMEOUT_INFINITY); + if (netbox_encode_auth(send_buf, registry->next_sync++, user, user_len, + password, password_len, salt) != 0) { + goto error; + } + struct xrow_header hdr; + if (netbox_send_and_recv_iproto(fd, send_buf, recv_buf, timeout, + &hdr) != 0) { + goto error; + } + if (hdr.type != IPROTO_OK) { + xrow_decode_error(&hdr); + goto error; + } + lua_pushinteger(L, hdr.schema_version); + return 1; +error: + return luaT_push_nil_and_error(L); +} + +/* + * Fetches schema over an iproto connection. While waiting for the schema, + * processes other requests in a loop, like netbox_iproto_loop(). + * Takes peer_version_id, request registry, socket fd, send_buf (ibuf), + * recv_buf (ibuf), timeout. + * Returns schema_version and a table with the following fields: + * [VSPACE_ID] = <spaces> + * [VINDEX_ID] = <indexes> + * [VCOLLATION_ID] = <collations> + * On failure returns nil, error. + */ +static int +netbox_iproto_schema(struct lua_State *L) +{ + uint32_t peer_version_id = lua_tointeger(L, 1); + struct netbox_registry *registry = luaT_check_netbox_registry(L, 2); + int fd = lua_tointeger(L, 3); + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 4); + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 5); + double timeout = (!lua_isnoneornil(L, 6) ? + lua_tonumber(L, 6) : TIMEOUT_INFINITY); + /* _vcollation view was added in 2.2.0-389-g3e3ef182f */ + bool peer_has_vcollation = peer_version_id >= version_id(2, 2, 1); +restart: + lua_newtable(L); + uint64_t vspace_sync = registry->next_sync++; + if (netbox_encode_select_all(send_buf, vspace_sync, + BOX_VSPACE_ID) != 0) { + return luaT_error(L); + } + uint64_t vindex_sync = registry->next_sync++; + if (netbox_encode_select_all(send_buf, vindex_sync, + BOX_VINDEX_ID) != 0) { + return luaT_error(L); + } + uint64_t vcollation_sync = registry->next_sync++; + if (peer_has_vcollation && + netbox_encode_select_all(send_buf, vcollation_sync, + BOX_VCOLLATION_ID) != 0) { + return luaT_error(L); + } + bool got_vspace = false; + bool got_vindex = false; + bool got_vcollation = false; + uint32_t schema_version = 0; + do { + struct xrow_header hdr; + if (netbox_send_and_recv_iproto(fd, send_buf, recv_buf, + timeout, &hdr) != 0) { + luaL_testcancel(L); + return luaT_push_nil_and_error(L); + } + netbox_dispatch_response_iproto(L, registry, &hdr); + if (hdr.sync != vspace_sync && + hdr.sync != vindex_sync && + hdr.sync != vcollation_sync) { + continue; + } + if (iproto_type_is_error(hdr.type)) { + uint32_t errcode = hdr.type & (IPROTO_TYPE_ERROR - 1); + if (errcode == ER_NO_SUCH_SPACE && + hdr.sync == vcollation_sync) { + /* + * No _vcollation space + * (server has old schema version). + */ + peer_has_vcollation = false; + continue; + } + xrow_decode_error(&hdr); + return luaT_push_nil_and_error(L); + } + if (schema_version == 0) { + schema_version = hdr.schema_version; + } else if (schema_version != hdr.schema_version) { + /* + * Schema changed while fetching schema. + * Restart loader. + */ + lua_pop(L, 1); + goto restart; + } + const char *data = hdr.body[0].iov_base; + const char *data_end = data + hdr.body[0].iov_len; + int key; + if (hdr.sync == vspace_sync) { + key = BOX_VSPACE_ID; + got_vspace = true; + } else if (hdr.sync == vindex_sync) { + key = BOX_VINDEX_ID; + got_vindex = true; + } else if (hdr.sync == vcollation_sync) { + key = BOX_VCOLLATION_ID; + got_vcollation = true; + } else { + unreachable(); + } + lua_pushinteger(L, key); + netbox_decode_table(L, &data, data_end, tuple_format_runtime); + lua_settable(L, -3); + } while (!(got_vspace && got_vindex && + (got_vcollation || !peer_has_vcollation))); + lua_pushinteger(L, schema_version); + lua_insert(L, -2); + return 2; +} + +/* + * Processes iproto requests in a loop until an error or a schema change. + * Takes schema_version, request registry, socket fd, send_buf (ibuf), + * recv_buf (ibuf), timeout. + * Returns schema_version if the loop was broken because of a schema change. + * If the loop was broken by an error, returns nil and the error. + */ +static int +netbox_iproto_loop(struct lua_State *L) +{ + uint32_t schema_version = lua_tointeger(L, 1); + struct netbox_registry *registry = luaT_check_netbox_registry(L, 2); + int fd = lua_tointeger(L, 3); + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 4); + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 5); + double timeout = (!lua_isnoneornil(L, 6) ? + lua_tonumber(L, 6) : TIMEOUT_INFINITY); + while (true) { + struct xrow_header hdr; + if (netbox_send_and_recv_iproto(fd, send_buf, recv_buf, + timeout, &hdr) != 0) { + luaL_testcancel(L); + return luaT_push_nil_and_error(L); + } + netbox_dispatch_response_iproto(L, registry, &hdr); + if (hdr.schema_version > 0 && + hdr.schema_version != schema_version) { + lua_pushinteger(L, hdr.schema_version); + return 1; + } + } +} + /* * Sets up console delimiter. Should be called before serving any requests. * Takes socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. @@ -1892,13 +2033,13 @@ luaopen_net_box(struct lua_State *L) luaL_register_type(L, netbox_request_typename, netbox_request_meta); static const luaL_Reg net_box_lib[] = { - { "encode_auth", netbox_encode_auth }, { "encode_method", netbox_encode_method }, { "decode_greeting",netbox_decode_greeting }, - { "send_and_recv_iproto", netbox_send_and_recv_iproto }, { "new_registry", netbox_new_registry }, { "new_request", netbox_new_request }, - { "dispatch_response_iproto", netbox_dispatch_response_iproto }, + { "iproto_auth", netbox_iproto_auth }, + { "iproto_schema", netbox_iproto_schema }, + { "iproto_loop", netbox_iproto_loop }, { "console_setup", netbox_console_setup }, { "console_loop", netbox_console_loop }, { NULL, NULL} diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 0a21c1341117..13def54de2c5 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -9,18 +9,15 @@ local urilib = require('uri') local internal = require('net.box.lib') local trigger = require('internal.trigger') -local band = bit.band local max = math.max local fiber_clock = fiber.clock local fiber_self = fiber.self -local decode = msgpack.decode_unchecked local check_iterator_type = box.internal.check_iterator_type local check_index_arg = box.internal.check_index_arg local check_space_arg = box.internal.check_space_arg local check_primary_index = box.internal.check_primary_index -local encode_auth = internal.encode_auth local encode_method = internal.encode_method local decode_greeting = internal.decode_greeting @@ -29,21 +26,12 @@ local VSPACE_ID = 281 local VINDEX_ID = 289 local VCOLLATION_ID = 277 local DEFAULT_CONNECT_TIMEOUT = 10 - -local IPROTO_STATUS_KEY = 0x00 -local IPROTO_ERRNO_MASK = 0x7FFF -local IPROTO_SYNC_KEY = 0x01 -local IPROTO_SCHEMA_VERSION_KEY = 0x05 -local IPROTO_DATA_KEY = 0x30 -local IPROTO_ERROR_24 = 0x31 -local IPROTO_ERROR = 0x52 local IPROTO_GREETING_SIZE = 128 -- select errors from box.error local E_UNKNOWN = box.error.UNKNOWN local E_NO_CONNECTION = box.error.NO_CONNECTION local E_PROC_LUA = box.error.PROC_LUA -local E_NO_SUCH_SPACE = box.error.NO_SUCH_SPACE -- Method types used internally by net.box. local M_PING = 0 @@ -69,19 +57,6 @@ local M_INJECT = 17 -- utility tables local is_final_state = {closed = 1, error = 1} -local function decode_push(raw_data) - local response, raw_end = decode(raw_data) - return response[IPROTO_DATA_KEY][1], raw_end -end - -local function version_id(major, minor, patch) - return bit.bor(bit.lshift(major, 16), bit.lshift(minor, 8), patch) -end - -local function version_at_least(peer_version_id, major, minor, patch) - return peer_version_id >= version_id(major, minor, patch) -end - -- -- Connect to a remote server, do handshake. -- @param host Hostname. @@ -320,24 +295,6 @@ local function create_transport(host, port, user, password, callback, return request:wait_result(timeout) end - local function dispatch_response_iproto(hdr, body_rpos, body_end) - local id = hdr[IPROTO_SYNC_KEY] - local status = hdr[IPROTO_STATUS_KEY] - internal.dispatch_response_iproto(requests, id, status, - body_rpos, body_end) - end - - -- IO (WORKER FIBER) -- - local function send_and_recv_iproto(timeout) - local hdr, body_rpos, body_end = internal.send_and_recv_iproto( - connection:fd(), send_buf, recv_buf, timeout) - if not hdr then - local err = body_rpos - return err.code, err.message - end - return nil, hdr, body_rpos, body_end - end - -- PROTOCOL STATE MACHINE (WORKER FIBER) -- -- -- The sm is implemented as a collection of functions performing @@ -396,17 +353,13 @@ local function create_transport(host, port, user, password, callback, set_state('fetch_schema') return iproto_schema_sm() end - encode_auth(send_buf, requests:new_id(), user, password, salt) - local err, hdr, body_rpos = send_and_recv_iproto() - if err then - return error_sm(err, hdr) - end - if hdr[IPROTO_STATUS_KEY] ~= 0 then - local body = decode(body_rpos) - return error_sm(E_NO_CONNECTION, body[IPROTO_ERROR_24]) + local schema_version, err = internal.iproto_auth( + user, password, salt, requests, connection:fd(), send_buf, recv_buf) + if not schema_version then + return error_sm(err.code, err.message) end set_state('fetch_schema') - return iproto_schema_sm(hdr[IPROTO_SCHEMA_VERSION_KEY]) + return iproto_schema_sm(schema_version) end iproto_schema_sm = function(schema_version) @@ -414,82 +367,28 @@ local function create_transport(host, port, user, password, callback, set_state('active') return iproto_sm(schema_version) end - -- _vcollation view was added in 2.2.0-389-g3e3ef182f - local peer_has_vcollation = version_at_least(greeting.version_id, - 2, 2, 1) - local select1_id = requests:new_id() - local select2_id = requests:new_id() - local select3_id - local response = {} - -- fetch everything from space _vspace, 2 = ITER_ALL - encode_method(M_SELECT, send_buf, select1_id, VSPACE_ID, 0, 2, 0, - 0xFFFFFFFF, nil) - -- fetch everything from space _vindex, 2 = ITER_ALL - encode_method(M_SELECT, send_buf, select2_id, VINDEX_ID, 0, 2, 0, - 0xFFFFFFFF, nil) - -- fetch everything from space _vcollation, 2 = ITER_ALL - if peer_has_vcollation then - select3_id = requests:new_id() - encode_method(M_SELECT, send_buf, select3_id, VCOLLATION_ID, - 0, 2, 0, 0xFFFFFFFF, nil) + local schema_version, schema = internal.iproto_schema( + greeting.version_id, requests, connection:fd(), send_buf, recv_buf) + if not schema_version then + local err = schema + return error_sm(err.code, err.message) end - - schema_version = nil -- any schema_version will do provided that - -- it is consistent across responses - repeat - local err, hdr, body_rpos, body_end = send_and_recv_iproto() - if err then return error_sm(err, hdr) end - dispatch_response_iproto(hdr, body_rpos, body_end) - local id = hdr[IPROTO_SYNC_KEY] - -- trick: omit check for peer_has_vcollation: id is - -- not nil - if id == select1_id or id == select2_id or id == select3_id then - -- response to a schema query we've submitted - local status = hdr[IPROTO_STATUS_KEY] - local response_schema_version = hdr[IPROTO_SCHEMA_VERSION_KEY] - if status ~= 0 then - -- No _vcollation space (server has an old - -- schema version). - local errno = band(status, IPROTO_ERRNO_MASK) - if id == select3_id and errno == E_NO_SUCH_SPACE then - peer_has_vcollation = false - goto continue - end - local body = decode(body_rpos) - return error_sm(E_NO_CONNECTION, body[IPROTO_ERROR_24]) - end - if schema_version == nil then - schema_version = response_schema_version - elseif schema_version ~= response_schema_version then - -- schema changed while fetching schema; restart loader - return iproto_schema_sm() - end - local body = decode(body_rpos) - response[id] = body[IPROTO_DATA_KEY] - end - ::continue:: - until response[select1_id] and response[select2_id] and - (not peer_has_vcollation or response[select3_id]) - -- trick: response[select3_id] is nil when the key is nil - callback('did_fetch_schema', schema_version, response[select1_id], - response[select2_id], response[select3_id]) + callback('did_fetch_schema', schema_version, schema[VSPACE_ID], + schema[VINDEX_ID], schema[VCOLLATION_ID]) set_state('active') return iproto_sm(schema_version) end iproto_sm = function(schema_version) - local err, hdr, body_rpos, body_end = send_and_recv_iproto() - if err then return error_sm(err, hdr) end - dispatch_response_iproto(hdr, body_rpos, body_end) - local response_schema_version = hdr[IPROTO_SCHEMA_VERSION_KEY] - if response_schema_version > 0 and - response_schema_version ~= schema_version then - -- schema_version has been changed - start to load a new version. - -- Sic: self.schema_version will be updated only after reload. - set_state('fetch_schema') - return iproto_schema_sm(schema_version) + local schema_version, err = internal.iproto_loop( + schema_version, requests, connection:fd(), send_buf, recv_buf) + if not schema_version then + return error_sm(err.code, err.message) end - return iproto_sm(schema_version) + -- schema_version has been changed - start to load a new version. + -- Sic: self.schema_version will be updated only after reload. + set_state('fetch_schema') + return iproto_schema_sm(schema_version) end error_sm = function(err, msg) -- 2.25.1
So as not to call tree C functions per each request, let's merge them and call the resulting function perform_async_request. --- src/box/lua/net_box.c | 55 +++++++++++++++++------------------------ src/box/lua/net_box.lua | 8 +++--- 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index fde2a8772890..844a1de613f2 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -962,17 +962,13 @@ netbox_encode_inject(struct lua_State *L, int idx, struct ibuf *ibuf, } /* - * Encodes a request for the specified method. - * - * Takes three mandatory arguments: - * - method: a value from the netbox_method enumeration - * - ibuf: buffer to write the result to - * - sync: value of the IPROTO_SYNC key - * - * Other arguments are method-specific. + * Encodes a request for the specified method and writes the result to the + * provided buffer. Values to encode depend on the method and are passed via + * Lua stack starting at index idx. */ static int -netbox_encode_method(struct lua_State *L) +netbox_encode_method(struct lua_State *L, int idx, enum netbox_method method, + struct ibuf *ibuf, uint64_t sync) { typedef void (*method_encoder_f)(struct lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync); @@ -996,11 +992,7 @@ netbox_encode_method(struct lua_State *L) [NETBOX_COUNT] = netbox_encode_call, [NETBOX_INJECT] = netbox_encode_inject, }; - enum netbox_method method = lua_tointeger(L, 1); - assert(method < netbox_method_MAX); - struct ibuf *ibuf = (struct ibuf *) lua_topointer(L, 2); - uint64_t sync = luaL_touint64(L, 3); - method_encoder[method](L, 4, ibuf, sync); + method_encoder[method](L, idx, ibuf, sync); return 0; } @@ -1404,15 +1396,6 @@ luaT_netbox_registry_gc(struct lua_State *L) return 0; } -/* Allocates a new id (sync). */ -static int -luaT_netbox_registry_new_id(struct lua_State *L) -{ - struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); - luaL_pushuint64(L, registry->next_sync++); - return 1; -} - static int luaT_netbox_registry_reset(struct lua_State *L) { @@ -1624,33 +1607,43 @@ netbox_new_registry(struct lua_State *L) } /* - * Creates a request object (userdata) and pushes it to Lua stack. + * Writes a request to the send buffer and registers the request object + * ('future') that can be used for waiting for a response. * * Takes the following arguments: * - requests: registry to register the new request with - * - id: id (sync) to assign to the new request + * - send_buf: buffer (ibuf) to write the encoded request to * - buffer: buffer (ibuf) to write the result to or nil * - skip_header: whether to skip header when writing the result to the buffer * - method: a value from the netbox_method enumeration * - on_push: on_push trigger function * - on_push_ctx: on_push trigger function argument * - format: tuple format to use for decoding the body or nil + * - ...: method-specific arguments passed to the encoder */ static int -netbox_new_request(struct lua_State *L) +netbox_perform_async_request(struct lua_State *L) { struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); netbox_request_create(request); luaL_getmetatable(L, netbox_request_typename); lua_setmetatable(L, -2); + + /* Encode and write the request to the send buffer. */ struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); - request->sync = luaL_touint64(L, 2); + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); + enum netbox_method method = lua_tointeger(L, 5); + assert(method < netbox_method_MAX); + uint64_t sync = registry->next_sync++; + netbox_encode_method(L, 9, method, send_buf, sync); + + /* Initialize and register the request object. */ + request->method = method; + request->sync = sync; request->buffer = (struct ibuf *) lua_topointer(L, 3); lua_pushvalue(L, 3); request->buffer_ref = luaL_ref(L, LUA_REGISTRYINDEX); request->skip_header = lua_toboolean(L, 4); - request->method = lua_tointeger(L, 5); - assert(request->method < netbox_method_MAX); lua_pushvalue(L, 6); request->on_push_ref = luaL_ref(L, LUA_REGISTRYINDEX); lua_pushvalue(L, 7); @@ -2015,7 +2008,6 @@ luaopen_net_box(struct lua_State *L) { static const struct luaL_Reg netbox_registry_meta[] = { { "__gc", luaT_netbox_registry_gc }, - { "new_id", luaT_netbox_registry_new_id }, { "reset", luaT_netbox_registry_reset }, { NULL, NULL } }; @@ -2033,10 +2025,9 @@ luaopen_net_box(struct lua_State *L) luaL_register_type(L, netbox_request_typename, netbox_request_meta); static const luaL_Reg net_box_lib[] = { - { "encode_method", netbox_encode_method }, { "decode_greeting",netbox_decode_greeting }, { "new_registry", netbox_new_registry }, - { "new_request", netbox_new_request }, + { "perform_async_request", netbox_perform_async_request }, { "iproto_auth", netbox_iproto_auth }, { "iproto_schema", netbox_iproto_schema }, { "iproto_loop", netbox_iproto_loop }, diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 13def54de2c5..55d172a1f6b9 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -18,7 +18,6 @@ local check_index_arg = box.internal.check_index_arg local check_space_arg = box.internal.check_space_arg local check_primary_index = box.internal.check_primary_index -local encode_method = internal.encode_method local decode_greeting = internal.decode_greeting local TIMEOUT_INFINITY = 500 * 365 * 86400 @@ -273,10 +272,9 @@ local function create_transport(host, port, user, password, callback, if send_buf:size() == 0 then worker_fiber:wakeup() end - local id = requests:new_id() - encode_method(method, send_buf, id, ...) - return internal.new_request(requests, id, buffer, skip_header, method, - on_push, on_push_ctx, format) + return internal.perform_async_request(requests, send_buf, buffer, + skip_header, method, on_push, + on_push_ctx, format, ...) end -- -- 2.25.1
It's not really necessary - we can wait for the request to complete in C code, without returning to Lua. Since creating a Lua object puts extra pressure on the garbage collector, we'd better avoid it when we can. --- src/box/lua/net_box.c | 67 +++++++++++++++++++++++++++++------------ src/box/lua/net_box.lua | 39 ++++++++++++++++-------- 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 844a1de613f2..684091cf898f 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -1610,7 +1610,7 @@ netbox_new_registry(struct lua_State *L) * Writes a request to the send buffer and registers the request object * ('future') that can be used for waiting for a response. * - * Takes the following arguments: + * Takes the following values from Lua stack starting at index idx: * - requests: registry to register the new request with * - send_buf: buffer (ibuf) to write the encoded request to * - buffer: buffer (ibuf) to write the result to or nil @@ -1621,43 +1621,71 @@ netbox_new_registry(struct lua_State *L) * - format: tuple format to use for decoding the body or nil * - ...: method-specific arguments passed to the encoder */ -static int -netbox_perform_async_request(struct lua_State *L) +static void +netbox_perform_async_request_impl(struct lua_State *L, int idx, + struct netbox_request *request) { - struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); - netbox_request_create(request); - luaL_getmetatable(L, netbox_request_typename); - lua_setmetatable(L, -2); - /* Encode and write the request to the send buffer. */ - struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); - struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); - enum netbox_method method = lua_tointeger(L, 5); + struct netbox_registry *registry = luaT_check_netbox_registry(L, idx); + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, idx + 1); + enum netbox_method method = lua_tointeger(L, idx + 4); assert(method < netbox_method_MAX); uint64_t sync = registry->next_sync++; - netbox_encode_method(L, 9, method, send_buf, sync); + netbox_encode_method(L, idx + 8, method, send_buf, sync); /* Initialize and register the request object. */ request->method = method; request->sync = sync; - request->buffer = (struct ibuf *) lua_topointer(L, 3); - lua_pushvalue(L, 3); + request->buffer = (struct ibuf *) lua_topointer(L, idx + 2); + lua_pushvalue(L, idx + 2); request->buffer_ref = luaL_ref(L, LUA_REGISTRYINDEX); - request->skip_header = lua_toboolean(L, 4); - lua_pushvalue(L, 6); + request->skip_header = lua_toboolean(L, idx + 3); + lua_pushvalue(L, idx + 5); request->on_push_ref = luaL_ref(L, LUA_REGISTRYINDEX); - lua_pushvalue(L, 7); + lua_pushvalue(L, idx + 6); request->on_push_ctx_ref = luaL_ref(L, LUA_REGISTRYINDEX); - if (!lua_isnil(L, 8)) - request->format = lbox_check_tuple_format(L, 8); + if (!lua_isnil(L, idx + 7)) + request->format = lbox_check_tuple_format(L, idx + 7); else request->format = tuple_format_runtime; tuple_format_ref(request->format); if (netbox_request_register(request, registry) != 0) luaT_error(L); +} + +static int +netbox_perform_async_request(struct lua_State *L) +{ + struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); + netbox_request_create(request); + luaL_getmetatable(L, netbox_request_typename); + lua_setmetatable(L, -2); + netbox_perform_async_request_impl(L, 1, request); return 1; } +static int +netbox_perform_request(struct lua_State *L) +{ + double timeout = (!lua_isnil(L, 1) ? + lua_tonumber(L, 1) : TIMEOUT_INFINITY); + struct netbox_request request; + netbox_request_create(&request); + netbox_perform_async_request_impl(L, 2, &request); + while (!netbox_request_is_ready(&request)) { + if (!netbox_request_wait(&request, &timeout)) { + netbox_request_unregister(&request); + netbox_request_destroy(&request); + luaL_testcancel(L); + diag_set(ClientError, ER_TIMEOUT); + return luaT_push_nil_and_error(L); + } + } + int ret = netbox_request_push_result(&request, L); + netbox_request_destroy(&request); + return ret; +} + /* * Given a request registry and a response header, decodes the response and * either completes the request or invokes the on-push trigger, depending on @@ -2028,6 +2056,7 @@ luaopen_net_box(struct lua_State *L) { "decode_greeting",netbox_decode_greeting }, { "new_registry", netbox_new_registry }, { "perform_async_request", netbox_perform_async_request }, + { "perform_request",netbox_perform_request }, { "iproto_auth", netbox_iproto_auth }, { "iproto_schema", netbox_iproto_schema }, { "iproto_loop", netbox_iproto_loop }, diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 55d172a1f6b9..d6367a848aa1 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -253,19 +253,27 @@ local function create_transport(host, port, user, password, callback, end end - -- - -- Send a request and do not wait for response. - -- @retval nil, error Error occured. - -- @retval not nil Future object. - -- - local function perform_async_request(buffer, skip_header, method, on_push, - on_push_ctx, format, ...) + local function check_active() if state ~= 'active' and state ~= 'fetch_schema' then local code = last_errno or E_NO_CONNECTION local msg = last_error or string.format('Connection is not established, state is "%s"', state) - return nil, box.error.new({code = code, reason = msg}) + return box.error.new({code = code, reason = msg}) + end + return nil + end + + -- + -- Send a request and do not wait for response. + -- @retval nil, error Error occured. + -- @retval not nil Future object. + -- + local function perform_async_request(buffer, skip_header, method, on_push, + on_push_ctx, format, ...) + local err = check_active() + if err then + return nil, err end -- alert worker to notify it of the queued outgoing data; -- if the buffer wasn't empty, assume the worker was already alerted @@ -284,13 +292,18 @@ local function create_transport(host, port, user, password, callback, -- local function perform_request(timeout, buffer, skip_header, method, on_push, on_push_ctx, format, ...) - local request, err = - perform_async_request(buffer, skip_header, method, on_push, - on_push_ctx, format, ...) - if not request then + local err = check_active() + if err then return nil, err end - return request:wait_result(timeout) + -- alert worker to notify it of the queued outgoing data; + -- if the buffer wasn't empty, assume the worker was already alerted + if send_buf:size() == 0 then + worker_fiber:wakeup() + end + return internal.perform_request(timeout, requests, send_buf, buffer, + skip_header, method, on_push, + on_push_ctx, format, ...) end -- PROTOCOL STATE MACHINE (WORKER FIBER) -- -- 2.25.1
CC Sasha.
--
Regards, Kirill Yukhin
On 23 июл 14:07, Vladimir Davydov via Tarantool-patches wrote:
> https://github.com/tarantool/tarantool/tree/vdavydov/net-box-optimization
On Fri, Jul 23, 2021 at 02:07:21PM +0300, Vladimir Davydov via Tarantool-patches wrote: > This patch moves method_decoder table from Lua to C. This is a step > towards rewriting performance-critical parts of net.box in C. ... > +static int > +netbox_decode_method(struct lua_State *L) > +{ > + typedef void (*method_decoder_f)(struct lua_State *L, const char **data, > + const char *data_end, > + struct tuple_format *format); > + static method_decoder_f method_decoder[] = { > + [NETBOX_PING] = netbox_decode_nil, > + [NETBOX_CALL_16] = netbox_decode_select, ... > + [NETBOX_INJECT] = netbox_decode_table, > + }; > + enum netbox_method method = lua_tointeger(L, 1); > + assert(method < netbox_method_MAX); Should not it be runtime testing like if (method < 0 || method >= lengthof(method_decoder) || method_decoder[method] == NULL) { // Some Lua error thrown return luaT_error(); } or we've validated this data somewhere already?
On Tue, Jul 27, 2021 at 05:07:42PM +0300, Cyrill Gorcunov via Tarantool-patches wrote:
> On Fri, Jul 23, 2021 at 02:07:21PM +0300, Vladimir Davydov via Tarantool-patches wrote:
> > This patch moves method_decoder table from Lua to C. This is a step
> > towards rewriting performance-critical parts of net.box in C.
> ...
> > +static int
> > +netbox_decode_method(struct lua_State *L)
> > +{
> > + typedef void (*method_decoder_f)(struct lua_State *L, const char **data,
> > + const char *data_end,
> > + struct tuple_format *format);
> > + static method_decoder_f method_decoder[] = {
> > + [NETBOX_PING] = netbox_decode_nil,
> > + [NETBOX_CALL_16] = netbox_decode_select,
> ...
> > + [NETBOX_INJECT] = netbox_decode_table,
> > + };
> > + enum netbox_method method = lua_tointeger(L, 1);
> > + assert(method < netbox_method_MAX);
>
> Should not it be runtime testing like
>
> if (method < 0 || method >= lengthof(method_decoder) ||
> method_decoder[method] == NULL) {
> // Some Lua error thrown
> return luaT_error();
> }
>
> or we've validated this data somewhere already?
This is an internal function, which is only called by net_box.lua.
All these constants are internal, as well. So I don't think it's
worth validating input here or anywhere else in net_box.c.
Thanks for the patch! The commit is different from what I see on the branch. Please, try to keep the email thread up to date. Or do you prefer the github PRs now? Here and in other emails I will use the commits I see on the branch. > diff --git a/test/box/net.box_discard_console_request.result b/test/box/net.box_discard_console_request.result > new file mode 100644 > index 000000000..e8da50a2f > --- /dev/null > +++ b/test/box/net.box_discard_console_request.result For bug tests we now use name format `gh-####-your-description`. The other names different from this format are outdated and should not be used as an example. https://github.com/tarantool/tarantool/wiki/Code-review-procedure#testing The rest of the commit LGTM.
Thanks for the patch!
On 23.07.2021 13:07, Vladimir Davydov via Tarantool-patches wrote:
> Request context only stores tuple format or nil, which is used for
> decoding a response. Rename it appropriately.
The name ctx was chosen intentionally, because when you pass it to
method_decoder[request.method](...), you don't know how will it be
used and what is stores. It was internal for the request sender and
codec. Why do you need to change it?
Thanks for the patch!
> diff --git a/test/box/net.box_long-poll_input_gh-3400.test.lua b/test/box/net.box_long-poll_input_gh-3400.test.lua
> index bc9db1e69af2..99e821a128b6 100644
> --- a/test/box/net.box_long-poll_input_gh-3400.test.lua
> +++ b/test/box/net.box_long-poll_input_gh-3400.test.lua
> @@ -14,6 +15,11 @@ c:ping()
> -- new attempts to read any data - the connection is closed
> -- already.
> --
> -f = fiber.create(c._transport.perform_request, nil, nil, false, 'call_17', nil, nil, nil, 'long', {}) c._transport.perform_request(nil, nil, false, 'inject', nil, nil, nil, '\x80')
> +test_run:cmd("setopt delimiter ';'")
> +f = fiber.create(c._transport.perform_request, nil, nil, false,
> + net._method.call_17, nil, nil, nil, 'long', {})
> +c._transport.perform_request(nil, nil, false, net._method.inject,
> + nil, nil, nil, '\x80')
> +test_run:cmd("setopt delimiter ''");
JFYI, in test-run scripts you can now use \ in the end of the line
to wrap it instead of using 'setopt delimiter'. This usually looks
better for small multi-line constructions. Up to you whether to use
it.
Thanks for the patch! See 2 comments below. > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index 8952efb7bb39..49030aabea69 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -52,16 +52,34 @@ <...> > static inline size_t > -netbox_prepare_request(lua_State *L, struct mpstream *stream, uint32_t r_type) > +netbox_prepare_request(struct mpstream *stream, uint64_t sync, > + enum iproto_type type) > { > - struct ibuf *ibuf = (struct ibuf *) lua_topointer(L, 1); > - uint64_t sync = luaL_touint64(L, 2); > - > - mpstream_init(stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, > - luamp_error, L); > - > /* Remember initial size of ibuf (see netbox_encode_request()) */ > + struct ibuf *ibuf = (struct ibuf *) stream->ctx; 1. There is a rule now that in the new code after unary operators we do not put whitespaces. But here you do not need a cast. In C void * to any other pointer works implicitly. Here and in other new places. > @@ -108,16 +126,15 @@ netbox_encode_request(struct mpstream *stream, size_t initial_size) > mp_store_u32(fixheader, total_size - fixheader_size); > } > > -static int > -netbox_encode_ping(lua_State *L) > +static void > +netbox_encode_ping(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) > { > - if (lua_gettop(L) < 2) > - return luaL_error(L, "Usage: netbox.encode_ping(ibuf, sync)"); > - > + (void) idx; > struct mpstream stream; > - size_t svp = netbox_prepare_request(L, &stream, IPROTO_PING); > + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, > + luamp_error, L); > + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_PING); 2. mpstream_init and netbox_prepare_request in 100% cases go together. Maybe you could move the init into netbox_prepare_request? It even takes the stream pointer already. You would only need to pass L again.
Hi! Thanks for the patchset!
Here is a first part of my review. I will return later to continue.
Commits 02-05, 07-08, 10 are LGTM.
> Asynchronous calls don't show as much of an improvement as synchronous,
> because per each asynchronous call we still have to create a 'future'
> object in Lua. Still, the improvement is quite noticeable - 30% for
> REPLACE, 10% for UPDATE, 20% for SELECT, 25% for CALL.
I didn't reach the end of the patchset yet, but did you try to create
the futures as cdata objects? They could be allocated on mempool, their
GC pressure might be optimized by doing similar to luaT_tuple_encode_table_ref
optimization (there was found a way to make __gc and other C functions
cheaper when it comes to amount of GC objects in Lua).
The API would stay the same, they just would become C structs with
methods instead of Lua tables.
On Thu, Jul 29, 2021 at 12:49:17AM +0200, Vladislav Shpilevoy wrote: > Thanks for the patch! > > The commit is different from what I see on the branch. Please, > try to keep the email thread up to date. Or do you prefer the > github PRs now? I just added changelogs and references to tickets so didn't bother to resend. Sorry about that. The PR is for alyapunov@. FWIW I don't like PRs, I don't like emails, either, but I think there should be one and only one way to submit a patch for review... Some thoughts: - Divergence between a branch and patches sent via email may be dangerous, because one may forget to push a branch or resend emails. Ideally, if patches are sent via email, they should be applied with git-am. - git-am is not particularly user-friendly. It's easier to pull patches from a branch. This is a huge plus for PRs. - Unfortunately, PRs are not very convenient when it comes to reviewing patch series because of the nature of git, which was initially designed for reviewing patches by email. Since there's no versioning of the same commit (commit sha changes on each amend), PR comments to a patch cannot be linked to the same commit after amending the patch. AFAIU PRs require pushing fix-commits instead of amending and squashing before push, which simply doesn't work for huge patch sets. In general, emails are more flexible. - This is unrelated to reviews, but I hate seeing tons of comments in a GitHub issue mentioning that someone pushed his private branch mentioning the issue to the Tarantool repository. I think we should prohibit pushing branches to the main repository and make everyone fork it instead. > > Here and in other emails I will use the commits I see on the > branch. > > > diff --git a/test/box/net.box_discard_console_request.result b/test/box/net.box_discard_console_request.result > > new file mode 100644 > > index 000000000..e8da50a2f > > --- /dev/null > > +++ b/test/box/net.box_discard_console_request.result > > For bug tests we now use name format `gh-####-your-description`. > The other names different from this format are outdated and should > not be used as an example. > https://github.com/tarantool/tarantool/wiki/Code-review-procedure#testing All net.box tests have prefix net_box_ and add gh-#### as a suffix. So I renamed the test to net.box_discard_console_request_gh-6249. > > The rest of the commit LGTM. Thanks. Pushed to the master branch, 1.10, 2.7, and 2.8.
Pushed to master, 1.10, 2.7, 2.8.
On Thu, Jul 29, 2021 at 12:49:39AM +0200, Vladislav Shpilevoy wrote: > On 23.07.2021 13:07, Vladimir Davydov via Tarantool-patches wrote: > > Request context only stores tuple format or nil, which is used for > > decoding a response. Rename it appropriately. > > The name ctx was chosen intentionally, because when you pass it to > method_decoder[request.method](...), you don't know how will it be > used and what is stores. It was internal for the request sender and > codec. Why do you need to change it? For one thing, it's always either nil or format. Naming it ctx is confusing. The reason I'm doing this in the scope of this series is that it is stored in the request class. Lua isn't a strictly typed language so we can store whatever we want there, but in C we need to know the type to define the request struct, see netbox_request::format here: https://github.com/tarantool/tarantool/blob/73cab8402f7e098c8e908af9a2b72695c754c472/src/box/lua/net_box.c#L99 Turning ctx to format solves this problem.
On Thu, Jul 29, 2021 at 12:50:05AM +0200, Vladislav Shpilevoy wrote:
> > diff --git a/test/box/net.box_long-poll_input_gh-3400.test.lua b/test/box/net.box_long-poll_input_gh-3400.test.lua
> > index bc9db1e69af2..99e821a128b6 100644
> > --- a/test/box/net.box_long-poll_input_gh-3400.test.lua
> > +++ b/test/box/net.box_long-poll_input_gh-3400.test.lua
> > @@ -14,6 +15,11 @@ c:ping()
> > -- new attempts to read any data - the connection is closed
> > -- already.
> > --
> > -f = fiber.create(c._transport.perform_request, nil, nil, false, 'call_17', nil, nil, nil, 'long', {}) c._transport.perform_request(nil, nil, false, 'inject', nil, nil, nil, '\x80')
> > +test_run:cmd("setopt delimiter ';'")
> > +f = fiber.create(c._transport.perform_request, nil, nil, false,
> > + net._method.call_17, nil, nil, nil, 'long', {})
> > +c._transport.perform_request(nil, nil, false, net._method.inject,
> > + nil, nil, nil, '\x80')
> > +test_run:cmd("setopt delimiter ''");
>
> JFYI, in test-run scripts you can now use \ in the end of the line
> to wrap it instead of using 'setopt delimiter'. This usually looks
> better for small multi-line constructions. Up to you whether to use
> it.
Looks better with backslashes. Fixed, thanks.
On Thu, Jul 29, 2021 at 12:51:18AM +0200, Vladislav Shpilevoy wrote: > Hi! Thanks for the patchset! > > Here is a first part of my review. I will return later to continue. > > Commits 02-05, 07-08, 10 are LGTM. Pushed to master. > > > Asynchronous calls don't show as much of an improvement as synchronous, > > because per each asynchronous call we still have to create a 'future' > > object in Lua. Still, the improvement is quite noticeable - 30% for > > REPLACE, 10% for UPDATE, 20% for SELECT, 25% for CALL. > > I didn't reach the end of the patchset yet, but did you try to create > the futures as cdata objects? They could be allocated on mempool, their > GC pressure might be optimized by doing similar to luaT_tuple_encode_table_ref > optimization (there was found a way to make __gc and other C functions > cheaper when it comes to amount of GC objects in Lua). > > The API would stay the same, they just would become C structs with > methods instead of Lua tables. Good call. Going to to try that. Thanks.
On Thu, Jul 29, 2021 at 12:51:01AM +0200, Vladislav Shpilevoy wrote: > > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > > index 8952efb7bb39..49030aabea69 100644 > > --- a/src/box/lua/net_box.c > > +++ b/src/box/lua/net_box.c > > @@ -52,16 +52,34 @@ > > <...> > > > static inline size_t > > -netbox_prepare_request(lua_State *L, struct mpstream *stream, uint32_t r_type) > > +netbox_prepare_request(struct mpstream *stream, uint64_t sync, > > + enum iproto_type type) > > { > > - struct ibuf *ibuf = (struct ibuf *) lua_topointer(L, 1); > > - uint64_t sync = luaL_touint64(L, 2); > > - > > - mpstream_init(stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, > > - luamp_error, L); > > - > > /* Remember initial size of ibuf (see netbox_encode_request()) */ > > + struct ibuf *ibuf = (struct ibuf *) stream->ctx; > > 1. There is a rule now that in the new code after unary operators we > do not put whitespaces. But here you do not need a cast. In C void * to > any other pointer works implicitly. > > Here and in other new places. Removed this useless conversion here and a space after a cast operator in all patches. I wish we had a tool that would do that automatically... > > @@ -108,16 +126,15 @@ netbox_encode_request(struct mpstream *stream, size_t initial_size) > > mp_store_u32(fixheader, total_size - fixheader_size); > > } > > > > -static int > > -netbox_encode_ping(lua_State *L) > > +static void > > +netbox_encode_ping(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) > > { > > - if (lua_gettop(L) < 2) > > - return luaL_error(L, "Usage: netbox.encode_ping(ibuf, sync)"); > > - > > + (void) idx; > > struct mpstream stream; > > - size_t svp = netbox_prepare_request(L, &stream, IPROTO_PING); > > + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, > > + luamp_error, L); > > + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_PING); > > 2. mpstream_init and netbox_prepare_request in 100% cases go together. > Maybe you could move the init into netbox_prepare_request? It even takes > the stream pointer already. You would only need to pass L again. Once all patches are applied, netbox_encode_auth will use a different error handler, see https://github.com/tarantool/tarantool/blob/bf2f94e9637b7028f6974cd7486e57983612c95e/src/box/lua/net_box.c#L415 But I agree that we have some code duplication because of this. We can initialize mpstream at the top level (netbox_encode_method) to eliminate it. I moved mpstream_init back to netbox_prepare_request in this patch and added a follow-up patch that moves mpstream_init to netbox_encode_method. The updated patch is below. The follow-up patch will be sent separately in reply to this email. -- From a3a201e14439ce5a9037826256755c556c3575d0 Mon Sep 17 00:00:00 2001 From: Vladimir Davydov <vdavydov@tarantool.org> Date: Wed, 14 Jul 2021 18:36:27 +0300 Subject: [PATCH] net.box: rewrite request encoder in C This patch moves method_encoder table from Lua to C. This is a step towards rewriting performance-critical parts of net.box in C. Part of #6241 diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 8952efb7bb39..3520f56cac62 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -52,12 +52,32 @@ #define cfg luaL_msgpack_default +enum netbox_method { + NETBOX_PING = 0, + NETBOX_CALL_16 = 1, + NETBOX_CALL_17 = 2, + NETBOX_EVAL = 3, + NETBOX_INSERT = 4, + NETBOX_REPLACE = 5, + NETBOX_DELETE = 6, + NETBOX_UPDATE = 7, + NETBOX_UPSERT = 8, + NETBOX_SELECT = 9, + NETBOX_EXECUTE = 10, + NETBOX_PREPARE = 11, + NETBOX_UNPREPARE = 12, + NETBOX_GET = 13, + NETBOX_MIN = 14, + NETBOX_MAX = 15, + NETBOX_COUNT = 16, + NETBOX_INJECT = 17, + netbox_method_MAX +}; + static inline size_t -netbox_prepare_request(lua_State *L, struct mpstream *stream, uint32_t r_type) +netbox_prepare_request(struct lua_State *L, struct mpstream *stream, + struct ibuf *ibuf, uint64_t sync, enum iproto_type type) { - struct ibuf *ibuf = (struct ibuf *) lua_topointer(L, 1); - uint64_t sync = luaL_touint64(L, 2); - mpstream_init(stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); @@ -76,7 +96,7 @@ netbox_prepare_request(lua_State *L, struct mpstream *stream, uint32_t r_type) mpstream_encode_uint(stream, sync); mpstream_encode_uint(stream, IPROTO_REQUEST_TYPE); - mpstream_encode_uint(stream, r_type); + mpstream_encode_uint(stream, type); /* Caller should remember how many bytes was used in ibuf */ return used; @@ -108,16 +128,14 @@ netbox_encode_request(struct mpstream *stream, size_t initial_size) mp_store_u32(fixheader, total_size - fixheader_size); } -static int -netbox_encode_ping(lua_State *L) +static void +netbox_encode_ping(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 2) - return luaL_error(L, "Usage: netbox.encode_ping(ibuf, sync)"); - + (void)idx; struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_PING); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, + IPROTO_PING); netbox_encode_request(&stream, svp); - return 0; } static int @@ -127,9 +145,12 @@ netbox_encode_auth(lua_State *L) return luaL_error(L, "Usage: netbox.encode_update(ibuf, sync, " "user, password, greeting)"); } + struct ibuf *ibuf = (struct ibuf *)lua_topointer(L, 1); + uint64_t sync = luaL_touint64(L, 2); struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_AUTH); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, + IPROTO_AUTH); size_t user_len; const char *user = lua_tolstring(L, 3, &user_len); @@ -157,91 +178,79 @@ netbox_encode_auth(lua_State *L) return 0; } -static int -netbox_encode_call_impl(lua_State *L, enum iproto_type type) +static void +netbox_encode_call_impl(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync, + enum iproto_type type) { - if (lua_gettop(L) < 4) { - return luaL_error(L, "Usage: netbox.encode_call(ibuf, sync, " - "function_name, args)"); - } - + /* Lua stack at idx: function_name, args */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, type); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, type); mpstream_encode_map(&stream, 2); /* encode proc name */ size_t name_len; - const char *name = lua_tolstring(L, 3, &name_len); + const char *name = lua_tolstring(L, idx, &name_len); mpstream_encode_uint(&stream, IPROTO_FUNCTION_NAME); mpstream_encode_strn(&stream, name, name_len); /* encode args */ mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, 4); + luamp_encode_tuple(L, cfg, &stream, idx + 1); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_call_16(lua_State *L) +static void +netbox_encode_call_16(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - return netbox_encode_call_impl(L, IPROTO_CALL_16); + netbox_encode_call_impl(L, idx, ibuf, sync, IPROTO_CALL_16); } -static int -netbox_encode_call(lua_State *L) +static void +netbox_encode_call(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - return netbox_encode_call_impl(L, IPROTO_CALL); + netbox_encode_call_impl(L, idx, ibuf, sync, IPROTO_CALL); } -static int -netbox_encode_eval(lua_State *L) +static void +netbox_encode_eval(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 4) { - return luaL_error(L, "Usage: netbox.encode_eval(ibuf, sync, " - "expr, args)"); - } - + /* Lua stack at idx: expr, args */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_EVAL); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, + IPROTO_EVAL); mpstream_encode_map(&stream, 2); /* encode expr */ size_t expr_len; - const char *expr = lua_tolstring(L, 3, &expr_len); + const char *expr = lua_tolstring(L, idx, &expr_len); mpstream_encode_uint(&stream, IPROTO_EXPR); mpstream_encode_strn(&stream, expr, expr_len); /* encode args */ mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, 4); + luamp_encode_tuple(L, cfg, &stream, idx + 1); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_select(lua_State *L) +static void +netbox_encode_select(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 8) { - return luaL_error(L, "Usage netbox.encode_select(ibuf, sync, " - "space_id, index_id, iterator, offset, " - "limit, key)"); - } - + /* Lua stack at idx: space_id, index_id, iterator, offset, limit, key */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_SELECT); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, + IPROTO_SELECT); mpstream_encode_map(&stream, 6); - uint32_t space_id = lua_tonumber(L, 3); - uint32_t index_id = lua_tonumber(L, 4); - int iterator = lua_tointeger(L, 5); - uint32_t offset = lua_tonumber(L, 6); - uint32_t limit = lua_tonumber(L, 7); + uint32_t space_id = lua_tonumber(L, idx); + uint32_t index_id = lua_tonumber(L, idx + 1); + int iterator = lua_tointeger(L, idx + 2); + uint32_t offset = lua_tonumber(L, idx + 3); + uint32_t limit = lua_tonumber(L, idx + 4); /* encode space_id */ mpstream_encode_uint(&stream, IPROTO_SPACE_ID); @@ -265,100 +274,89 @@ netbox_encode_select(lua_State *L) /* encode key */ mpstream_encode_uint(&stream, IPROTO_KEY); - luamp_convert_key(L, cfg, &stream, 8); + luamp_convert_key(L, cfg, &stream, idx + 5); netbox_encode_request(&stream, svp); - return 0; } -static inline int -netbox_encode_insert_or_replace(lua_State *L, uint32_t reqtype) +static void +netbox_encode_insert_or_replace(lua_State *L, int idx, struct ibuf *ibuf, + uint64_t sync, enum iproto_type type) { - if (lua_gettop(L) < 4) { - return luaL_error(L, "Usage: netbox.encode_insert(ibuf, sync, " - "space_id, tuple)"); - } + /* Lua stack at idx: space_id, tuple */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, reqtype); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, type); mpstream_encode_map(&stream, 2); /* encode space_id */ - uint32_t space_id = lua_tonumber(L, 3); + uint32_t space_id = lua_tonumber(L, idx); mpstream_encode_uint(&stream, IPROTO_SPACE_ID); mpstream_encode_uint(&stream, space_id); /* encode args */ mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, 4); + luamp_encode_tuple(L, cfg, &stream, idx + 1); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_insert(lua_State *L) +static void +netbox_encode_insert(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - return netbox_encode_insert_or_replace(L, IPROTO_INSERT); + netbox_encode_insert_or_replace(L, idx, ibuf, sync, IPROTO_INSERT); } -static int -netbox_encode_replace(lua_State *L) +static void +netbox_encode_replace(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - return netbox_encode_insert_or_replace(L, IPROTO_REPLACE); + netbox_encode_insert_or_replace(L, idx, ibuf, sync, IPROTO_REPLACE); } -static int -netbox_encode_delete(lua_State *L) +static void +netbox_encode_delete(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 5) { - return luaL_error(L, "Usage: netbox.encode_delete(ibuf, sync, " - "space_id, index_id, key)"); - } - + /* Lua stack at idx: space_id, index_id, key */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_DELETE); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, + IPROTO_DELETE); mpstream_encode_map(&stream, 3); /* encode space_id */ - uint32_t space_id = lua_tonumber(L, 3); + uint32_t space_id = lua_tonumber(L, idx); mpstream_encode_uint(&stream, IPROTO_SPACE_ID); mpstream_encode_uint(&stream, space_id); /* encode space_id */ - uint32_t index_id = lua_tonumber(L, 4); + uint32_t index_id = lua_tonumber(L, idx + 1); mpstream_encode_uint(&stream, IPROTO_INDEX_ID); mpstream_encode_uint(&stream, index_id); /* encode key */ mpstream_encode_uint(&stream, IPROTO_KEY); - luamp_convert_key(L, cfg, &stream, 5); + luamp_convert_key(L, cfg, &stream, idx + 2); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_update(lua_State *L) +static void +netbox_encode_update(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 6) { - return luaL_error(L, "Usage: netbox.encode_update(ibuf, sync, " - "space_id, index_id, key, ops)"); - } - + /* Lua stack at idx: space_id, index_id, key, ops */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_UPDATE); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, + IPROTO_UPDATE); mpstream_encode_map(&stream, 5); /* encode space_id */ - uint32_t space_id = lua_tonumber(L, 3); + uint32_t space_id = lua_tonumber(L, idx); mpstream_encode_uint(&stream, IPROTO_SPACE_ID); mpstream_encode_uint(&stream, space_id); /* encode index_id */ - uint32_t index_id = lua_tonumber(L, 4); + uint32_t index_id = lua_tonumber(L, idx + 1); mpstream_encode_uint(&stream, IPROTO_INDEX_ID); mpstream_encode_uint(&stream, index_id); @@ -368,31 +366,27 @@ netbox_encode_update(lua_State *L) /* encode key */ mpstream_encode_uint(&stream, IPROTO_KEY); - luamp_convert_key(L, cfg, &stream, 5); + luamp_convert_key(L, cfg, &stream, idx + 2); /* encode ops */ mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, 6); + luamp_encode_tuple(L, cfg, &stream, idx + 3); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_upsert(lua_State *L) +static void +netbox_encode_upsert(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) != 5) { - return luaL_error(L, "Usage: netbox.encode_upsert(ibuf, sync, " - "space_id, tuple, ops)"); - } - + /* Lua stack at idx: space_id, tuple, ops */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_UPSERT); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, + IPROTO_UPSERT); mpstream_encode_map(&stream, 4); /* encode space_id */ - uint32_t space_id = lua_tonumber(L, 3); + uint32_t space_id = lua_tonumber(L, idx); mpstream_encode_uint(&stream, IPROTO_SPACE_ID); mpstream_encode_uint(&stream, space_id); @@ -402,14 +396,13 @@ netbox_encode_upsert(lua_State *L) /* encode tuple */ mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, 4); + luamp_encode_tuple(L, cfg, &stream, idx + 1); /* encode ops */ mpstream_encode_uint(&stream, IPROTO_OPS); - luamp_encode_tuple(L, cfg, &stream, 5); + luamp_encode_tuple(L, cfg, &stream, idx + 2); netbox_encode_request(&stream, svp); - return 0; } static int @@ -556,61 +549,121 @@ handle_error: return 2; } -static int -netbox_encode_execute(lua_State *L) +static void +netbox_encode_execute(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 5) - return luaL_error(L, "Usage: netbox.encode_execute(ibuf, "\ - "sync, query, parameters, options)"); + /* Lua stack at idx: query, parameters, options */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_EXECUTE); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, + IPROTO_EXECUTE); mpstream_encode_map(&stream, 3); - if (lua_type(L, 3) == LUA_TNUMBER) { - uint32_t query_id = lua_tointeger(L, 3); + if (lua_type(L, idx) == LUA_TNUMBER) { + uint32_t query_id = lua_tointeger(L, idx); mpstream_encode_uint(&stream, IPROTO_STMT_ID); mpstream_encode_uint(&stream, query_id); } else { size_t len; - const char *query = lua_tolstring(L, 3, &len); + const char *query = lua_tolstring(L, idx, &len); mpstream_encode_uint(&stream, IPROTO_SQL_TEXT); mpstream_encode_strn(&stream, query, len); } mpstream_encode_uint(&stream, IPROTO_SQL_BIND); - luamp_encode_tuple(L, cfg, &stream, 4); + luamp_encode_tuple(L, cfg, &stream, idx + 1); mpstream_encode_uint(&stream, IPROTO_OPTIONS); - luamp_encode_tuple(L, cfg, &stream, 5); + luamp_encode_tuple(L, cfg, &stream, idx + 2); netbox_encode_request(&stream, svp); - return 0; } -static int -netbox_encode_prepare(lua_State *L) +static void +netbox_encode_prepare(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) { - if (lua_gettop(L) < 3) - return luaL_error(L, "Usage: netbox.encode_prepare(ibuf, "\ - "sync, query)"); + /* Lua stack at idx: query */ struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, IPROTO_PREPARE); + size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, + IPROTO_PREPARE); mpstream_encode_map(&stream, 1); - if (lua_type(L, 3) == LUA_TNUMBER) { - uint32_t query_id = lua_tointeger(L, 3); + if (lua_type(L, idx) == LUA_TNUMBER) { + uint32_t query_id = lua_tointeger(L, idx); mpstream_encode_uint(&stream, IPROTO_STMT_ID); mpstream_encode_uint(&stream, query_id); } else { size_t len; - const char *query = lua_tolstring(L, 3, &len); + const char *query = lua_tolstring(L, idx, &len); mpstream_encode_uint(&stream, IPROTO_SQL_TEXT); mpstream_encode_strn(&stream, query, len); }; netbox_encode_request(&stream, svp); +} + +static void +netbox_encode_unprepare(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +{ + /* Lua stack at idx: query, parameters, options */ + netbox_encode_prepare(L, idx, ibuf, sync); +} + +static void +netbox_encode_inject(struct lua_State *L, int idx, struct ibuf *ibuf, + uint64_t sync) +{ + /* Lua stack at idx: bytes */ + (void)sync; + size_t len; + const char *data = lua_tolstring(L, idx, &len); + void *wpos = ibuf_alloc(ibuf, len); + if (wpos == NULL) + luaL_error(L, "out of memory"); + memcpy(wpos, data, len); +} + +/* + * Encodes a request for the specified method. + * + * Takes three mandatory arguments: + * - method: a value from the netbox_method enumeration + * - ibuf: buffer to write the result to + * - sync: value of the IPROTO_SYNC key + * + * Other arguments are method-specific. + */ +static int +netbox_encode_method(struct lua_State *L) +{ + typedef void (*method_encoder_f)(struct lua_State *L, int idx, + struct ibuf *ibuf, uint64_t sync); + static method_encoder_f method_encoder[] = { + [NETBOX_PING] = netbox_encode_ping, + [NETBOX_CALL_16] = netbox_encode_call_16, + [NETBOX_CALL_17] = netbox_encode_call, + [NETBOX_EVAL] = netbox_encode_eval, + [NETBOX_INSERT] = netbox_encode_insert, + [NETBOX_REPLACE] = netbox_encode_replace, + [NETBOX_DELETE] = netbox_encode_delete, + [NETBOX_UPDATE] = netbox_encode_update, + [NETBOX_UPSERT] = netbox_encode_upsert, + [NETBOX_SELECT] = netbox_encode_select, + [NETBOX_EXECUTE] = netbox_encode_execute, + [NETBOX_PREPARE] = netbox_encode_prepare, + [NETBOX_UNPREPARE] = netbox_encode_unprepare, + [NETBOX_GET] = netbox_encode_select, + [NETBOX_MIN] = netbox_encode_select, + [NETBOX_MAX] = netbox_encode_select, + [NETBOX_COUNT] = netbox_encode_call, + [NETBOX_INJECT] = netbox_encode_inject, + }; + enum netbox_method method = lua_tointeger(L, 1); + assert(method < netbox_method_MAX); + struct ibuf *ibuf = (struct ibuf *)lua_topointer(L, 2); + uint64_t sync = luaL_touint64(L, 3); + method_encoder[method](L, 4, ibuf, sync); return 0; } @@ -885,19 +938,8 @@ int luaopen_net_box(struct lua_State *L) { static const luaL_Reg net_box_lib[] = { - { "encode_ping", netbox_encode_ping }, - { "encode_call_16", netbox_encode_call_16 }, - { "encode_call", netbox_encode_call }, - { "encode_eval", netbox_encode_eval }, - { "encode_select", netbox_encode_select }, - { "encode_insert", netbox_encode_insert }, - { "encode_replace", netbox_encode_replace }, - { "encode_delete", netbox_encode_delete }, - { "encode_update", netbox_encode_update }, - { "encode_upsert", netbox_encode_upsert }, - { "encode_execute", netbox_encode_execute}, - { "encode_prepare", netbox_encode_prepare}, { "encode_auth", netbox_encode_auth }, + { "encode_method", netbox_encode_method }, { "decode_greeting",netbox_decode_greeting }, { "communicate", netbox_communicate }, { "decode_select", netbox_decode_select }, diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index a98bbfa00465..d400f92358aa 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -24,7 +24,7 @@ local check_primary_index = box.internal.check_primary_index local communicate = internal.communicate local encode_auth = internal.encode_auth -local encode_select = internal.encode_select +local encode_method = internal.encode_method local decode_greeting = internal.decode_greeting local TIMEOUT_INFINITY = 500 * 365 * 86400 @@ -111,31 +111,6 @@ local function version_at_least(peer_version_id, major, minor, patch) return peer_version_id >= version_id(major, minor, patch) end -local method_encoder = { - [M_PING] = internal.encode_ping, - [M_CALL_16] = internal.encode_call_16, - [M_CALL_17] = internal.encode_call, - [M_EVAL] = internal.encode_eval, - [M_INSERT] = internal.encode_insert, - [M_REPLACE] = internal.encode_replace, - [M_DELETE] = internal.encode_delete, - [M_UPDATE] = internal.encode_update, - [M_UPSERT] = internal.encode_upsert, - [M_SELECT] = internal.encode_select, - [M_EXECUTE] = internal.encode_execute, - [M_PREPARE] = internal.encode_prepare, - [M_UNPREPARE] = internal.encode_prepare, - [M_GET] = internal.encode_select, - [M_MIN] = internal.encode_select, - [M_MAX] = internal.encode_select, - [M_COUNT] = internal.encode_call, - [M_INJECT] = function(buf, id, bytes) -- luacheck: no unused args - local ptr = buf:reserve(#bytes) - ffi.copy(ptr, bytes, #bytes) - buf.wpos = ptr + #bytes - end -} - local method_decoder = { [M_PING] = decode_nil, [M_CALL_16] = internal.decode_select, @@ -557,7 +532,7 @@ local function create_transport(host, port, user, password, callback, worker_fiber:wakeup() end local id = next_request_id - method_encoder[method](send_buf, id, ...) + encode_method(method, send_buf, id, ...) next_request_id = next_id(id) -- Request in most cases has maximum 10 members: -- method, buffer, skip_header, id, cond, errno, response, @@ -770,7 +745,7 @@ local function create_transport(host, port, user, password, callback, log.warn("Netbox text protocol support is deprecated since 1.10, ".. "please use require('console').connect() instead") local setup_delimiter = 'require("console").delimiter("$EOF$")\n' - method_encoder[M_INJECT](send_buf, nil, setup_delimiter) + encode_method(M_INJECT, send_buf, nil, setup_delimiter) local err, response = send_and_recv_console() if err then return error_sm(err, response) @@ -830,14 +805,16 @@ local function create_transport(host, port, user, password, callback, local select3_id local response = {} -- fetch everything from space _vspace, 2 = ITER_ALL - encode_select(send_buf, select1_id, VSPACE_ID, 0, 2, 0, 0xFFFFFFFF, nil) + encode_method(M_SELECT, send_buf, select1_id, VSPACE_ID, 0, 2, 0, + 0xFFFFFFFF, nil) -- fetch everything from space _vindex, 2 = ITER_ALL - encode_select(send_buf, select2_id, VINDEX_ID, 0, 2, 0, 0xFFFFFFFF, nil) + encode_method(M_SELECT, send_buf, select2_id, VINDEX_ID, 0, 2, 0, + 0xFFFFFFFF, nil) -- fetch everything from space _vcollation, 2 = ITER_ALL if peer_has_vcollation then select3_id = new_request_id() - encode_select(send_buf, select3_id, VCOLLATION_ID, 0, 2, 0, - 0xFFFFFFFF, nil) + encode_method(M_SELECT, send_buf, select3_id, VCOLLATION_ID, + 0, 2, 0, 0xFFFFFFFF, nil) end schema_version = nil -- any schema_version will do provided that
On Thu, Jul 29, 2021 at 05:08:30PM +0300, Vladimir Davydov wrote: > On Thu, Jul 29, 2021 at 12:51:01AM +0200, Vladislav Shpilevoy wrote: > > > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > > > index 8952efb7bb39..49030aabea69 100644 > > > --- a/src/box/lua/net_box.c > > > +++ b/src/box/lua/net_box.c > > > @@ -108,16 +126,15 @@ netbox_encode_request(struct mpstream *stream, size_t initial_size) > > > mp_store_u32(fixheader, total_size - fixheader_size); > > > } > > > > > > -static int > > > -netbox_encode_ping(lua_State *L) > > > +static void > > > +netbox_encode_ping(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) > > > { > > > - if (lua_gettop(L) < 2) > > > - return luaL_error(L, "Usage: netbox.encode_ping(ibuf, sync)"); > > > - > > > + (void) idx; > > > struct mpstream stream; > > > - size_t svp = netbox_prepare_request(L, &stream, IPROTO_PING); > > > + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, > > > + luamp_error, L); > > > + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_PING); > > > > 2. mpstream_init and netbox_prepare_request in 100% cases go together. > > Maybe you could move the init into netbox_prepare_request? It even takes > > the stream pointer already. You would only need to pass L again. > > Once all patches are applied, netbox_encode_auth will use a different > error handler, see > > https://github.com/tarantool/tarantool/blob/bf2f94e9637b7028f6974cd7486e57983612c95e/src/box/lua/net_box.c#L415 > > But I agree that we have some code duplication because of this. We can > initialize mpstream at the top level (netbox_encode_method) to eliminate > it. > > I moved mpstream_init back to netbox_prepare_request in this patch and > added a follow-up patch that moves mpstream_init to > netbox_encode_method. The updated patch is below. The follow-up patch > will be sent separately in reply to this email. Here goes: -- From 933a3ca44f9c44687f58c5f8da7ec8ab2d79b5b9 Mon Sep 17 00:00:00 2001 From: Vladimir Davydov <vdavydov@tarantool.org> Date: Thu, 29 Jul 2021 16:49:30 +0300 Subject: [PATCH] net.box: create mpstream in netbox_encode_method Currently, an mpstream is initialized with the Lua error handler in netbox_prepare_request, which is used by all encoding methods, including netbox_encode_auth. The latter will be moved to C, along with iproto request handlers, where we will have to use a different error handler. Let's create an mpstream in netbox_encode_method and netbox_encode_auth instead. For now, they do the same, but once we move the code to C they will use different error handlers. Part of #6241 diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 3520f56cac62..0bce0b2ceb32 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -75,13 +75,11 @@ enum netbox_method { }; static inline size_t -netbox_prepare_request(struct lua_State *L, struct mpstream *stream, - struct ibuf *ibuf, uint64_t sync, enum iproto_type type) +netbox_prepare_request(struct mpstream *stream, uint64_t sync, + enum iproto_type type) { - mpstream_init(stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, - luamp_error, L); - /* Remember initial size of ibuf (see netbox_encode_request()) */ + struct ibuf *ibuf = stream->ctx; size_t used = ibuf_used(ibuf); /* Reserve and skip space for fixheader */ @@ -129,13 +127,13 @@ netbox_encode_request(struct mpstream *stream, size_t initial_size) } static void -netbox_encode_ping(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_ping(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { + (void)L; (void)idx; - struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, - IPROTO_PING); - netbox_encode_request(&stream, svp); + size_t svp = netbox_prepare_request(stream, sync, IPROTO_PING); + netbox_encode_request(stream, svp); } static int @@ -149,8 +147,9 @@ netbox_encode_auth(lua_State *L) uint64_t sync = luaL_touint64(L, 2); struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, - IPROTO_AUTH); + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + size_t svp = netbox_prepare_request(&stream, sync, IPROTO_AUTH); size_t user_len; const char *user = lua_tolstring(L, 3, &user_len); @@ -179,72 +178,71 @@ netbox_encode_auth(lua_State *L) } static void -netbox_encode_call_impl(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync, - enum iproto_type type) +netbox_encode_call_impl(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync, enum iproto_type type) { /* Lua stack at idx: function_name, args */ - struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, type); + size_t svp = netbox_prepare_request(stream, sync, type); - mpstream_encode_map(&stream, 2); + mpstream_encode_map(stream, 2); /* encode proc name */ size_t name_len; const char *name = lua_tolstring(L, idx, &name_len); - mpstream_encode_uint(&stream, IPROTO_FUNCTION_NAME); - mpstream_encode_strn(&stream, name, name_len); + mpstream_encode_uint(stream, IPROTO_FUNCTION_NAME); + mpstream_encode_strn(stream, name, name_len); /* encode args */ - mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, idx + 1); + mpstream_encode_uint(stream, IPROTO_TUPLE); + luamp_encode_tuple(L, cfg, stream, idx + 1); - netbox_encode_request(&stream, svp); + netbox_encode_request(stream, svp); } static void -netbox_encode_call_16(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_call_16(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { - netbox_encode_call_impl(L, idx, ibuf, sync, IPROTO_CALL_16); + netbox_encode_call_impl(L, idx, stream, sync, IPROTO_CALL_16); } static void -netbox_encode_call(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_call(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { - netbox_encode_call_impl(L, idx, ibuf, sync, IPROTO_CALL); + netbox_encode_call_impl(L, idx, stream, sync, IPROTO_CALL); } static void -netbox_encode_eval(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_eval(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { /* Lua stack at idx: expr, args */ - struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, - IPROTO_EVAL); + size_t svp = netbox_prepare_request(stream, sync, IPROTO_EVAL); - mpstream_encode_map(&stream, 2); + mpstream_encode_map(stream, 2); /* encode expr */ size_t expr_len; const char *expr = lua_tolstring(L, idx, &expr_len); - mpstream_encode_uint(&stream, IPROTO_EXPR); - mpstream_encode_strn(&stream, expr, expr_len); + mpstream_encode_uint(stream, IPROTO_EXPR); + mpstream_encode_strn(stream, expr, expr_len); /* encode args */ - mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, idx + 1); + mpstream_encode_uint(stream, IPROTO_TUPLE); + luamp_encode_tuple(L, cfg, stream, idx + 1); - netbox_encode_request(&stream, svp); + netbox_encode_request(stream, svp); } static void -netbox_encode_select(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_select(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { /* Lua stack at idx: space_id, index_id, iterator, offset, limit, key */ - struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, - IPROTO_SELECT); + size_t svp = netbox_prepare_request(stream, sync, IPROTO_SELECT); - mpstream_encode_map(&stream, 6); + mpstream_encode_map(stream, 6); uint32_t space_id = lua_tonumber(L, idx); uint32_t index_id = lua_tonumber(L, idx + 1); @@ -253,156 +251,154 @@ netbox_encode_select(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) uint32_t limit = lua_tonumber(L, idx + 4); /* encode space_id */ - mpstream_encode_uint(&stream, IPROTO_SPACE_ID); - mpstream_encode_uint(&stream, space_id); + mpstream_encode_uint(stream, IPROTO_SPACE_ID); + mpstream_encode_uint(stream, space_id); /* encode index_id */ - mpstream_encode_uint(&stream, IPROTO_INDEX_ID); - mpstream_encode_uint(&stream, index_id); + mpstream_encode_uint(stream, IPROTO_INDEX_ID); + mpstream_encode_uint(stream, index_id); /* encode iterator */ - mpstream_encode_uint(&stream, IPROTO_ITERATOR); - mpstream_encode_uint(&stream, iterator); + mpstream_encode_uint(stream, IPROTO_ITERATOR); + mpstream_encode_uint(stream, iterator); /* encode offset */ - mpstream_encode_uint(&stream, IPROTO_OFFSET); - mpstream_encode_uint(&stream, offset); + mpstream_encode_uint(stream, IPROTO_OFFSET); + mpstream_encode_uint(stream, offset); /* encode limit */ - mpstream_encode_uint(&stream, IPROTO_LIMIT); - mpstream_encode_uint(&stream, limit); + mpstream_encode_uint(stream, IPROTO_LIMIT); + mpstream_encode_uint(stream, limit); /* encode key */ - mpstream_encode_uint(&stream, IPROTO_KEY); - luamp_convert_key(L, cfg, &stream, idx + 5); + mpstream_encode_uint(stream, IPROTO_KEY); + luamp_convert_key(L, cfg, stream, idx + 5); - netbox_encode_request(&stream, svp); + netbox_encode_request(stream, svp); } static void -netbox_encode_insert_or_replace(lua_State *L, int idx, struct ibuf *ibuf, +netbox_encode_insert_or_replace(lua_State *L, int idx, struct mpstream *stream, uint64_t sync, enum iproto_type type) { /* Lua stack at idx: space_id, tuple */ - struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, type); + size_t svp = netbox_prepare_request(stream, sync, type); - mpstream_encode_map(&stream, 2); + mpstream_encode_map(stream, 2); /* encode space_id */ uint32_t space_id = lua_tonumber(L, idx); - mpstream_encode_uint(&stream, IPROTO_SPACE_ID); - mpstream_encode_uint(&stream, space_id); + mpstream_encode_uint(stream, IPROTO_SPACE_ID); + mpstream_encode_uint(stream, space_id); /* encode args */ - mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, idx + 1); + mpstream_encode_uint(stream, IPROTO_TUPLE); + luamp_encode_tuple(L, cfg, stream, idx + 1); - netbox_encode_request(&stream, svp); + netbox_encode_request(stream, svp); } static void -netbox_encode_insert(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_insert(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { - netbox_encode_insert_or_replace(L, idx, ibuf, sync, IPROTO_INSERT); + netbox_encode_insert_or_replace(L, idx, stream, sync, IPROTO_INSERT); } static void -netbox_encode_replace(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_replace(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { - netbox_encode_insert_or_replace(L, idx, ibuf, sync, IPROTO_REPLACE); + netbox_encode_insert_or_replace(L, idx, stream, sync, IPROTO_REPLACE); } static void -netbox_encode_delete(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_delete(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { /* Lua stack at idx: space_id, index_id, key */ - struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, - IPROTO_DELETE); + size_t svp = netbox_prepare_request(stream, sync, IPROTO_DELETE); - mpstream_encode_map(&stream, 3); + mpstream_encode_map(stream, 3); /* encode space_id */ uint32_t space_id = lua_tonumber(L, idx); - mpstream_encode_uint(&stream, IPROTO_SPACE_ID); - mpstream_encode_uint(&stream, space_id); + mpstream_encode_uint(stream, IPROTO_SPACE_ID); + mpstream_encode_uint(stream, space_id); /* encode space_id */ uint32_t index_id = lua_tonumber(L, idx + 1); - mpstream_encode_uint(&stream, IPROTO_INDEX_ID); - mpstream_encode_uint(&stream, index_id); + mpstream_encode_uint(stream, IPROTO_INDEX_ID); + mpstream_encode_uint(stream, index_id); /* encode key */ - mpstream_encode_uint(&stream, IPROTO_KEY); - luamp_convert_key(L, cfg, &stream, idx + 2); + mpstream_encode_uint(stream, IPROTO_KEY); + luamp_convert_key(L, cfg, stream, idx + 2); - netbox_encode_request(&stream, svp); + netbox_encode_request(stream, svp); } static void -netbox_encode_update(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_update(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { /* Lua stack at idx: space_id, index_id, key, ops */ - struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, - IPROTO_UPDATE); + size_t svp = netbox_prepare_request(stream, sync, IPROTO_UPDATE); - mpstream_encode_map(&stream, 5); + mpstream_encode_map(stream, 5); /* encode space_id */ uint32_t space_id = lua_tonumber(L, idx); - mpstream_encode_uint(&stream, IPROTO_SPACE_ID); - mpstream_encode_uint(&stream, space_id); + mpstream_encode_uint(stream, IPROTO_SPACE_ID); + mpstream_encode_uint(stream, space_id); /* encode index_id */ uint32_t index_id = lua_tonumber(L, idx + 1); - mpstream_encode_uint(&stream, IPROTO_INDEX_ID); - mpstream_encode_uint(&stream, index_id); + mpstream_encode_uint(stream, IPROTO_INDEX_ID); + mpstream_encode_uint(stream, index_id); /* encode index_id */ - mpstream_encode_uint(&stream, IPROTO_INDEX_BASE); - mpstream_encode_uint(&stream, 1); + mpstream_encode_uint(stream, IPROTO_INDEX_BASE); + mpstream_encode_uint(stream, 1); /* encode key */ - mpstream_encode_uint(&stream, IPROTO_KEY); - luamp_convert_key(L, cfg, &stream, idx + 2); + mpstream_encode_uint(stream, IPROTO_KEY); + luamp_convert_key(L, cfg, stream, idx + 2); /* encode ops */ - mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, idx + 3); + mpstream_encode_uint(stream, IPROTO_TUPLE); + luamp_encode_tuple(L, cfg, stream, idx + 3); - netbox_encode_request(&stream, svp); + netbox_encode_request(stream, svp); } static void -netbox_encode_upsert(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_upsert(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { /* Lua stack at idx: space_id, tuple, ops */ - struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, - IPROTO_UPSERT); + size_t svp = netbox_prepare_request(stream, sync, IPROTO_UPSERT); - mpstream_encode_map(&stream, 4); + mpstream_encode_map(stream, 4); /* encode space_id */ uint32_t space_id = lua_tonumber(L, idx); - mpstream_encode_uint(&stream, IPROTO_SPACE_ID); - mpstream_encode_uint(&stream, space_id); + mpstream_encode_uint(stream, IPROTO_SPACE_ID); + mpstream_encode_uint(stream, space_id); /* encode index_base */ - mpstream_encode_uint(&stream, IPROTO_INDEX_BASE); - mpstream_encode_uint(&stream, 1); + mpstream_encode_uint(stream, IPROTO_INDEX_BASE); + mpstream_encode_uint(stream, 1); /* encode tuple */ - mpstream_encode_uint(&stream, IPROTO_TUPLE); - luamp_encode_tuple(L, cfg, &stream, idx + 1); + mpstream_encode_uint(stream, IPROTO_TUPLE); + luamp_encode_tuple(L, cfg, stream, idx + 1); /* encode ops */ - mpstream_encode_uint(&stream, IPROTO_OPS); - luamp_encode_tuple(L, cfg, &stream, idx + 2); + mpstream_encode_uint(stream, IPROTO_OPS); + luamp_encode_tuple(L, cfg, stream, idx + 2); - netbox_encode_request(&stream, svp); + netbox_encode_request(stream, svp); } static int @@ -550,78 +546,75 @@ handle_error: } static void -netbox_encode_execute(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_execute(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { /* Lua stack at idx: query, parameters, options */ - struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, - IPROTO_EXECUTE); + size_t svp = netbox_prepare_request(stream, sync, IPROTO_EXECUTE); - mpstream_encode_map(&stream, 3); + mpstream_encode_map(stream, 3); if (lua_type(L, idx) == LUA_TNUMBER) { uint32_t query_id = lua_tointeger(L, idx); - mpstream_encode_uint(&stream, IPROTO_STMT_ID); - mpstream_encode_uint(&stream, query_id); + mpstream_encode_uint(stream, IPROTO_STMT_ID); + mpstream_encode_uint(stream, query_id); } else { size_t len; const char *query = lua_tolstring(L, idx, &len); - mpstream_encode_uint(&stream, IPROTO_SQL_TEXT); - mpstream_encode_strn(&stream, query, len); + mpstream_encode_uint(stream, IPROTO_SQL_TEXT); + mpstream_encode_strn(stream, query, len); } - mpstream_encode_uint(&stream, IPROTO_SQL_BIND); - luamp_encode_tuple(L, cfg, &stream, idx + 1); + mpstream_encode_uint(stream, IPROTO_SQL_BIND); + luamp_encode_tuple(L, cfg, stream, idx + 1); - mpstream_encode_uint(&stream, IPROTO_OPTIONS); - luamp_encode_tuple(L, cfg, &stream, idx + 2); + mpstream_encode_uint(stream, IPROTO_OPTIONS); + luamp_encode_tuple(L, cfg, stream, idx + 2); - netbox_encode_request(&stream, svp); + netbox_encode_request(stream, svp); } static void -netbox_encode_prepare(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_prepare(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { /* Lua stack at idx: query */ - struct mpstream stream; - size_t svp = netbox_prepare_request(L, &stream, ibuf, sync, - IPROTO_PREPARE); + size_t svp = netbox_prepare_request(stream, sync, IPROTO_PREPARE); - mpstream_encode_map(&stream, 1); + mpstream_encode_map(stream, 1); if (lua_type(L, idx) == LUA_TNUMBER) { uint32_t query_id = lua_tointeger(L, idx); - mpstream_encode_uint(&stream, IPROTO_STMT_ID); - mpstream_encode_uint(&stream, query_id); + mpstream_encode_uint(stream, IPROTO_STMT_ID); + mpstream_encode_uint(stream, query_id); } else { size_t len; const char *query = lua_tolstring(L, idx, &len); - mpstream_encode_uint(&stream, IPROTO_SQL_TEXT); - mpstream_encode_strn(&stream, query, len); + mpstream_encode_uint(stream, IPROTO_SQL_TEXT); + mpstream_encode_strn(stream, query, len); }; - netbox_encode_request(&stream, svp); + netbox_encode_request(stream, svp); } static void -netbox_encode_unprepare(lua_State *L, int idx, struct ibuf *ibuf, uint64_t sync) +netbox_encode_unprepare(lua_State *L, int idx, struct mpstream *stream, + uint64_t sync) { /* Lua stack at idx: query, parameters, options */ - netbox_encode_prepare(L, idx, ibuf, sync); + netbox_encode_prepare(L, idx, stream, sync); } static void -netbox_encode_inject(struct lua_State *L, int idx, struct ibuf *ibuf, +netbox_encode_inject(struct lua_State *L, int idx, struct mpstream *stream, uint64_t sync) { /* Lua stack at idx: bytes */ (void)sync; size_t len; const char *data = lua_tolstring(L, idx, &len); - void *wpos = ibuf_alloc(ibuf, len); - if (wpos == NULL) - luaL_error(L, "out of memory"); - memcpy(wpos, data, len); + mpstream_memcpy(stream, data, len); + mpstream_flush(stream); } /* @@ -638,7 +631,8 @@ static int netbox_encode_method(struct lua_State *L) { typedef void (*method_encoder_f)(struct lua_State *L, int idx, - struct ibuf *ibuf, uint64_t sync); + struct mpstream *stream, + uint64_t sync); static method_encoder_f method_encoder[] = { [NETBOX_PING] = netbox_encode_ping, [NETBOX_CALL_16] = netbox_encode_call_16, @@ -663,7 +657,10 @@ netbox_encode_method(struct lua_State *L) assert(method < netbox_method_MAX); struct ibuf *ibuf = (struct ibuf *)lua_topointer(L, 2); uint64_t sync = luaL_touint64(L, 3); - method_encoder[method](L, 4, ibuf, sync); + struct mpstream stream; + mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, + luamp_error, L); + method_encoder[method](L, 4, &stream, sync); return 0; }
On Thu, Jul 29, 2021 at 02:33:13PM +0300, Vladimir Davydov wrote: > On Thu, Jul 29, 2021 at 12:51:18AM +0200, Vladislav Shpilevoy wrote: > > Hi! Thanks for the patchset! > > > > Here is a first part of my review. I will return later to continue. > > > > Commits 02-05, 07-08, 10 are LGTM. > > Pushed to master. > > > > > > Asynchronous calls don't show as much of an improvement as synchronous, > > > because per each asynchronous call we still have to create a 'future' > > > object in Lua. Still, the improvement is quite noticeable - 30% for > > > REPLACE, 10% for UPDATE, 20% for SELECT, 25% for CALL. > > > > I didn't reach the end of the patchset yet, but did you try to create > > the futures as cdata objects? They could be allocated on mempool, their > > GC pressure might be optimized by doing similar to luaT_tuple_encode_table_ref > > optimization (there was found a way to make __gc and other C functions > > cheaper when it comes to amount of GC objects in Lua). > > > > The API would stay the same, they just would become C structs with > > methods instead of Lua tables. > > Good call. Going to to try that. Thanks. Quickly whipped up a patch that converts userdata and cdata. Applied on top of the series. Surprisingly, it only made things worse: With the patch (future is cdata): ==== FUTURE ==== REPLACE: WALL 221.182 PROC 343.368 KRPS UPDATE: WALL 178.918 PROC 291.504 KRPS SELECT: WALL 220.815 PROC 248.843 KRPS CALL: WALL 218.313 PROC 315.670 KRPS Without the patch (future is userdata): ==== FUTURE ==== REPLACE: WALL 262.454 PROC 450.425 KRPS UPDATE: WALL 191.538 PROC 322.888 KRPS SELECT: WALL 288.498 PROC 333.393 KRPS CALL: WALL 247.463 PROC 375.180 KRPS The patch is below. Note, it isn't entirely correct - future:pairs doesn't work, because luaL_checkcdata doesn't seem to handle upvalues, but it shouldn't affect the test. -- From 8dc651e67f124a394cb6f4973b71080626d84d50 Mon Sep 17 00:00:00 2001 From: Vladimir Davydov <vdavydov@tarantool.org> Date: Thu, 29 Jul 2021 18:06:32 +0300 Subject: [PATCH] net.box: convert netbox_request to Lua cdata diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 957d4ff18a18..1fb3c566d59f 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -32,6 +32,7 @@ #include <sys/socket.h> #include <small/ibuf.h> +#include <small/mempool.h> #include <msgpuck.h> /* mp_store_u32() */ #include "scramble.h" @@ -135,8 +136,9 @@ struct netbox_request { }; +struct mempool netbox_request_pool; +static uint32_t CTID_STRUCT_NETBOX_REQUEST_PTR; static const char netbox_registry_typename[] = "net.box.registry"; -static const char netbox_request_typename[] = "net.box.request"; /* Passed to mpstream_init() to set a boolean flag on error. */ static void @@ -1394,7 +1396,11 @@ luaT_netbox_registry_reset(struct lua_State *L) static inline struct netbox_request * luaT_check_netbox_request(struct lua_State *L, int idx) { - return luaL_checkudata(L, idx, netbox_request_typename); + uint32_t cdata_type; + struct netbox_request **ptr = luaL_checkcdata(L, idx, &cdata_type); + if (ptr == NULL || cdata_type != CTID_STRUCT_NETBOX_REQUEST_PTR) + luaL_error(L, "expected net.box future as %d argument", idx); + return *ptr; } static int @@ -1403,6 +1409,7 @@ luaT_netbox_request_gc(struct lua_State *L) struct netbox_request *request = luaT_check_netbox_request(L, 1); netbox_request_unregister(request); netbox_request_destroy(request); + mempool_free(&netbox_request_pool, request); return 0; } @@ -1642,10 +1649,14 @@ netbox_perform_async_request_impl(struct lua_State *L, int idx, static int netbox_perform_async_request(struct lua_State *L) { - struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); + struct netbox_request *request = mempool_alloc(&netbox_request_pool); + if (request == NULL) + return luaL_error(L, "out of memory"); netbox_request_create(request); - luaL_getmetatable(L, netbox_request_typename); - lua_setmetatable(L, -2); + *(struct netbox_request **) + luaL_pushcdata(L, CTID_STRUCT_NETBOX_REQUEST_PTR) = request; + lua_pushcfunction(L, luaT_netbox_request_gc); + luaL_setcdatagc(L, -2); netbox_perform_async_request_impl(L, 1, request); return 1; } @@ -2020,6 +2031,13 @@ netbox_console_loop(struct lua_State *L) int luaopen_net_box(struct lua_State *L) { + mempool_create(&netbox_request_pool, cord_slab_cache(), + sizeof(struct netbox_request)); + + luaL_cdef(L, "struct netbox_request;"); + CTID_STRUCT_NETBOX_REQUEST_PTR = luaL_ctypeid( + L, "struct netbox_request *"); + static const struct luaL_Reg netbox_registry_meta[] = { { "__gc", luaT_netbox_registry_gc }, { "reset", luaT_netbox_registry_reset }, @@ -2027,17 +2045,6 @@ luaopen_net_box(struct lua_State *L) }; luaL_register_type(L, netbox_registry_typename, netbox_registry_meta); - static const struct luaL_Reg netbox_request_meta[] = { - { "__gc", luaT_netbox_request_gc }, - { "is_ready", luaT_netbox_request_is_ready }, - { "result", luaT_netbox_request_result }, - { "wait_result", luaT_netbox_request_wait_result }, - { "discard", luaT_netbox_request_discard }, - { "pairs", luaT_netbox_request_pairs }, - { NULL, NULL } - }; - luaL_register_type(L, netbox_request_typename, netbox_request_meta); - static const luaL_Reg net_box_lib[] = { { "decode_greeting",netbox_decode_greeting }, { "new_registry", netbox_new_registry }, @@ -2048,6 +2055,14 @@ luaopen_net_box(struct lua_State *L) { "iproto_loop", netbox_iproto_loop }, { "console_setup", netbox_console_setup }, { "console_loop", netbox_console_loop }, + + /* Request methods. */ + { "request_is_ready", luaT_netbox_request_is_ready }, + { "request_result", luaT_netbox_request_result }, + { "request_wait_result", luaT_netbox_request_wait_result }, + { "request_discard", luaT_netbox_request_discard }, + { "request_pairs", luaT_netbox_request_pairs }, + { NULL, NULL} }; /* luaL_register_module polutes _G */ diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 9da900933fbc..2aa76427a940 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -53,6 +53,24 @@ local M_COUNT = 16 -- Injects raw data into connection. Used by console and tests. local M_INJECT = 17 +local request_t = ffi.typeof('struct netbox_request') + +local request_methods = { + ['is_ready'] = internal.request_is_ready, + ['result'] = internal.request_result, + ['wait_result'] = internal.request_wait_result, + ['discard'] = internal.request_discard, + ['pairs'] = internal.request_pairs, +} + +ffi.metatype(request_t, { + __index = function(self, key) + return request_methods[key] + end, + -- Lua 5.2 compatibility + __pairs = internal.request_pairs, +}) + -- utility tables local is_final_state = {closed = 1, error = 1}
>>>> Asynchronous calls don't show as much of an improvement as synchronous, >>>> because per each asynchronous call we still have to create a 'future' >>>> object in Lua. Still, the improvement is quite noticeable - 30% for >>>> REPLACE, 10% for UPDATE, 20% for SELECT, 25% for CALL. >>> >>> I didn't reach the end of the patchset yet, but did you try to create >>> the futures as cdata objects? They could be allocated on mempool, their >>> GC pressure might be optimized by doing similar to luaT_tuple_encode_table_ref >>> optimization (there was found a way to make __gc and other C functions >>> cheaper when it comes to amount of GC objects in Lua). >>> >>> The API would stay the same, they just would become C structs with >>> methods instead of Lua tables. >> >> Good call. Going to to try that. Thanks. > > Quickly whipped up a patch that converts userdata and cdata. Applied on > top of the series. Surprisingly, it only made things worse: > > With the patch (future is cdata): > > ==== FUTURE ==== > REPLACE: WALL 221.182 PROC 343.368 KRPS > UPDATE: WALL 178.918 PROC 291.504 KRPS > SELECT: WALL 220.815 PROC 248.843 KRPS > CALL: WALL 218.313 PROC 315.670 KRPS > > > Without the patch (future is userdata): > > ==== FUTURE ==== > REPLACE: WALL 262.454 PROC 450.425 KRPS > UPDATE: WALL 191.538 PROC 322.888 KRPS > SELECT: WALL 288.498 PROC 333.393 KRPS > CALL: WALL 247.463 PROC 375.180 KRPS > > The patch is below. Note, it isn't entirely correct - future:pairs > doesn't work, because luaL_checkcdata doesn't seem to handle upvalues, > but it shouldn't affect the test. Sorry, the patch can't be applied on top of the branch somewhy. But I see now that you already had the futures as C objects even before my proposal on top of the branch, so perhaps this is expectable that not much improved. However there is an idea which might make the perf gap smaller. > @@ -1642,10 +1649,14 @@ netbox_perform_async_request_impl(struct lua_State *L, int idx, > static int > netbox_perform_async_request(struct lua_State *L) > { > - struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); > + struct netbox_request *request = mempool_alloc(&netbox_request_pool); > + if (request == NULL) > + return luaL_error(L, "out of memory"); > netbox_request_create(request); > - luaL_getmetatable(L, netbox_request_typename); > - lua_setmetatable(L, -2); > + *(struct netbox_request **) > + luaL_pushcdata(L, CTID_STRUCT_NETBOX_REQUEST_PTR) = request; > + lua_pushcfunction(L, luaT_netbox_request_gc); Here is the problem, which I mentioned in my email. lua_pushcfunction() is an expensive thing because it pushes a new GC object on the stack every time. It was happening in a few hot places before, and there is now a solution that for such cfunctions we push them only once, remember their ref like luaT_tuple_encode_table_ref does, and then re-push the same finalizer for each cdata object. See ec9a7fa7ebbb8fd6e15b9516875c3fd1a1f6dfee and e88c0d21ab765d4c53bed2437c49d77b3ffe4216. You need to do lua_pushcfunction(L, luaT_netbox_request_gc); only once somewhere in netbox_init() or something. Then netbox_request_gc_ref = luaL_ref(L, LUA_REGISTRYINDEX); Then in netbox_perform_async_request() you make lua_rawgeti(L, LUA_REGISTRYINDEX, netbox_request_gc_ref); luaL_setcdatagc(L, -2); I think this should help. But I doubt it will help too much though.
Thanks for the patch! See 3 comments below. On 23.07.2021 13:07, Vladimir Davydov via Tarantool-patches wrote: > This patch moves method_decoder table from Lua to C. This is a step > towards rewriting performance-critical parts of net.box in C. > --- > src/box/lua/net_box.c | 235 +++++++++++++++++++++++++++++++--------- > src/box/lua/net_box.lua | 43 +------- > 2 files changed, 189 insertions(+), 89 deletions(-) > > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index 49030aabea69..c0c3725e5350 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -678,6 +678,79 @@ netbox_encode_method(struct lua_State *L) <...> > + > +/** > + * Decode Tarantool response body consisting of single > + * IPROTO_DATA key into Lua table. > + * @param L Lua stack to push result on. > + * @param data MessagePack. > + * @retval Lua table 1. It is not necessary to write these doxygen comments for each param really if you think they are obvious. The only 'rule' is to comment the function on its first declaration. Up to you of course. Not a rule. > + */ > +static void > +netbox_decode_table(struct lua_State *L, const char **data, > + const char *data_end, struct tuple_format *format) > +{ > + (void) data_end; > + (void) format; > + netbox_skip_to_data(data); > + luamp_decode(L, cfg, data); > +} > + > +/** > + * Same as netbox_decode_table, but only decodes the first element of the > + * table, skipping the rest. Used to decode index.count() call result. > + * @param L Lua stack to push result on. > + * @param data MessagePack. > + * @retval count or nil. > + */ > +static void > +netbox_decode_value(struct lua_State *L, const char **data, > + const char *data_end, struct tuple_format *format) > +{ > + (void) data_end; > + (void) format; > + netbox_skip_to_data(data); > + uint32_t count = mp_decode_array(data); > + for (uint32_t i = 0; i < count; ++i) { > + if (i == 0) > + luamp_decode(L, cfg, data); > + else > + mp_next(data); 2. Is the compiler able to turn this into?: ... if (count == 0) return lua_pushnil(L); luamp_decode(L, cfg, data); for (uint32_t i = 1; i < count; ++i) mp_next(data); } And why not do it right away? It looks shorter and a bit simpler IMO. Up to you. > + } > + if (count == 0) > + lua_pushnil(L); > +} > + > /** > * Decode IPROTO_DATA into tuples array. > * @param L Lua stack to push result on. > @@ -704,31 +777,45 @@ netbox_decode_data(struct lua_State *L, const char **data, <...> > + > +/** > + * Same as netbox_decode_select, but only decodes the first tuple of the array, > + * skipping the rest. > + * @param L Lua stack to push result on. > + * @param data MessagePack. > + * @retval Tuple or nil. > + */ > +static void > +netbox_decode_tuple(struct lua_State *L, const char **data, > + const char *data_end, struct tuple_format *format) > +{ > + (void) data_end; > + netbox_skip_to_data(data); > + uint32_t count = mp_decode_array(data); > + for (uint32_t i = 0; i < count; ++i) { > + const char *begin = *data; > + mp_next(data); > + if (i > 0) > + continue; > + struct tuple *tuple = box_tuple_new(format, begin, *data); > + if (tuple == NULL) > + luaT_error(L); > + luaT_pushtuple(L, tuple); > + } > + if (count == 0) > + lua_pushnil(L); 3. Ditto. Wouldn't it be shorter and a bit easier to read by doing this?: ... if (count == 0) return lua_pushnil(L); struct tuple *tuple = box_tuple_new(format, begin, *data); if (tuple == NULL) luaT_error(L); luaT_pushtuple(L, tuple); for (uint32_t i = 1; i < count; ++i) mp_next(data); } Up to you.
On 29.07.2021 12:54, Vladimir Davydov wrote:
> On Thu, Jul 29, 2021 at 12:49:39AM +0200, Vladislav Shpilevoy wrote:
>> On 23.07.2021 13:07, Vladimir Davydov via Tarantool-patches wrote:
>>> Request context only stores tuple format or nil, which is used for
>>> decoding a response. Rename it appropriately.
>>
>> The name ctx was chosen intentionally, because when you pass it to
>> method_decoder[request.method](...), you don't know how will it be
>> used and what is stores. It was internal for the request sender and
>> codec. Why do you need to change it?
>
> For one thing, it's always either nil or format. Naming it ctx is
> confusing.
>
> The reason I'm doing this in the scope of this series is that it is
> stored in the request class. Lua isn't a strictly typed language so we
> can store whatever we want there, but in C we need to know the type to
> define the request struct, see netbox_request::format here:
>
> https://github.com/tarantool/tarantool/blob/73cab8402f7e098c8e908af9a2b72695c754c472/src/box/lua/net_box.c#L99
>
> Turning ctx to format solves this problem.
You could store it as `void *` then. It just looks ugly, for
instance, when you pass `struct tuple_format` into netbox_decode_nil().
Not all responses consist of tuples or even have a body.
Up to you. We could change it in the future if need.
Hi! Thanks for the updates! Patches 06 and 09 LGTM. Also the not numbered commit "net.box: create mpstream in netbox_encode_method" LGTM. I will provide more comments later.
On Fri, Jul 30, 2021 at 12:39:29AM +0200, Vladislav Shpilevoy wrote: > On 29.07.2021 12:54, Vladimir Davydov wrote: > > On Thu, Jul 29, 2021 at 12:49:39AM +0200, Vladislav Shpilevoy wrote: > >> On 23.07.2021 13:07, Vladimir Davydov via Tarantool-patches wrote: > >>> Request context only stores tuple format or nil, which is used for > >>> decoding a response. Rename it appropriately. > >> > >> The name ctx was chosen intentionally, because when you pass it to > >> method_decoder[request.method](...), you don't know how will it be > >> used and what is stores. It was internal for the request sender and > >> codec. Why do you need to change it? > > > > For one thing, it's always either nil or format. Naming it ctx is > > confusing. > > > > The reason I'm doing this in the scope of this series is that it is > > stored in the request class. Lua isn't a strictly typed language so we > > can store whatever we want there, but in C we need to know the type to > > define the request struct, see netbox_request::format here: > > > > https://github.com/tarantool/tarantool/blob/73cab8402f7e098c8e908af9a2b72695c754c472/src/box/lua/net_box.c#L99 > > > > Turning ctx to format solves this problem. > You could store it as `void *` then. It just looks ugly, for > instance, when you pass `struct tuple_format` into netbox_decode_nil(). > Not all responses consist of tuples or even have a body. If we stored a format in void *, we'd have to cast it on request destruction to decrement its reference counter. To avoid that, we'd need to introduce an abstract class for the decoder context, but since currently the context can only be a tuple format, I don't think we should bother now. > > Up to you. We could change it in the future if need. Let's change that in future, when we need it.
On Fri, Jul 30, 2021 at 12:40:33AM +0200, Vladislav Shpilevoy wrote:
> Patches 06 and 09 LGTM.
>
> Also the not numbered commit "net.box: create mpstream in
> netbox_encode_method" LGTM.
Pushed to master and rebased, thanks.
On Fri, Jul 30, 2021 at 12:39:17AM +0200, Vladislav Shpilevoy wrote: > On 23.07.2021 13:07, Vladimir Davydov via Tarantool-patches wrote: > > +/** > > + * Decode Tarantool response body consisting of single > > + * IPROTO_DATA key into Lua table. > > + * @param L Lua stack to push result on. > > + * @param data MessagePack. > > + * @retval Lua table > > 1. It is not necessary to write these doxygen comments for each > param really if you think they are obvious. The only 'rule' is to > comment the function on its first declaration. Up to you of > course. Not a rule. You're right. Removed all the doxygen. > > +static void > > +netbox_decode_value(struct lua_State *L, const char **data, > > + const char *data_end, struct tuple_format *format) > > +{ > > + (void) data_end; > > + (void) format; > > + netbox_skip_to_data(data); > > + uint32_t count = mp_decode_array(data); > > + for (uint32_t i = 0; i < count; ++i) { > > + if (i == 0) > > + luamp_decode(L, cfg, data); > > + else > > + mp_next(data); > > 2. Is the compiler able to turn this into?: > > ... > if (count == 0) > return lua_pushnil(L); > > luamp_decode(L, cfg, data); > for (uint32_t i = 1; i < count; ++i) > mp_next(data); > } > > And why not do it right away? It looks shorter and a bit > simpler IMO. Up to you. Looks better, reworked, thanks. > > +static void > > +netbox_decode_tuple(struct lua_State *L, const char **data, > > + const char *data_end, struct tuple_format *format) > > +{ > > + (void) data_end; > > + netbox_skip_to_data(data); > > + uint32_t count = mp_decode_array(data); > > + for (uint32_t i = 0; i < count; ++i) { > > + const char *begin = *data; > > + mp_next(data); > > + if (i > 0) > > + continue; > > + struct tuple *tuple = box_tuple_new(format, begin, *data); > > + if (tuple == NULL) > > + luaT_error(L); > > + luaT_pushtuple(L, tuple); > > + } > > + if (count == 0) > > + lua_pushnil(L); > > 3. Ditto. Wouldn't it be shorter and a bit easier to read by doing this?: > > ... > if (count == 0) > return lua_pushnil(L); > > struct tuple *tuple = box_tuple_new(format, begin, *data); > if (tuple == NULL) > luaT_error(L); > luaT_pushtuple(L, tuple); > for (uint32_t i = 1; i < count; ++i) > mp_next(data); > } > > Up to you. Done. The incremental diff is below. The full patch is available by the link: https://github.com/tarantool/tarantool/commit/1087c1e687284efa4e39ce67fc4bea67265b7f58 -- diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 2d2562550752..ac9052de286c 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -664,7 +664,7 @@ netbox_encode_method(struct lua_State *L) return 0; } -/** +/* * This function handles a response that is supposed to have an empty body * (e.g. IPROTO_PING result). It doesn't decode anything per se. Instead it * simply pushes nil to Lua stack and advances the data ptr to data_end. @@ -678,7 +678,7 @@ netbox_decode_nil(struct lua_State *L, const char **data, lua_pushnil(L); } -/** +/* * This helper skips a MessagePack map header and IPROTO_DATA key so that * *data points to the actual response content. */ @@ -695,12 +695,9 @@ netbox_skip_to_data(const char **data) (void)key; } -/** - * Decode Tarantool response body consisting of single - * IPROTO_DATA key into Lua table. - * @param L Lua stack to push result on. - * @param data MessagePack. - * @retval Lua table +/* + * Decodes Tarantool response body consisting of single IPROTO_DATA key into + * a Lua table and pushes the table to Lua stack. */ static void netbox_decode_table(struct lua_State *L, const char **data, @@ -712,12 +709,9 @@ netbox_decode_table(struct lua_State *L, const char **data, luamp_decode(L, cfg, data); } -/** +/* * Same as netbox_decode_table, but only decodes the first element of the - * table, skipping the rest. Used to decode index.count() call result. - * @param L Lua stack to push result on. - * @param data MessagePack. - * @retval count or nil. + * table, skipping the rest. */ static void netbox_decode_value(struct lua_State *L, const char **data, @@ -727,20 +721,15 @@ netbox_decode_value(struct lua_State *L, const char **data, (void)format; netbox_skip_to_data(data); uint32_t count = mp_decode_array(data); - for (uint32_t i = 0; i < count; ++i) { - if (i == 0) - luamp_decode(L, cfg, data); - else - mp_next(data); - } if (count == 0) - lua_pushnil(L); + return lua_pushnil(L); + luamp_decode(L, cfg, data); + for (uint32_t i = 1; i < count; ++i) + mp_next(data); } -/** - * Decode IPROTO_DATA into tuples array. - * @param L Lua stack to push result on. - * @param data MessagePack. +/* + * Decodes IPROTO_DATA into a tuple array and pushes the array to Lua stack. */ static void netbox_decode_data(struct lua_State *L, const char **data, @@ -760,12 +749,9 @@ netbox_decode_data(struct lua_State *L, const char **data, } } -/** - * Decode Tarantool response body consisting of single - * IPROTO_DATA key into array of tuples. - * @param L Lua stack to push result on. - * @param data MessagePack. - * @retval Tuples array. +/* + * Decodes Tarantool response body consisting of single IPROTO_DATA key into + * tuple array and pushes the array to Lua stack. */ static void netbox_decode_select(struct lua_State *L, const char **data, @@ -776,12 +762,9 @@ netbox_decode_select(struct lua_State *L, const char **data, netbox_decode_data(L, data, format); } -/** +/* * Same as netbox_decode_select, but only decodes the first tuple of the array, * skipping the rest. - * @param L Lua stack to push result on. - * @param data MessagePack. - * @retval Tuple or nil. */ static void netbox_decode_tuple(struct lua_State *L, const char **data, @@ -790,18 +773,16 @@ netbox_decode_tuple(struct lua_State *L, const char **data, (void)data_end; netbox_skip_to_data(data); uint32_t count = mp_decode_array(data); - for (uint32_t i = 0; i < count; ++i) { - const char *begin = *data; - mp_next(data); - if (i > 0) - continue; - struct tuple *tuple = box_tuple_new(format, begin, *data); - if (tuple == NULL) - luaT_error(L); - luaT_pushtuple(L, tuple); - } if (count == 0) - lua_pushnil(L); + return lua_pushnil(L); + const char *begin = *data; + mp_next(data); + struct tuple *tuple = box_tuple_new(format, begin, *data); + if (tuple == NULL) + luaT_error(L); + luaT_pushtuple(L, tuple); + for (uint32_t i = 1; i < count; ++i) + mp_next(data); } /** Decode optional (i.e. may be present in response) metadata fields. */
On Fri, Jul 30, 2021 at 12:38:52AM +0200, Vladislav Shpilevoy wrote: > >>>> Asynchronous calls don't show as much of an improvement as synchronous, > >>>> because per each asynchronous call we still have to create a 'future' > >>>> object in Lua. Still, the improvement is quite noticeable - 30% for > >>>> REPLACE, 10% for UPDATE, 20% for SELECT, 25% for CALL. > >>> > >>> I didn't reach the end of the patchset yet, but did you try to create > >>> the futures as cdata objects? They could be allocated on mempool, their > >>> GC pressure might be optimized by doing similar to luaT_tuple_encode_table_ref > >>> optimization (there was found a way to make __gc and other C functions > >>> cheaper when it comes to amount of GC objects in Lua). > >>> > >>> The API would stay the same, they just would become C structs with > >>> methods instead of Lua tables. > >> > >> Good call. Going to to try that. Thanks. > > > > Quickly whipped up a patch that converts userdata and cdata. Applied on > > top of the series. Surprisingly, it only made things worse: > > > > With the patch (future is cdata): > > > > ==== FUTURE ==== > > REPLACE: WALL 221.182 PROC 343.368 KRPS > > UPDATE: WALL 178.918 PROC 291.504 KRPS > > SELECT: WALL 220.815 PROC 248.843 KRPS > > CALL: WALL 218.313 PROC 315.670 KRPS > > > > > > Without the patch (future is userdata): > > > > ==== FUTURE ==== > > REPLACE: WALL 262.454 PROC 450.425 KRPS > > UPDATE: WALL 191.538 PROC 322.888 KRPS > > SELECT: WALL 288.498 PROC 333.393 KRPS > > CALL: WALL 247.463 PROC 375.180 KRPS > > > > The patch is below. Note, it isn't entirely correct - future:pairs > > doesn't work, because luaL_checkcdata doesn't seem to handle upvalues, > > but it shouldn't affect the test. > > Sorry, the patch can't be applied on top of the branch somewhy. > > But I see now that you already had the futures as C objects even > before my proposal on top of the branch, so perhaps this is expectable > that not much improved. > > However there is an idea which might make the perf gap smaller. > > > @@ -1642,10 +1649,14 @@ netbox_perform_async_request_impl(struct lua_State *L, int idx, > > static int > > netbox_perform_async_request(struct lua_State *L) > > { > > - struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); > > + struct netbox_request *request = mempool_alloc(&netbox_request_pool); > > + if (request == NULL) > > + return luaL_error(L, "out of memory"); > > netbox_request_create(request); > > - luaL_getmetatable(L, netbox_request_typename); > > - lua_setmetatable(L, -2); > > + *(struct netbox_request **) > > + luaL_pushcdata(L, CTID_STRUCT_NETBOX_REQUEST_PTR) = request; > > + lua_pushcfunction(L, luaT_netbox_request_gc); > > Here is the problem, which I mentioned in my email. > > lua_pushcfunction() is an expensive thing because it pushes a new GC > object on the stack every time. It was happening in a few hot places before, > and there is now a solution that for such cfunctions we push them only once, > remember their ref like luaT_tuple_encode_table_ref does, and then re-push > the same finalizer for each cdata object. > > See ec9a7fa7ebbb8fd6e15b9516875c3fd1a1f6dfee and > e88c0d21ab765d4c53bed2437c49d77b3ffe4216. > > You need to do > > lua_pushcfunction(L, luaT_netbox_request_gc); > > only once somewhere in netbox_init() or something. Then > > netbox_request_gc_ref = luaL_ref(L, LUA_REGISTRYINDEX); > > Then in netbox_perform_async_request() you make > > lua_rawgeti(L, LUA_REGISTRYINDEX, netbox_request_gc_ref); > luaL_setcdatagc(L, -2); > > I think this should help. But I doubt it will help too much > though. It does help, but the userdata implementation of the future object still performs better: * Userdata (original implementation) REPLACE: WALL 254.037 PROC 424.737 KRPS UPDATE: WALL 193.136 PROC 323.086 KRPS SELECT: WALL 354.040 PROC 428.211 KRPS CALL: WALL 275.215 PROC 449.326 KRPS * Cdata; set gc function using lua_pushcfunction REPLACE: WALL 216.516 PROC 334.610 KRPS UPDATE: WALL 180.824 PROC 290.090 KRPS SELECT: WALL 261.858 PROC 301.944 KRPS CALL: WALL 244.465 PROC 372.902 KRPS * Cdata; set gc function using lua_rawgeti REPLACE: WALL 237.665 PROC 381.624 KRPS UPDATE: WALL 184.152 PROC 298.147 KRPS SELECT: WALL 299.181 PROC 350.282 KRPS CALL: WALL 263.745 PROC 419.355 KRPS The patches turning the future object into cdata are available on the following branch: https://github.com/tarantool/tarantool/tree/vdavydov/net-box-optimization-cdata-request
Thanks for the fixes! > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index 2d2562550752..ac9052de286c 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -664,7 +664,7 @@ netbox_encode_method(struct lua_State *L) > return 0; > } > > -/** > +/* There is misunderstanding, sorry. As doxygen I meant only these '@a param desc' lines. The /** are a part of our code style, look: https://github.com/tarantool/tarantool/wiki/Code-review-procedure#code-style All out-of-function comments use /** on the first line, all comments inside of functions use /* on the first line. After your fix this - the commit LGTM.
Thanks for the patch! See 3 comments below. > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index c0c3725e5350..e88db6323afa 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -1082,6 +1084,63 @@ netbox_decode_method(struct lua_State *L) > +static int > +netbox_decode_error(struct lua_State *L) > +{ > + uint32_t ctypeid; > + const char **data = luaL_checkcdata(L, 1, &ctypeid); > + assert(ctypeid == CTID_CHAR_PTR || ctypeid == CTID_CONST_CHAR_PTR); > + uint32_t errcode = lua_tointeger(L, 2); > + struct error *error = NULL; > + assert(mp_typeof(**data) == MP_MAP); > + uint32_t map_size = mp_decode_map(data); > + for (uint32_t i = 0; i < map_size; ++i) { > + uint32_t key = mp_decode_uint(data); > + if (key == IPROTO_ERROR) { > + if (error != NULL) > + error_unref(error); > + error = error_unpack_unsafe(data); > + if (error == NULL) > + return luaT_error(L); > + error_ref(error); > + /* > + * IPROTO_ERROR comprises error encoded with > + * IPROTO_ERROR_24, so we may ignore content > + * of that key. > + */ > + break; > + } else if (key == IPROTO_ERROR_24) { > + if (error != NULL) > + error_unref(error); > + const char *reason = ""; > + uint32_t reason_len = 0; > + if (mp_typeof(**data) == MP_STR) > + reason = mp_decode_str(data, &reason_len); > + box_error_raise(errcode, "%.*s", reason_len, reason); > + error = box_error_last(); > + error_ref(error); > + continue; > + } > + mp_next(data); /* skip value */ 1. Could you please write comments on their own lines if possible? > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > index 9e41d6c0844b..d7394b088752 100644 > --- a/src/box/lua/net_box.lua > +++ b/src/box/lua/net_box.lua > @@ -73,14 +74,6 @@ local M_COUNT = 16 > -- Injects raw data into connection. Used by console and tests. > local M_INJECT = 17 > > -ffi.cdef[[ > -struct error * > -error_unpack_unsafe(const char **data); 2. You should be able to remove it from exports.h now. It is not used by FFI anymore. (error_unref() should stay in export.h - it is used by error Lua module.) > - > -void > -error_unref(struct error *e); > -]] > - > -- utility tables > local is_final_state = {closed = 1, error = 1} > > @@ -97,29 +90,6 @@ local function version_at_least(peer_version_id, major, minor, patch) > return peer_version_id >= version_id(major, minor, patch) > end > > -local function decode_error(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 > - -- From FFI it is returned as 'struct error *', which is > - -- not considered equal to 'const struct error &', and is > - -- is not accepted by functions like box.error(). Need to > - -- cast explicitly. > - err = ffi.cast('const struct error &', err) > - err = ffi.gc(err, ffi.C.error_unref) > - else > - -- Error unpacker installs fail reason into diag. > - box.error() > - end > - return err, ptr[0] > -end > - > -local response_decoder = { > - [IPROTO_ERROR_24] = decode, > - [IPROTO_ERROR] = decode_error, 3. IPROTO_ERROR is now unused.
On Sat, Jul 31, 2021 at 12:12:51AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
>
> > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
> > index 2d2562550752..ac9052de286c 100644
> > --- a/src/box/lua/net_box.c
> > +++ b/src/box/lua/net_box.c
> > @@ -664,7 +664,7 @@ netbox_encode_method(struct lua_State *L)
> > return 0;
> > }
> >
> > -/**
> > +/*
>
> There is misunderstanding, sorry. As doxygen I meant only these
> '@a param desc' lines. The /** are a part of our code style, look:
> https://github.com/tarantool/tarantool/wiki/Code-review-procedure#code-style
>
> All out-of-function comments use /** on the first line, all comments inside
> of functions use /* on the first line.
>
> After your fix this - the commit LGTM.
Fixed and pushed to master.
On Sat, Jul 31, 2021 at 12:13:15AM +0200, Vladislav Shpilevoy wrote: > > + mp_next(data); /* skip value */ > > 1. Could you please write comments on their own lines if possible? Done. > > -ffi.cdef[[ > > -struct error * > > -error_unpack_unsafe(const char **data); > > 2. You should be able to remove it from exports.h now. It > is not used by FFI anymore. (error_unref() should stay in > export.h - it is used by error Lua module.) Done. > > -local response_decoder = { > > - [IPROTO_ERROR_24] = decode, > > - [IPROTO_ERROR] = decode_error, > > 3. IPROTO_ERROR is now unused. Removed. Pushed all the changes to the branch and rebased. The incremental diff is below: -- diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 0914697f2b2e..e1f820926838 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -1051,7 +1051,7 @@ netbox_decode_method(struct lua_State *L) return 2; } -/* +/** * Decodes an error from raw data and pushes it to Lua stack. Takes a pointer * to the data (char ptr) and an error code. */ @@ -1092,7 +1092,8 @@ netbox_decode_error(struct lua_State *L) error_ref(error); continue; } - mp_next(data); /* skip value */ + /* Skip value. */ + mp_next(data); } if (error == NULL) { /* diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index c0c3c4098525..242b1c8d9314 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -41,7 +41,6 @@ local IPROTO_SYNC_KEY = 0x01 local IPROTO_SCHEMA_VERSION_KEY = 0x05 local IPROTO_DATA_KEY = 0x30 local IPROTO_ERROR_24 = 0x31 -local IPROTO_ERROR = 0x52 local IPROTO_GREETING_SIZE = 128 local IPROTO_CHUNK_KEY = 128 local IPROTO_OK_KEY = 0 diff --git a/src/exports.h b/src/exports.h index 5bb3e6a2b55a..7be6486f0075 100644 --- a/src/exports.h +++ b/src/exports.h @@ -220,7 +220,6 @@ EXPORT(curl_version_info) EXPORT(decimal_unpack) EXPORT(error_ref) EXPORT(error_set_prev) -EXPORT(error_unpack_unsafe) EXPORT(error_unref) EXPORT(exception_get_int) EXPORT(exception_get_string)
Hi! Thanks for the fixes, nice work! The commits 12 and 14 LGTM.
Thanks for working on this! See 3 comments below. > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index e88db6323afa..12d82738a050 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -465,62 +465,45 @@ netbox_decode_greeting(lua_State *L) > * interaction. > */ > static int > -netbox_communicate(lua_State *L) > +netbox_communicate(int fd, struct ibuf *send_buf, struct ibuf *recv_buf, > + size_t limit, const void *boundary, size_t boundary_len, > + double timeout, size_t *limit_or_boundary_pos) 1. IMO, the name looks really bulky. I would just call it `size` or `end_pos`. Up to you. > { > - uint32_t fd = lua_tonumber(L, 1); > const int NETBOX_READAHEAD = 16320; > - struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); > - struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 3); > - > - /* limit or boundary */ > - size_t limit = SIZE_MAX; > - const void *boundary = NULL; > - size_t boundary_len; > - > - if (lua_type(L, 4) == LUA_TSTRING) > - boundary = lua_tolstring(L, 4, &boundary_len); > - else > - limit = lua_tonumber(L, 4); > - > - /* timeout */ > - ev_tstamp timeout = TIMEOUT_INFINITY; > - if (lua_type(L, 5) == LUA_TNUMBER) > - timeout = lua_tonumber(L, 5); > if (timeout < 0) { > - lua_pushinteger(L, ER_TIMEOUT); > - lua_pushstring(L, "Timeout exceeded"); > - return 2; > + diag_set(ClientError, ER_TIMEOUT); > + return -1; > } > int revents = COIO_READ; > while (true) { > /* reader serviced first */ > check_limit: > if (ibuf_used(recv_buf) >= limit) { > - lua_pushnil(L); > - lua_pushinteger(L, (lua_Integer)limit); > - return 2; > + *limit_or_boundary_pos = limit; > + return 0; > } > const char *p; > if (boundary != NULL && (p = memmem( > recv_buf->rpos, > ibuf_used(recv_buf), > boundary, boundary_len)) != NULL) { > - lua_pushnil(L); > - lua_pushinteger(L, (lua_Integer)( > - p - recv_buf->rpos)); > - return 2; > + *limit_or_boundary_pos = p - recv_buf->rpos; > + return 0; > } > > while (revents & COIO_READ) { > void *p = ibuf_reserve(recv_buf, NETBOX_READAHEAD); > - if (p == NULL) > - luaL_error(L, "out of memory"); > + if (p == NULL) { > + diag_set(OutOfMemory, NETBOX_READAHEAD, > + "ibuf", "recv_buf"); 2. For OutOfMemory errors as a rule we use the format diag_set(OutOfMemory, size, "name of the allocation function", "name of the variable") So here it would be diag_set(OutOfMemory, NETBOX_READAHEAD, "ibuf_reserve", "p"); I know it is violated in a lot of old code and I gave up trying to enforce it in new patches to exact that form. Up to you. The same in the other new OutOfMemory in the other patches. > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > index d7394b088752..0ad6cac022f2 100644 > --- a/src/box/lua/net_box.lua > +++ b/src/box/lua/net_box.lua > @@ -582,46 +581,23 @@ local function create_transport(host, port, user, password, callback, > end > > -- IO (WORKER FIBER) -- > - local function send_and_recv(limit_or_boundary, timeout) > - return communicate(connection:fd(), send_buf, recv_buf, > - limit_or_boundary, timeout) > - end > - > local function send_and_recv_iproto(timeout) > - local data_len = recv_buf.wpos - recv_buf.rpos > - local required > - if data_len < 5 then > - required = 5 > - else > - -- PWN! insufficient input validation > - local bufpos = recv_buf.rpos > - local len, rpos = decode(bufpos) > - required = (rpos - bufpos) + len > - if data_len >= required then > - local body_end = rpos + len > - local hdr, body_rpos = decode(rpos) > - recv_buf.rpos = body_end > - return nil, hdr, body_rpos, body_end > - end > + local hdr, body_rpos, body_end = internal.send_and_recv_iproto( 3. Indexing 'internal' via '.' is not free. It is a lookup in a hash. You might want to save internal.send_and_recv_iproto into a global variable when the module loads first time and use the cached value. Non-cached version is a bit faster only for FFI, but here you are using Lua C - cache should be good. > + connection:fd(), send_buf, recv_buf, timeout) Another idea is to cache 'connection:fd()' result into a variable in the root of create_transport() function. And update it when the connetion is re-established. Although you probably move this all to C later as well, I didn't reach the last commits yet.
Thanks for the patch! In an early version I saw on the branch you tried to keep the discarded requests in the hash. It is not so anymore, correct? Now discarded requests are GCed even their response didn't arrive yet? (It should be so, but I decided to clarify just in case.) See 12 comments below. > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index 122d69e9219e..044f7d337ca7 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -76,6 +78,253 @@ enum netbox_method { > netbox_method_MAX > }; > > +struct netbox_registry { > + /* sync -> netbox_request */ 1. For out of function comments we usually use /**. This includes the comments for structs and their members. > + struct mh_i64ptr_t *requests; > +}; > + > +struct netbox_request { > + enum netbox_method method; > + /* > + * Unique identifier needed for matching the request with its response. > + * Used as a key in the registry. > + */ > + uint64_t sync; > + /* > + * The registry this request belongs to or NULL if the request has been > + * completed. > + */ > + struct netbox_registry *registry; > + /* Format used for decoding the response (ref incremented). */ > + struct tuple_format *format; > + /* Signaled when the response is received. */ > + struct fiber_cond cond; > + /* > + * A user-provided buffer to which the response body should be copied. > + * If NULL, the response will be decoded to Lua stack. > + */ > + struct ibuf *buffer; > + /* > + * Lua reference to the buffer. Used to prevent garbage collection in > + * case the user discards the request. > + */ > + int buffer_ref;> + /* > + * Whether to skip MessagePack map header and IPROTO_DATA key when > + * copying the response body to a user-provided buffer. Ignored if > + * buffer is not set. > + */ > + bool skip_header; > + /* Lua references to on_push trigger and its context. */ > + int on_push_ref; > + int on_push_ctx_ref; > + /* > + * Reference to the request result or LUA_NOREF if the response hasn't > + * been received yet. If the response was decoded to a user-provided > + * buffer, the result stores the length of the decoded data. 2. Do you mean result_ref stores the length? I can't find it in the code. It always stores a real Lua ref from what I see. > + */ > + int result_ref; > + /* > + * Error if the request failed (ref incremented). NULL on success or if > + * the response hasn't been received yet. > + */ > + struct error *error; > + 3. Extra empty line. > +}; > + > +static const char netbox_registry_typename[] = "net.box.registry"; > +static const char netbox_request_typename[] = "net.box.request"; > + > +static void > +netbox_request_create(struct netbox_request *request) > +{ > + request->method = netbox_method_MAX; > + request->sync = -1; > + request->registry = NULL; > + request->format = NULL; > + fiber_cond_create(&request->cond); > + request->buffer = NULL; > + request->buffer_ref = LUA_REFNIL; > + request->skip_header = false; > + request->on_push_ref = LUA_REFNIL; > + request->on_push_ctx_ref = LUA_REFNIL; > + request->result_ref = LUA_NOREF; > + request->error = NULL; > +} > + > +static void > +netbox_request_destroy(struct netbox_request *request) > +{ > + assert(request->registry == NULL); > + if (request->format != NULL) > + tuple_format_unref(request->format); > + fiber_cond_destroy(&request->cond); > + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->buffer_ref); > + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->on_push_ref); > + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->on_push_ctx_ref); > + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->result_ref); > + if (request->error != NULL) > + error_unref(request->error); > +} > + > +/* > + * Adds a request to a registry. There must not be a request with the same id > + * (sync) in the registry. Returns -1 if out of memory. > + */ > +static int > +netbox_request_register(struct netbox_request *request, > + struct netbox_registry *registry) > +{ > + assert(request->registry == NULL); > + struct mh_i64ptr_t *h = registry->requests; > + struct mh_i64ptr_node_t node = { request->sync, request }; > + struct mh_i64ptr_node_t *old_node = NULL; > + if (mh_i64ptr_put(h, &node, &old_node, NULL) == mh_end(h)) { > + diag_set(OutOfMemory, 0, "mhash", "netbox_registry"); > + return -1; > + } > + assert(old_node == NULL); > + request->registry = registry; > + return 0; > +} > + > +/* > + * Unregisters a previously registered request. Does nothing if the request has > + * already been unregistered or has never been registered. > + */ > +static void > +netbox_request_unregister(struct netbox_request *request) > +{ > + struct netbox_registry *registry = request->registry; > + if (registry == NULL) > + return; > + request->registry = NULL; > + struct mh_i64ptr_t *h = registry->requests; > + mh_int_t k = mh_i64ptr_find(h, request->sync, NULL); > + assert(k != mh_end(h)); > + assert(mh_i64ptr_node(h, k)->val == request); > + mh_i64ptr_del(h, k, NULL); > +} > + > +static bool > +netbox_request_is_ready(struct netbox_request *request) 4. The request can be made 'const'. > +{ > + return request->registry == NULL; > +} > + > +static void > +netbox_request_signal(struct netbox_request *request) > +{ > + fiber_cond_broadcast(&request->cond); > +} > + > +static void > +netbox_request_complete(struct netbox_request *request) > +{ > + netbox_request_unregister(request); > + netbox_request_signal(request); > +} > + > +/* > + * Waits on netbox_request::cond. Subtracts the wait time from the timeout. > + * Returns false on timeout or if the fiber was cancelled. > + */ > +static bool > +netbox_request_wait(struct netbox_request *request, double *timeout) > +{> + double ts = ev_monotonic_now(loop()); 5. fiber_clock() might be a little shorter (does the same). The same below. Although it is not inline. Up to you. Also see a related comment below. > + int rc = fiber_cond_wait_timeout(&request->cond, *timeout); > + *timeout -= ev_monotonic_now(loop()) - ts; > + return rc == 0; > +} > + > +static void 6. Have you tried making these 1-5 line functions explicitly inline? I remember with Mergen we saw some actual perf difference in SQL code when did so. Although that time the functions were in the header vs in a .c file. I see you used inline for some functions in this commit. Why not for the ones like these? <...> > + > +/* > + * Waits until the response is received for the given request and obtains the > + * result. Takes an optional timeout argument. > + * > + * See the comment to request.result() for the return value format. > + */ > +static int > +luaT_netbox_request_wait_result(struct lua_State *L) > +{ > + struct netbox_request *request = luaT_check_netbox_request(L, 1); > + double timeout = TIMEOUT_INFINITY; > + if (!lua_isnoneornil(L, 2)) { > + if (lua_type(L, 2) != LUA_TNUMBER || > + (timeout = lua_tonumber(L, 2)) < 0) > + luaL_error(L, "Usage: future:wait_result(timeout)"); > + } > + while (!netbox_request_is_ready(request)) { > + if (!netbox_request_wait(request, &timeout)) { > + luaL_testcancel(L); > + diag_set(ClientError, ER_TIMEOUT); 7. In some places you use box_error_raise(), in others the explicit diag_set(ClientError). Why? For instance: box_error_raise(ER_NO_CONNECTION, "%s", strerror(errno)); box_error_raise(ER_NO_CONNECTION, "Peer closed"); In others the raise is justified because you do not know the error code and its message at compile time there. In these 2 I probably do not know something? > + return luaT_push_nil_and_error(L); > + } > + } > + return netbox_request_push_result(request, L); > +} <...> > + > +static int > +luaT_netbox_request_pairs(struct lua_State *L) > +{ > + if (!lua_isnoneornil(L, 2)) { > + if (lua_type(L, 2) != LUA_TNUMBER || lua_tonumber(L, 2) < 0) > + luaL_error(L, "Usage: future:pairs(timeout)"); > + } else { > + if (lua_isnil(L, 2)) > + lua_pop(L, 1); > + lua_pushnumber(L, TIMEOUT_INFINITY); > + } > + lua_pushcclosure(L, luaT_netbox_request_iterator_next, 2); 8. Push of cfunctions, especially closures, on a regular basis might be expensive. Could you please try to make it a non-closure function and cache its reference like I showed in the proposal about request __gc? > + lua_pushnil(L); > + lua_pushinteger(L, 0); > + return 3; > +} <...> > + > +/* > + * Creates a request object (userdata) and pushes it to Lua stack. > + * > + * Takes the following arguments: > + * - requests: registry to register the new request with > + * - id: id (sync) to assign to the new request > + * - buffer: buffer (ibuf) to write the result to or nil > + * - skip_header: whether to skip header when writing the result to the buffer > + * - method: a value from the netbox_method enumeration > + * - on_push: on_push trigger function > + * - on_push_ctx: on_push trigger function argument > + * - format: tuple format to use for decoding the body or nil > + */ > +static int > +netbox_new_request(struct lua_State *L) > +{ > + struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); 9. Does it help perf if the requests are allocated on mempool? > + netbox_request_create(request); 10. Below you override a lot of fields initialized in netbox_request_create(). Do you really need this create(), can you inline it without the unnecessary assignments? > + luaL_getmetatable(L, netbox_request_typename); > + lua_setmetatable(L, -2); > + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); > + request->sync = luaL_touint64(L, 2); > + request->buffer = (struct ibuf *) lua_topointer(L, 3); > + lua_pushvalue(L, 3); > + request->buffer_ref = luaL_ref(L, LUA_REGISTRYINDEX); > + request->skip_header = lua_toboolean(L, 4); > + request->method = lua_tointeger(L, 5); > + assert(request->method < netbox_method_MAX); > + lua_pushvalue(L, 6); > + request->on_push_ref = luaL_ref(L, LUA_REGISTRYINDEX); > + lua_pushvalue(L, 7); > + request->on_push_ctx_ref = luaL_ref(L, LUA_REGISTRYINDEX); > + if (!lua_isnil(L, 8)) > + request->format = lbox_check_tuple_format(L, 8); > + else > + request->format = tuple_format_runtime; > + tuple_format_ref(request->format); > + if (netbox_request_register(request, registry) != 0) > + luaT_error(L); > + return 1; > +} > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > index 0ad6cac022f2..4bc66940ea2a 100644 > --- a/src/box/lua/net_box.lua > +++ b/src/box/lua/net_box.lua > @@ -468,20 +304,8 @@ local function create_transport(host, port, user, password, callback, > local id = next_request_id > encode_method(method, send_buf, id, ...) > next_request_id = next_id(id) > - -- Request in most cases has maximum 10 members: > - -- method, buffer, skip_header, id, cond, errno, response, > - -- on_push, on_push_ctx and format. > - local request = setmetatable(table_new(0, 10), request_mt) > - request.method = method > - request.buffer = buffer > - request.skip_header = skip_header > - request.id = id > - request.cond = fiber.cond() > - requests[id] = request > - request.on_push = on_push > - request.on_push_ctx = on_push_ctx > - request.format = format > - return request > + return internal.new_request(requests, id, buffer, skip_header, method, > + on_push, on_push_ctx, format) 11. You might want to try to cache internal.new_request globally, use it without '.' operator, and re-check the benches. Might be nothing or not, but '.' is definitely not free. The same for dispatch_response_iproto, dispatch_response_console. > end > > -- > @@ -502,76 +326,13 @@ local function create_transport(host, port, user, password, callback, > > local function dispatch_response_iproto(hdr, body_rpos, body_end) > local id = hdr[IPROTO_SYNC_KEY] > - local request = requests[id] > - if request == nil then -- nobody is waiting for the response > - return > - end > local status = hdr[IPROTO_STATUS_KEY] > - local body_len = body_end - body_rpos > - > - if status > IPROTO_CHUNK_KEY then > - -- Handle errors > - requests[id] = nil > - request.id = nil > - request.errno = band(status, IPROTO_ERRNO_MASK) > - request.response = decode_error(body_rpos, request.errno) > - request.cond:broadcast() > - return > - end > - > - local buffer = request.buffer > - if buffer ~= nil then > - -- Copy xrow.body to user-provided buffer > - if request.skip_header then > - -- Skip {[IPROTO_DATA_KEY] = ...} wrapper. > - local map_len, key > - map_len, body_rpos = decode_map_header(body_rpos, body_len) > - assert(map_len == 1) > - key, body_rpos = decode(body_rpos) > - assert(key == IPROTO_DATA_KEY) > - body_len = body_end - body_rpos > - end > - local wpos = buffer:alloc(body_len) > - ffi.copy(wpos, body_rpos, body_len) > - body_len = tonumber(body_len) > - if status == IPROTO_OK_KEY then > - request.response = body_len > - requests[id] = nil > - request.id = nil > - else > - request.on_push(request.on_push_ctx, body_len) > - end > - request.cond:broadcast() > - return > - end > - > - local real_end > - -- Decode xrow.body[DATA] to Lua objects > - if status == IPROTO_OK_KEY then > - request.response, real_end = decode_method(request.method, > - body_rpos, body_end, > - request.format) > - assert(real_end == body_end, "invalid body length") > - requests[id] = nil > - request.id = nil > - else > - local msg > - msg, real_end, request.errno = decode_push(body_rpos, body_end) 12. decode_push is not unused. > - assert(real_end == body_end, "invalid body length") > - request.on_push(request.on_push_ctx, msg) > - end > - request.cond:broadcast() > + internal.dispatch_response_iproto(requests, id, status, > + body_rpos, body_end) > end > > local function dispatch_response_console(rid, response) > - local request = requests[rid] > - if request == nil then -- nobody is waiting for the response > - return > - end > - request.id = nil > - requests[rid] = nil > - request.response = response > - request.cond:broadcast() > + internal.dispatch_response_console(requests, rid, response) > end > > local function new_request_id() >
On Mon, Aug 02, 2021 at 11:49:51PM +0200, Vladislav Shpilevoy wrote: > > +netbox_communicate(int fd, struct ibuf *send_buf, struct ibuf *recv_buf, > > + size_t limit, const void *boundary, size_t boundary_len, > > + double timeout, size_t *limit_or_boundary_pos) > > 1. IMO, the name looks really bulky. I would just call it `size` or `end_pos`. > Up to you. Right. Renamed to size. > > + diag_set(OutOfMemory, NETBOX_READAHEAD, > > + "ibuf", "recv_buf"); > > 2. For OutOfMemory errors as a rule we use the format > > diag_set(OutOfMemory, size, "name of the allocation function", > "name of the variable") > > So here it would be > > diag_set(OutOfMemory, NETBOX_READAHEAD, "ibuf_reserve", "p"); > > I know it is violated in a lot of old code and I gave up trying to > enforce it in new patches to exact that form. Up to you. > > The same in the other new OutOfMemory in the other patches. Fixed. > > + local hdr, body_rpos, body_end = internal.send_and_recv_iproto( > > 3. Indexing 'internal' via '.' is not free. It is a lookup > in a hash. You might want to save internal.send_and_recv_iproto into > a global variable when the module loads first time and use the > cached value. Non-cached version is a bit faster only for FFI, but > here you are using Lua C - cache should be good. > > > + connection:fd(), send_buf, recv_buf, timeout) > > Another idea is to cache 'connection:fd()' result into a variable in > the root of create_transport() function. And update it when the > connetion is re-established. Although you probably move this all to > C later as well, I didn't reach the last commits yet. The calling function is moved to C later in the patch set so these comments will become irrelevant. Regarding caching function name (instead of accessing via dot operator), eventually there will be only two hot C functions that could benefit from this: internal.perform_request internal.perform_async_request I tried caching their names, but saw no performance gain at all in my test. I also tried removing fiber_self and fiber_clock aliases from net_box.lua and accessing these functions as fiber.<name> - again no difference. I will do some research about this (try to bench this separately) and add a separate commit to cache the internal function names on top of the series if it helps. Will follow-up in reply to this email. Incremental diff is below. -- diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index cb80d2efa364..6d6d09acafdb 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -437,7 +437,7 @@ netbox_decode_greeting(lua_State *L) /** * Reads data from the given socket until the limit or boundary is reached. - * Returns 0 and sets *limit_or_boundary_pos to limit/boundary_pos on success. + * Returns 0 and sets *size to limit or boundary position on success. * On error returns -1 and sets diag. * * The need for this function arises from not wanting to @@ -452,7 +452,7 @@ netbox_decode_greeting(lua_State *L) static int netbox_communicate(int fd, struct ibuf *send_buf, struct ibuf *recv_buf, size_t limit, const void *boundary, size_t boundary_len, - double timeout, size_t *limit_or_boundary_pos) + double timeout, size_t *size) { const int NETBOX_READAHEAD = 16320; if (timeout < 0) { @@ -464,7 +464,7 @@ netbox_communicate(int fd, struct ibuf *send_buf, struct ibuf *recv_buf, /* reader serviced first */ check_limit: if (ibuf_used(recv_buf) >= limit) { - *limit_or_boundary_pos = limit; + *size = limit; return 0; } const char *p; @@ -472,7 +472,7 @@ check_limit: recv_buf->rpos, ibuf_used(recv_buf), boundary, boundary_len)) != NULL) { - *limit_or_boundary_pos = p - recv_buf->rpos; + *size = p - recv_buf->rpos; return 0; } @@ -480,7 +480,7 @@ check_limit: void *p = ibuf_reserve(recv_buf, NETBOX_READAHEAD); if (p == NULL) { diag_set(OutOfMemory, NETBOX_READAHEAD, - "ibuf", "recv_buf"); + "ibuf_reserve", "p"); return -1; } ssize_t rc = recv(
Did you try to port netbox to direct usage of ev_io instead of a dedicated fiber + coio_wait wrapper? Do you think it is worth trying? The only problem I see is the necessity to call on_connect/on_disconnect/on_schema_reload/... triggers somewhere but the scheduler fiber, because they are allowed to yield. The pros is that you won't call ev_io_start/ev_io_stop on each 'communicate' iteration. AFAIR, on Linux they lead to epoll_ctl() to add/remove a descriptor from epoll, and these calls are expensive. At that point the netbox state machine does not seem to be doing much in Lua. Even if you keep the fiber, it could be ported to C entirely. It is now only 270 lines including empty lines and comments.
>>> + local hdr, body_rpos, body_end = internal.send_and_recv_iproto(
>>
>> 3. Indexing 'internal' via '.' is not free. It is a lookup
>> in a hash. You might want to save internal.send_and_recv_iproto into
>> a global variable when the module loads first time and use the
>> cached value. Non-cached version is a bit faster only for FFI, but
>> here you are using Lua C - cache should be good.
>>
>>> + connection:fd(), send_buf, recv_buf, timeout)
>>
>> Another idea is to cache 'connection:fd()' result into a variable in
>> the root of create_transport() function. And update it when the
>> connetion is re-established. Although you probably move this all to
>> C later as well, I didn't reach the last commits yet.
>
> The calling function is moved to C later in the patch set so these
> comments will become irrelevant.
>
> Regarding caching function name (instead of accessing via dot operator),
> eventually there will be only two hot C functions that could benefit
> from this:
>
> internal.perform_request
> internal.perform_async_request
>
> I tried caching their names, but saw no performance gain at all in my
> test. I also tried removing fiber_self and fiber_clock aliases from
> net_box.lua and accessing these functions as fiber.<name> - again no
> difference.
Both can be explained by jitting. I wasn't sure if 'internal' is jitted,
but seems so. While jit works, there probably won't be any difference. Now
it is just inconsistent because you have a couple of methods cached:
local encode_method = internal.encode_method
local decode_greeting = internal.decode_greeting
The commit LGTM anyway.
Thanks for the patch! > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index 044f7d337ca7..1a615797d485 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -79,6 +79,8 @@ enum netbox_method { > }; > > struct netbox_registry { > + /* Next request id. */ Should start with /**. After that LGTM. > + uint64_t next_sync; > /* sync -> netbox_request */ > struct mh_i64ptr_t *requests; > };
Thanks for the patch! See 3 comments below. > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index 1a615797d485..85a45c54b979 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -850,30 +850,25 @@ netbox_send_and_recv_iproto(lua_State *L) > > /* > * Sends and receives data over a console connection. > - * Takes socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. > - * On success returns response (string). > - * On error returns nil, error. > + * Returns a pointer to a response string and its len. > + * On error returns NULL. > */ > -static int > -netbox_send_and_recv_console(lua_State *L) > +static const char * > +netbox_send_and_recv_console(int fd, struct ibuf *send_buf, > + struct ibuf *recv_buf, double timeout, > + size_t *response_len) 1. To be consistent with netbox_communicate() I would call the last argument 'size'. Up to you. <...> > + > +/* > + * Sets up console delimiter. Should be called before serving any requests. > + * Takes socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. > + * Returns none on success, error on failure. > + */ > +static int > +netbox_console_setup(struct lua_State *L) > +{ > + static const char setup_delimiter_cmd[] = > + "require('console').delimiter('$EOF$')\n"; > + static const size_t setup_delimiter_cmd_len = > + sizeof(setup_delimiter_cmd) - 1; > + static const char ok_response[] = "---\n...\n"; > + static const size_t ok_response_len = sizeof(ok_response) - 1; 2. Why do you make them static? Wouldn't it be enough to just store them on the stack? Btw, the same question for netbox_registry_meta, netbox_request_meta, net_box_lib. Why should they be static? It seems Lua anyway copies them somewhere, so they don't need to stay valid for the entire process' lifetime. > + > +/* > + * Processes console requests in a loop until an error. > + * Takes request registry, socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. > + * Returns the error that broke the loop. > + */ > +static int > +netbox_console_loop(struct lua_State *L) > +{ > + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); > + int fd = lua_tointeger(L, 2); > + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 3); > + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 4); > + double timeout = (!lua_isnoneornil(L, 5) ? > + lua_tonumber(L, 5) : TIMEOUT_INFINITY); 3. You never pass timeout as not nil. It is always TIMEOUT_INFINITY. > + uint64_t sync = registry->next_sync; > + while (true) { > + size_t response_len; > + const char *response = netbox_send_and_recv_console( > + fd, send_buf, recv_buf, timeout, &response_len); > + if (response == NULL) { > + luaL_testcancel(L); > + luaT_pusherror(L, box_error_last()); > + return 1; > + } > + netbox_dispatch_response_console(L, registry, sync++, > + response, response_len); > + } > }
Thanks for the patch!
> diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
> index 85a45c54b979..fde2a8772890 100644
> --- a/src/box/lua/net_box.c
> +++ b/src/box/lua/net_box.c
> @@ -1800,6 +1759,188 @@ netbox_dispatch_response_console(struct lua_State *L,
> netbox_request_complete(request);
> }
>
> +/*
> + * Performs an authorization request for an iproto connection.
> + * Takes user, password, salt, request registry, socket fd,
> + * send_buf (ibuf), recv_buf (ibuf), timeout.
> + * Returns schema_version on success, nil and error on failure.
> + */
> +static int
> +netbox_iproto_auth(struct lua_State *L)
> +{
> + size_t user_len;
> + const char *user = lua_tolstring(L, 1, &user_len);
> + size_t password_len;
> + const char *password = lua_tolstring(L, 2, &password_len);
> + size_t salt_len;
> + const char *salt = lua_tolstring(L, 3, &salt_len);
> + if (salt_len < SCRAMBLE_SIZE)
> + return luaL_error(L, "Invalid salt");
> + struct netbox_registry *registry = luaT_check_netbox_registry(L, 4);
> + int fd = lua_tointeger(L, 5);
> + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 6);
> + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 7);
> + double timeout = (!lua_isnoneornil(L, 8) ?
> + lua_tonumber(L, 8) : TIMEOUT_INFINITY);
Timeout is never passed, it is always TIMEOUT_INFINITY. The same in
netbox_iproto_schema and netbox_iproto_loop.
Thanks for the patch! On 23.07.2021 13:07, Vladimir Davydov via Tarantool-patches wrote: > So as not to call tree C functions per each request, let's merge them tree -> three. > and call the resulting function perform_async_request.
Thanks for the patch! See 4 comments below. > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index 844a1de613f2..684091cf898f 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -1621,43 +1621,71 @@ netbox_new_registry(struct lua_State *L) > * - format: tuple format to use for decoding the body or nil > * - ...: method-specific arguments passed to the encoder > */ > -static int > -netbox_perform_async_request(struct lua_State *L) > +static void > +netbox_perform_async_request_impl(struct lua_State *L, int idx, > + struct netbox_request *request) 1. Impl is a confusing name here. Not clear what it does exactly. I propose netbox_make_request() or netbox_send_request() or netbox_push_request() or something likewise. > { > - struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); > - netbox_request_create(request); > - luaL_getmetatable(L, netbox_request_typename); > - lua_setmetatable(L, -2); > - > /* Encode and write the request to the send buffer. */ > - struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); > - struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); > - enum netbox_method method = lua_tointeger(L, 5); > + struct netbox_registry *registry = luaT_check_netbox_registry(L, idx); > + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, idx + 1); > + enum netbox_method method = lua_tointeger(L, idx + 4); > assert(method < netbox_method_MAX); > uint64_t sync = registry->next_sync++; > - netbox_encode_method(L, 9, method, send_buf, sync); > + netbox_encode_method(L, idx + 8, method, send_buf, sync); > > /* Initialize and register the request object. */ > request->method = method; > request->sync = sync; > - request->buffer = (struct ibuf *) lua_topointer(L, 3); > - lua_pushvalue(L, 3); > + request->buffer = (struct ibuf *) lua_topointer(L, idx + 2); > + lua_pushvalue(L, idx + 2); > request->buffer_ref = luaL_ref(L, LUA_REGISTRYINDEX); > - request->skip_header = lua_toboolean(L, 4); > - lua_pushvalue(L, 6); > + request->skip_header = lua_toboolean(L, idx + 3); > + lua_pushvalue(L, idx + 5); > request->on_push_ref = luaL_ref(L, LUA_REGISTRYINDEX); > - lua_pushvalue(L, 7); > + lua_pushvalue(L, idx + 6); > request->on_push_ctx_ref = luaL_ref(L, LUA_REGISTRYINDEX); > - if (!lua_isnil(L, 8)) > - request->format = lbox_check_tuple_format(L, 8); > + if (!lua_isnil(L, idx + 7)) > + request->format = lbox_check_tuple_format(L, idx + 7); 2. To avoid most of that diff could you make the index relative from the beginning? Before this commit you could make 'int idx = 1;' and then for each next parameter use 'idx++'. Then this commit wouldn't change much here. > else > request->format = tuple_format_runtime; > tuple_format_ref(request->format); > if (netbox_request_register(request, registry) != 0) > luaT_error(L); > +} > + > +static int > +netbox_perform_async_request(struct lua_State *L) > +{ > + struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); > + netbox_request_create(request); > + luaL_getmetatable(L, netbox_request_typename); > + lua_setmetatable(L, -2); > + netbox_perform_async_request_impl(L, 1, request); > return 1; > } > > +static int > +netbox_perform_request(struct lua_State *L) > +{ > + double timeout = (!lua_isnil(L, 1) ? > + lua_tonumber(L, 1) : TIMEOUT_INFINITY); 2. This piece of code is repeated 6 times in the file. Maybe time to make it a function? > + struct netbox_request request; > + netbox_request_create(&request); > + netbox_perform_async_request_impl(L, 2, &request); > + while (!netbox_request_is_ready(&request)) { > + if (!netbox_request_wait(&request, &timeout)) { > + netbox_request_unregister(&request); > + netbox_request_destroy(&request); > + luaL_testcancel(L); > + diag_set(ClientError, ER_TIMEOUT); > + return luaT_push_nil_and_error(L); > + } > + } > + int ret = netbox_request_push_result(&request, L); > + netbox_request_destroy(&request); > + return ret; > +} > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > index 55d172a1f6b9..d6367a848aa1 100644 > --- a/src/box/lua/net_box.lua > +++ b/src/box/lua/net_box.lua > @@ -253,19 +253,27 @@ local function create_transport(host, port, user, password, callback, > end > end > > - -- > - -- Send a request and do not wait for response. > - -- @retval nil, error Error occured. > - -- @retval not nil Future object. > - -- > - local function perform_async_request(buffer, skip_header, method, on_push, > - on_push_ctx, format, ...) > + local function check_active() > if state ~= 'active' and state ~= 'fetch_schema' then > local code = last_errno or E_NO_CONNECTION > local msg = last_error or > string.format('Connection is not established, state is "%s"', > state) > - return nil, box.error.new({code = code, reason = msg}) > + return box.error.new({code = code, reason = msg}) > + end > + return nil 3. You can drop this return, it does not do anything (given how the function is used). > + end > @@ -284,13 +292,18 @@ local function create_transport(host, port, user, password, callback, > -- > local function perform_request(timeout, buffer, skip_header, method, > on_push, on_push_ctx, format, ...) > - local request, err = > - perform_async_request(buffer, skip_header, method, on_push, > - on_push_ctx, format, ...) > - if not request then > + local err = check_active() > + if err then > return nil, err > end > - return request:wait_result(timeout) > + -- alert worker to notify it of the queued outgoing data; > + -- if the buffer wasn't empty, assume the worker was already alerted > + if send_buf:size() == 0 then > + worker_fiber:wakeup() > + end 4. Why not extract it all into a function along with check_active? Such as 'prepare_request()' or something. These blocks until their 'return's are exactly the same. > + return internal.perform_request(timeout, requests, send_buf, buffer, > + skip_header, method, on_push, > + on_push_ctx, format, ...) > end
On Mon, Aug 02, 2021 at 11:54:43PM +0200, Vladislav Shpilevoy wrote: > In an early version I saw on the branch you tried to keep the > discarded requests in the hash. It is not so anymore, correct? > Now discarded requests are GCed even their response didn't arrive > yet? (It should be so, but I decided to clarify just in case.) Both original version and the current version pushed to the branch remove discarded requests from the hash (both luaT_netbox_request_gc and luaT_netbox_request_discard call netbox_request_unregister, which removes the request from the hash table). > > +struct netbox_registry { > > + /* sync -> netbox_request */ > > 1. For out of function comments we usually use /**. This includes the > comments for structs and their members. Fixed here, there, and everywhere. > > +struct netbox_request { > > + /* > > + * Reference to the request result or LUA_NOREF if the response hasn't > > + * been received yet. If the response was decoded to a user-provided > > + * buffer, the result stores the length of the decoded data. > > 2. Do you mean result_ref stores the length? I can't find it in the code. > It always stores a real Lua ref from what I see. Yes, it always stores a Lua ref. In case the result is written to a user-provided buffer, it stores a reference to the result lenght. Updated the comment to clarify that. > > + */ > > + int result_ref; > > + /* > > + * Error if the request failed (ref incremented). NULL on success or if > > + * the response hasn't been received yet. > > + */ > > + struct error *error; > > + > > 3. Extra empty line. Removed. > > +static bool > > +netbox_request_is_ready(struct netbox_request *request) > > 4. The request can be made 'const'. Added. > > +static bool > > +netbox_request_wait(struct netbox_request *request, double *timeout) > > +{ > > + double ts = ev_monotonic_now(loop()); > > 5. fiber_clock() might be a little shorter (does the same). The same below. > Although it is not inline. Up to you. Also see a related comment below. We use ev_monotonic_now in more places in the code so I'm going to stick to it: ~/src/tarantool$ git grep fiber_clock src | grep -c -v -e '\.lua:' 19 ~/src/tarantool$ git grep ev_monotonic_now src | grep -c -v -e '\.lua:' 60 > > 6. Have you tried making these 1-5 line functions explicitly inline? > I remember with Mergen we saw some actual perf difference in SQL code > when did so. Although that time the functions were in the header vs in > a .c file. > > I see you used inline for some functions in this commit. Why not for > the ones like these? Marked all short, frequently called functions 'inline'. It didn't affect the test results though. > > +static int > > +luaT_netbox_request_wait_result(struct lua_State *L) > > +{ > > + struct netbox_request *request = luaT_check_netbox_request(L, 1); > > + double timeout = TIMEOUT_INFINITY; > > + if (!lua_isnoneornil(L, 2)) { > > + if (lua_type(L, 2) != LUA_TNUMBER || > > + (timeout = lua_tonumber(L, 2)) < 0) > > + luaL_error(L, "Usage: future:wait_result(timeout)"); > > + } > > + while (!netbox_request_is_ready(request)) { > > + if (!netbox_request_wait(request, &timeout)) { > > + luaL_testcancel(L); > > + diag_set(ClientError, ER_TIMEOUT); > > 7. In some places you use box_error_raise(), in others the > explicit diag_set(ClientError). Why? For instance: > > box_error_raise(ER_NO_CONNECTION, "%s", strerror(errno)); > box_error_raise(ER_NO_CONNECTION, "Peer closed"); > > In others the raise is justified because you do not know the > error code and its message at compile time there. In these 2 > I probably do not know something? box_error_raise() allows you to set an arbitrary error message while diag_set() uses error messages corresponding to error codes (src/box/errcode.h). E.g. for ER_NO_CONNECTION, it's always "Connection is not established". I use box_error_raise() here in order to avoid changing error messages which were reported by Lua code originally. > > +static int > > +luaT_netbox_request_pairs(struct lua_State *L) > > +{ > > + if (!lua_isnoneornil(L, 2)) { > > + if (lua_type(L, 2) != LUA_TNUMBER || lua_tonumber(L, 2) < 0) > > + luaL_error(L, "Usage: future:pairs(timeout)"); > > + } else { > > + if (lua_isnil(L, 2)) > > + lua_pop(L, 1); > > + lua_pushnumber(L, TIMEOUT_INFINITY); > > + } > > + lua_pushcclosure(L, luaT_netbox_request_iterator_next, 2); > > 8. Push of cfunctions, especially closures, on a regular basis might > be expensive. Could you please try to make it a non-closure function > and cache its reference like I showed in the proposal about request __gc? My test doesn't check performance of the request iterator so I need to come up with a new test first to check this hypothesis. I'll reply to this email with the results once they're ready. > > +static int > > +netbox_new_request(struct lua_State *L) > > +{ > > + struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); > > 9. Does it help perf if the requests are allocated on mempool? I tried to allocate requests on mempool. To achieve that, we have to turn them into C data. It resulted in a performance degradation, see the parallel thread: https://lists.tarantool.org/pipermail/tarantool-patches/2021-July/025039.html The code is here (last two commits): https://github.com/tarantool/tarantool/tree/vdavydov/net-box-optimization-cdata-request Lua does a lot of malloc calls under the hood. Eliminating just this one newuserdata doesn't seem to make any difference. > > + netbox_request_create(request); > > 10. Below you override a lot of fields initialized in > netbox_request_create(). Do you really need this create(), can you > inline it without the unnecessary assignments? Right. Removed netbox_request_create. > > + luaL_getmetatable(L, netbox_request_typename); > > + lua_setmetatable(L, -2); > > + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); > > + request->sync = luaL_touint64(L, 2); > > + request->buffer = (struct ibuf *) lua_topointer(L, 3); > > + lua_pushvalue(L, 3); > > + request->buffer_ref = luaL_ref(L, LUA_REGISTRYINDEX); > > + request->skip_header = lua_toboolean(L, 4); > > + request->method = lua_tointeger(L, 5); > > + assert(request->method < netbox_method_MAX); > > + lua_pushvalue(L, 6); > > + request->on_push_ref = luaL_ref(L, LUA_REGISTRYINDEX); > > + lua_pushvalue(L, 7); > > + request->on_push_ctx_ref = luaL_ref(L, LUA_REGISTRYINDEX); > > + if (!lua_isnil(L, 8)) > > + request->format = lbox_check_tuple_format(L, 8); > > + else > > + request->format = tuple_format_runtime; > > + tuple_format_ref(request->format); > > + if (netbox_request_register(request, registry) != 0) > > + luaT_error(L); > > + return 1; > > +} > > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > > index 0ad6cac022f2..4bc66940ea2a 100644 > > --- a/src/box/lua/net_box.lua > > +++ b/src/box/lua/net_box.lua > > @@ -468,20 +304,8 @@ local function create_transport(host, port, user, password, callback, > > local id = next_request_id > > encode_method(method, send_buf, id, ...) > > next_request_id = next_id(id) > > - -- Request in most cases has maximum 10 members: > > - -- method, buffer, skip_header, id, cond, errno, response, > > - -- on_push, on_push_ctx and format. > > - local request = setmetatable(table_new(0, 10), request_mt) > > - request.method = method > > - request.buffer = buffer > > - request.skip_header = skip_header > > - request.id = id > > - request.cond = fiber.cond() > > - requests[id] = request > > - request.on_push = on_push > > - request.on_push_ctx = on_push_ctx > > - request.format = format > > - return request > > + return internal.new_request(requests, id, buffer, skip_header, method, > > + on_push, on_push_ctx, format) > > 11. You might want to try to cache internal.new_request globally, > use it without '.' operator, and re-check the benches. Might be nothing > or not, but '.' is definitely not free. The same for > dispatch_response_iproto, dispatch_response_console. Replied in the parallel thread: https://lists.tarantool.org/pipermail/tarantool-patches/2021-August/025137.html > > - msg, real_end, request.errno = decode_push(body_rpos, body_end) > > 12. decode_push is not unused. Deleted it. The incremental diff is below. -- diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 6859e955ac6e..e7cb5b3d810f 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -79,79 +79,63 @@ enum netbox_method { }; struct netbox_registry { - /* sync -> netbox_request */ + /** sync -> netbox_request */ struct mh_i64ptr_t *requests; }; struct netbox_request { enum netbox_method method; - /* + /** * Unique identifier needed for matching the request with its response. * Used as a key in the registry. */ uint64_t sync; - /* + /** * The registry this request belongs to or NULL if the request has been * completed. */ struct netbox_registry *registry; - /* Format used for decoding the response (ref incremented). */ + /** Format used for decoding the response (ref incremented). */ struct tuple_format *format; - /* Signaled when the response is received. */ + /** Signaled when the response is received. */ struct fiber_cond cond; - /* + /** * A user-provided buffer to which the response body should be copied. * If NULL, the response will be decoded to Lua stack. */ struct ibuf *buffer; - /* + /** * Lua reference to the buffer. Used to prevent garbage collection in * case the user discards the request. */ int buffer_ref; - /* + /** * Whether to skip MessagePack map header and IPROTO_DATA key when * copying the response body to a user-provided buffer. Ignored if * buffer is not set. */ bool skip_header; - /* Lua references to on_push trigger and its context. */ + /** Lua references to on_push trigger and its context. */ int on_push_ref; int on_push_ctx_ref; - /* - * Reference to the request result or LUA_NOREF if the response hasn't - * been received yet. If the response was decoded to a user-provided - * buffer, the result stores the length of the decoded data. + /** + * Lua reference to the request result or LUA_NOREF if the response + * hasn't been received yet. If the response was decoded to a + * user-provided buffer (see buffer_ref), result_ref stores a Lua + * reference to an integer value that contains the length of the + * decoded data. */ int result_ref; - /* + /** * Error if the request failed (ref incremented). NULL on success or if * the response hasn't been received yet. */ struct error *error; - }; static const char netbox_registry_typename[] = "net.box.registry"; static const char netbox_request_typename[] = "net.box.request"; -static void -netbox_request_create(struct netbox_request *request) -{ - request->method = netbox_method_MAX; - request->sync = -1; - request->registry = NULL; - request->format = NULL; - fiber_cond_create(&request->cond); - request->buffer = NULL; - request->buffer_ref = LUA_REFNIL; - request->skip_header = false; - request->on_push_ref = LUA_REFNIL; - request->on_push_ctx_ref = LUA_REFNIL; - request->result_ref = LUA_NOREF; - request->error = NULL; -} - static void netbox_request_destroy(struct netbox_request *request) { @@ -206,19 +190,19 @@ netbox_request_unregister(struct netbox_request *request) mh_i64ptr_del(h, k, NULL); } -static bool -netbox_request_is_ready(struct netbox_request *request) +static inline bool +netbox_request_is_ready(const struct netbox_request *request) { return request->registry == NULL; } -static void +static inline void netbox_request_signal(struct netbox_request *request) { fiber_cond_broadcast(&request->cond); } -static void +static inline void netbox_request_complete(struct netbox_request *request) { netbox_request_unregister(request); @@ -229,7 +213,7 @@ netbox_request_complete(struct netbox_request *request) * Waits on netbox_request::cond. Subtracts the wait time from the timeout. * Returns false on timeout or if the fiber was cancelled. */ -static bool +static inline bool netbox_request_wait(struct netbox_request *request, double *timeout) { double ts = ev_monotonic_now(loop()); @@ -238,14 +222,14 @@ netbox_request_wait(struct netbox_request *request, double *timeout) return rc == 0; } -static void +static inline void netbox_request_set_result(struct netbox_request *request, int result_ref) { assert(request->result_ref == LUA_NOREF); request->result_ref = result_ref; } -static void +static inline void netbox_request_set_error(struct netbox_request *request, struct error *error) { assert(request->error == NULL); @@ -297,7 +281,7 @@ netbox_registry_destroy(struct netbox_registry *registry) /** * Looks up a request by id (sync). Returns NULL if not found. */ -static struct netbox_request * +static inline struct netbox_request * netbox_registry_lookup(struct netbox_registry *registry, uint64_t sync) { struct mh_i64ptr_t *h = registry->requests; @@ -1637,9 +1621,6 @@ static int netbox_new_request(struct lua_State *L) { struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); - netbox_request_create(request); - luaL_getmetatable(L, netbox_request_typename); - lua_setmetatable(L, -2); struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); request->sync = luaL_touint64(L, 2); request->buffer = (struct ibuf *)lua_topointer(L, 3); @@ -1657,8 +1638,15 @@ netbox_new_request(struct lua_State *L) else request->format = tuple_format_runtime; tuple_format_ref(request->format); - if (netbox_request_register(request, registry) != 0) + fiber_cond_create(&request->cond); + request->result_ref = LUA_NOREF; + request->error = NULL; + if (netbox_request_register(request, registry) != 0) { + netbox_request_destroy(request); luaT_error(L); + } + luaL_getmetatable(L, netbox_request_typename); + lua_setmetatable(L, -2); return 1; } diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 1379d04268d9..20dec5648926 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -68,11 +68,6 @@ local M_INJECT = 17 -- utility tables local is_final_state = {closed = 1, error = 1} -local function decode_push(raw_data) - local response, raw_end = decode(raw_data) - return response[IPROTO_DATA_KEY][1], raw_end -end - local function version_id(major, minor, patch) return bit.bor(bit.lshift(major, 16), bit.lshift(minor, 8), patch) end
On Wed, Aug 04, 2021 at 01:05:37AM +0200, Vladislav Shpilevoy wrote: > Did you try to port netbox to direct usage of ev_io instead of a dedicated > fiber + coio_wait wrapper? Do you think it is worth trying? The only problem > I see is the necessity to call on_connect/on_disconnect/on_schema_reload/... > triggers somewhere but the scheduler fiber, because they are allowed to yield. > > The pros is that you won't call ev_io_start/ev_io_stop on each 'communicate' > iteration. AFAIR, on Linux they lead to epoll_ctl() to add/remove a descriptor > from epoll, and these calls are expensive. > > At that point the netbox state machine does not seem to be doing much in Lua. > Even if you keep the fiber, it could be ported to C entirely. It is now only > 270 lines including empty lines and comments. No, I haven't tried or even planned to do that, but I agree it might improve overall net.box performance - at the very least it will reduce the number of Lua function calls, because perform_async_request and perform_request will be called directly, without Lua wrappers. Still, this doesn't block this series and we can easily do it on top. I opened a ticket so that we don't forget: https://github.com/tarantool/tarantool/issues/6291
On Wed, Aug 04, 2021 at 01:06:04AM +0200, Vladislav Shpilevoy wrote: > >>> + local hdr, body_rpos, body_end = internal.send_and_recv_iproto( > >> > >> 3. Indexing 'internal' via '.' is not free. It is a lookup > >> in a hash. You might want to save internal.send_and_recv_iproto into > >> a global variable when the module loads first time and use the > >> cached value. Non-cached version is a bit faster only for FFI, but > >> here you are using Lua C - cache should be good. > >> > >>> + connection:fd(), send_buf, recv_buf, timeout) > >> > >> Another idea is to cache 'connection:fd()' result into a variable in > >> the root of create_transport() function. And update it when the > >> connetion is re-established. Although you probably move this all to > >> C later as well, I didn't reach the last commits yet. > > > > The calling function is moved to C later in the patch set so these > > comments will become irrelevant. > > > > Regarding caching function name (instead of accessing via dot operator), > > eventually there will be only two hot C functions that could benefit > > from this: > > > > internal.perform_request > > internal.perform_async_request > > > > I tried caching their names, but saw no performance gain at all in my > > test. I also tried removing fiber_self and fiber_clock aliases from > > net_box.lua and accessing these functions as fiber.<name> - again no > > difference. > > Both can be explained by jitting. I wasn't sure if 'internal' is jitted, > but seems so. While jit works, there probably won't be any difference. Now > it is just inconsistent because you have a couple of methods cached: > > local encode_method = internal.encode_method > local decode_greeting = internal.decode_greeting I compared performance of fiber_self vs fiber.self by running it 100M times in a loop. Here are the results over 10 runs: fiber.self : 15.279 +- 0.021 MRPS fiber_self : 15.357 +- 0.023 MRPS (fiber.self is a Lua C function) So caching function name does result in 0.5% boost. Okay, let's add aliases for the hot functions. Here's the patch I applied on top of the series: -- From 58687162d5102ffabc42fb2ef9a561ee7b218ab1 Mon Sep 17 00:00:00 2001 From: Vladimir Davydov <vdavydov@tarantool.org> Date: Wed, 4 Aug 2021 16:51:06 +0300 Subject: [PATCH] net.box: add Lua aliases for perform_request and perform_async_request These two are hot functions. Let's add aliases for them to avoid a lookup in a Lua table on each call. At the same time, internal.decode_greeting is called only when a connection is established so we don't need to have an alias for it. Part of #6241 diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 9da900933fbc..ad65a3dd528f 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -18,7 +18,8 @@ local check_index_arg = box.internal.check_index_arg local check_space_arg = box.internal.check_space_arg local check_primary_index = box.internal.check_primary_index -local decode_greeting = internal.decode_greeting +local perform_request_impl = internal.perform_request +local perform_async_request_impl = internal.perform_async_request local TIMEOUT_INFINITY = 500 * 365 * 86400 local VSPACE_ID = 281 @@ -79,7 +80,7 @@ local function establish_connection(host, port, timeout) s:close() return nil, err end - local greeting, err = decode_greeting(msg) + local greeting, err = internal.decode_greeting(msg) if not greeting then s:close() return nil, err @@ -280,9 +281,9 @@ local function create_transport(host, port, user, password, callback, if send_buf:size() == 0 then worker_fiber:wakeup() end - return internal.perform_async_request(requests, send_buf, buffer, - skip_header, method, on_push, - on_push_ctx, format, ...) + return perform_async_request_impl(requests, send_buf, buffer, + skip_header, method, on_push, + on_push_ctx, format, ...) end -- @@ -301,9 +302,9 @@ local function create_transport(host, port, user, password, callback, if send_buf:size() == 0 then worker_fiber:wakeup() end - return internal.perform_request(timeout, requests, send_buf, buffer, - skip_header, method, on_push, - on_push_ctx, format, ...) + return perform_request_impl(timeout, requests, send_buf, buffer, + skip_header, method, on_push, on_push_ctx, + format, ...) end -- PROTOCOL STATE MACHINE (WORKER FIBER) --
On Wed, Aug 04, 2021 at 03:30:01PM +0300, Vladimir Davydov wrote: > On Mon, Aug 02, 2021 at 11:54:43PM +0200, Vladislav Shpilevoy wrote: > > > + lua_pushcclosure(L, luaT_netbox_request_iterator_next, 2); > > > > 8. Push of cfunctions, especially closures, on a regular basis might > > be expensive. Could you please try to make it a non-closure function > > and cache its reference like I showed in the proposal about request __gc? > > My test doesn't check performance of the request iterator so I need to > come up with a new test first to check this hypothesis. I'll reply to > this email with the results once they're ready. Using getref instead of pushcclosure does improve performance of the final version by about 10%: KRPS (WALL TIME) KRPS (PROC TIME) pushcclosure : 233 +- 3 : 344 +- 7 getref : 259 +- 4 : 404 +- 10 The test is here: https://gist.github.com/locker/9df4eaaf1bc889b16706640711c946f7 Amended the patch, thanks. The incremental diff is below: -- diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 7ef40792320e..fadd71a6813b 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -131,11 +131,20 @@ struct netbox_request { * the response hasn't been received yet. */ struct error *error; + /** Timeout for luaT_netbox_request_iterator_next(). */ + double iterator_timeout; }; static const char netbox_registry_typename[] = "net.box.registry"; static const char netbox_request_typename[] = "net.box.request"; +/** + * Instead of pushing luaT_netbox_request_iterator_next with lua_pushcclosure + * in luaT_netbox_request_pairs, we get it by reference, because it works + * faster. + */ +static int luaT_netbox_request_iterator_next_ref; + static void netbox_request_destroy(struct netbox_request *request) { @@ -1505,9 +1513,8 @@ luaT_netbox_request_discard(struct lua_State *L) static int luaT_netbox_request_iterator_next(struct lua_State *L) { - struct netbox_request *request = luaT_check_netbox_request( - L, lua_upvalueindex(1)); - double timeout = lua_tonumber(L, lua_upvalueindex(2)); + struct netbox_request *request = lua_touserdata(L, 1); + double timeout = request->iterator_timeout; if (luaL_isnull(L, 2)) { /* The previous call returned an error. */ goto stop; @@ -1575,16 +1582,17 @@ error: static int luaT_netbox_request_pairs(struct lua_State *L) { + struct netbox_request *request = luaT_check_netbox_request(L, 1); + double timeout = TIMEOUT_INFINITY; if (!lua_isnoneornil(L, 2)) { - if (lua_type(L, 2) != LUA_TNUMBER || lua_tonumber(L, 2) < 0) + if (lua_type(L, 2) != LUA_TNUMBER || + (timeout = lua_tonumber(L, 2)) < 0) luaL_error(L, "Usage: future:pairs(timeout)"); - } else { - if (lua_isnil(L, 2)) - lua_pop(L, 1); - lua_pushnumber(L, TIMEOUT_INFINITY); } - lua_pushcclosure(L, luaT_netbox_request_iterator_next, 2); - lua_pushnil(L); + request->iterator_timeout = timeout; + lua_rawgeti(L, LUA_REGISTRYINDEX, + luaT_netbox_request_iterator_next_ref); + lua_insert(L, 1); lua_pushinteger(L, 0); return 3; } @@ -1750,6 +1758,9 @@ netbox_dispatch_response_console(struct lua_State *L) int luaopen_net_box(struct lua_State *L) { + lua_pushcfunction(L, luaT_netbox_request_iterator_next); + luaT_netbox_request_iterator_next_ref = luaL_ref(L, LUA_REGISTRYINDEX); + static const struct luaL_Reg netbox_registry_meta[] = { { "__gc", luaT_netbox_registry_gc }, { "reset", luaT_netbox_registry_reset },
On Wed, Aug 04, 2021 at 06:35:26PM +0300, Vladimir Davydov wrote: > On Wed, Aug 04, 2021 at 03:30:01PM +0300, Vladimir Davydov wrote: > > On Mon, Aug 02, 2021 at 11:54:43PM +0200, Vladislav Shpilevoy wrote: > > > > + lua_pushcclosure(L, luaT_netbox_request_iterator_next, 2); > > > > > > 8. Push of cfunctions, especially closures, on a regular basis might > > > be expensive. Could you please try to make it a non-closure function > > > and cache its reference like I showed in the proposal about request __gc? > > > > My test doesn't check performance of the request iterator so I need to > > come up with a new test first to check this hypothesis. I'll reply to > > this email with the results once they're ready. > > Using getref instead of pushcclosure does improve performance of > the final version by about 10%: > > KRPS (WALL TIME) KRPS (PROC TIME) > pushcclosure : 233 +- 3 : 344 +- 7 > getref : 259 +- 4 : 404 +- 10 > > The test is here: > > https://gist.github.com/locker/9df4eaaf1bc889b16706640711c946f7 > > Amended the patch, thanks. The incremental diff is below: > -- > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index 7ef40792320e..fadd71a6813b 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -131,11 +131,20 @@ struct netbox_request { > * the response hasn't been received yet. > */ > struct error *error; > + /** Timeout for luaT_netbox_request_iterator_next(). */ > + double iterator_timeout; > }; It's totally wrong to store iterator_timeout in netbox_request, because there may be more than one iterator open for the same request. Instead we should push a table with {request, timeout}, just like the Lua implementation did. This is a bit slower, but still faster than using pushcclosure: KRPS (WALL TIME) KRPS (PROC TIME) pushcclosure : 233 +- 3 : 344 +- 7 getref : 259 +- 4 : 404 +- 10 getref + table : 257 +- 7 : 397 +- 15 The new incremental diff is below: -- diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index de9c11736603..45b0c7f3bac3 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -136,6 +136,13 @@ struct netbox_request { static const char netbox_registry_typename[] = "net.box.registry"; static const char netbox_request_typename[] = "net.box.request"; +/** + * Instead of pushing luaT_netbox_request_iterator_next with lua_pushcclosure + * in luaT_netbox_request_pairs, we get it by reference, because it works + * faster. + */ +static int luaT_netbox_request_iterator_next_ref; + static void netbox_request_destroy(struct netbox_request *request) { @@ -1488,8 +1495,8 @@ luaT_netbox_request_discard(struct lua_State *L) /** * Gets the next message or the final result. Takes the index of the last - * returned message as a second argument (the first argument is ignored). - * The request and timeout are passed as up-values (see request.pairs()). + * returned message as a second argument. The request and timeout are passed in + * the first argument as a table (see request.pairs()). * * On success returns the index of the current message (used by the iterator * implementation to continue iteration) and an object, which is either the @@ -1504,9 +1511,12 @@ luaT_netbox_request_discard(struct lua_State *L) static int luaT_netbox_request_iterator_next(struct lua_State *L) { - struct netbox_request *request = luaT_check_netbox_request( - L, lua_upvalueindex(1)); - double timeout = lua_tonumber(L, lua_upvalueindex(2)); + /* The first argument is a table: {request, timeout}. */ + lua_rawgeti(L, 1, 1); + struct netbox_request *request = luaT_check_netbox_request(L, -1); + lua_rawgeti(L, 1, 2); + double timeout = lua_tonumber(L, -1); + /* The second argument is the index of the last returned message. */ if (luaL_isnull(L, 2)) { /* The previous call returned an error. */ goto stop; @@ -1582,8 +1592,17 @@ luaT_netbox_request_pairs(struct lua_State *L) lua_pop(L, 1); lua_pushnumber(L, TIMEOUT_INFINITY); } - lua_pushcclosure(L, luaT_netbox_request_iterator_next, 2); - lua_pushnil(L); + lua_settop(L, 2); + /* Create a table passed to next(): {request, timeout}. */ + lua_createtable(L, 2, 0); + lua_insert(L, 1); + lua_rawseti(L, 1, 2); /* timeout */ + lua_rawseti(L, 1, 1); /* request */ + /* Push the next() function. It must go first. */ + lua_rawgeti(L, LUA_REGISTRYINDEX, + luaT_netbox_request_iterator_next_ref); + lua_insert(L, 1); + /* Push the iterator index. */ lua_pushinteger(L, 0); return 3; } @@ -1749,6 +1768,9 @@ netbox_dispatch_response_console(struct lua_State *L) int luaopen_net_box(struct lua_State *L) { + lua_pushcfunction(L, luaT_netbox_request_iterator_next); + luaT_netbox_request_iterator_next_ref = luaL_ref(L, LUA_REGISTRYINDEX); + static const struct luaL_Reg netbox_registry_meta[] = { { "__gc", luaT_netbox_registry_gc }, { "reset", luaT_netbox_registry_reset },
On Wed, Aug 04, 2021 at 01:06:17AM +0200, Vladislav Shpilevoy wrote:
> > struct netbox_registry {
> > + /* Next request id. */
>
> Should start with /**. After that LGTM.
Fixed.
Hi! Thanks for the fixes! > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index 19289e750..6d6d09aca 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c <...> > + > +/** > + * Sends and receives data over an iproto connection. > + * Takes socket fd, send_buf (ibuf), recv_buf (ibuf), timeout. > + * On success returns header (table), body_rpos (char *), body_end (char *). > + * On error returns nil, error. > + */ > +static int > +netbox_send_and_recv_iproto(lua_State *L) > +{ > + int fd = lua_tointeger(L, 1); > + struct ibuf *send_buf = (struct ibuf *)lua_topointer(L, 2); > + struct ibuf *recv_buf = (struct ibuf *)lua_topointer(L, 3); > + double timeout = (!lua_isnoneornil(L, 4) ? > + lua_tonumber(L, 4) : TIMEOUT_INFINITY); Timeout is never passed to this function. It is always TIMEOUT_INFINITY. The same for netbox_send_and_recv_console.
>>> +static int
>>> +netbox_new_request(struct lua_State *L)
>>> +{
>>> + struct netbox_request *request = lua_newuserdata(L, sizeof(*request));
>>
>> 9. Does it help perf if the requests are allocated on mempool?
>
> I tried to allocate requests on mempool. To achieve that, we have to
> turn them into C data. It resulted in a performance degradation, see the
> parallel thread:
It does not necessarily need to be cdata. You can use
lua_newuserdata(L, sizeof(void *)) and store pointers at the requests which
you allocate on mempool. The downside is that there will be +1 dereference for
each access. The upside is that Lua might have optimizations for small userdata
objects. For example, ffi.new() for objects < 128 bytes is order of magnitude
faster than for bigger objects. Something similar might exist for userdata.
On Wed, Aug 04, 2021 at 11:18:55PM +0200, Vladislav Shpilevoy wrote: > > +static int > > +netbox_send_and_recv_iproto(lua_State *L) > > +{ > > + int fd = lua_tointeger(L, 1); > > + struct ibuf *send_buf = (struct ibuf *)lua_topointer(L, 2); > > + struct ibuf *recv_buf = (struct ibuf *)lua_topointer(L, 3); > > + double timeout = (!lua_isnoneornil(L, 4) ? > > + lua_tonumber(L, 4) : TIMEOUT_INFINITY); > > Timeout is never passed to this function. It is always TIMEOUT_INFINITY. > The same for netbox_send_and_recv_console. Removed the unused timeout argument in a separate patch and rebased the rest of the series on top: From 6cceb3ecc0f326c212103e6319226d40b2223d31 Mon Sep 17 00:00:00 2001 From: Vladimir Davydov <vdavydov@tarantool.org> Date: Thu, 5 Aug 2021 10:52:13 +0300 Subject: [PATCH] net.box: remove unused timeout argument from IO methods Part of #6241 diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 19289e750c98..ee3cfe95a49c 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -436,7 +436,7 @@ netbox_decode_greeting(lua_State *L) } /** - * communicate(fd, send_buf, recv_buf, limit_or_boundary, timeout) + * communicate(fd, send_buf, recv_buf, limit_or_boundary) * -> errno, error * -> nil, limit/boundary_pos * @@ -467,15 +467,6 @@ netbox_communicate(lua_State *L) else limit = lua_tonumber(L, 4); - /* timeout */ - ev_tstamp timeout = TIMEOUT_INFINITY; - if (lua_type(L, 5) == LUA_TNUMBER) - timeout = lua_tonumber(L, 5); - if (timeout < 0) { - lua_pushinteger(L, ER_TIMEOUT); - lua_pushstring(L, "Timeout exceeded"); - return 2; - } int revents = COIO_READ; while (true) { /* reader serviced first */ @@ -526,18 +517,10 @@ check_limit: goto handle_error; } - ev_tstamp deadline = ev_monotonic_now(loop()) + timeout; ERROR_INJECT_YIELD(ERRINJ_NETBOX_IO_DELAY); revents = coio_wait(fd, EV_READ | (ibuf_used(send_buf) != 0 ? - EV_WRITE : 0), timeout); + EV_WRITE : 0), TIMEOUT_INFINITY); luaL_testcancel(L); - timeout = deadline - ev_monotonic_now(loop()); - timeout = MAX(0.0, timeout); - if (revents == 0 && timeout == 0.0) { - lua_pushinteger(L, ER_TIMEOUT); - lua_pushstring(L, "Timeout exceeded"); - return 2; - } } handle_error: lua_pushinteger(L, ER_NO_CONNECTION); diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 242b1c8d9314..3b2d60b39a0d 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -581,12 +581,12 @@ local function create_transport(host, port, user, password, callback, end -- IO (WORKER FIBER) -- - local function send_and_recv(limit_or_boundary, timeout) + local function send_and_recv(limit_or_boundary) return communicate(connection:fd(), send_buf, recv_buf, - limit_or_boundary, timeout) + limit_or_boundary) end - local function send_and_recv_iproto(timeout) + local function send_and_recv_iproto() local data_len = recv_buf.wpos - recv_buf.rpos local required if data_len < 5 then @@ -603,17 +603,16 @@ local function create_transport(host, port, user, password, callback, return nil, hdr, body_rpos, body_end end end - local deadline = fiber_clock() + (timeout or TIMEOUT_INFINITY) - local err, extra = send_and_recv(required, timeout) + local err, extra = send_and_recv(required) if err then return err, extra end - return send_and_recv_iproto(max(0, deadline - fiber_clock())) + return send_and_recv_iproto() end - local function send_and_recv_console(timeout) + local function send_and_recv_console() local delim = '\n...\n' - local err, delim_pos = send_and_recv(delim, timeout) + local err, delim_pos = send_and_recv(delim) if err then return err, delim_pos else
On Wed, Aug 04, 2021 at 01:07:42AM +0200, Vladislav Shpilevoy wrote: > > +static const char * > > +netbox_send_and_recv_console(int fd, struct ibuf *send_buf, > > + struct ibuf *recv_buf, double timeout, > > + size_t *response_len) > > 1. To be consistent with netbox_communicate() I would call the > last argument 'size'. Up to you. Let's leave response_len to emphasize that response is a string. > > +static int > > +netbox_console_setup(struct lua_State *L) > > +{ > > + static const char setup_delimiter_cmd[] = > > + "require('console').delimiter('$EOF$')\n"; > > + static const size_t setup_delimiter_cmd_len = > > + sizeof(setup_delimiter_cmd) - 1; > > + static const char ok_response[] = "---\n...\n"; > > + static const size_t ok_response_len = sizeof(ok_response) - 1; > > 2. Why do you make them static? Wouldn't it be enough to > just store them on the stack? No reason. Fixed. > Btw, the same question for > netbox_registry_meta, netbox_request_meta, net_box_lib. > Why should they be static? It seems Lua anyway copies them > somewhere, so they don't need to stay valid for the entire > process' lifetime. I honestly don't know, but we always define them as static: ~/src/tarantool$ git grep 'luaL_Reg.*{' src | wc -l 60 ~/src/tarantool$ git grep 'luaL_Reg.*{' src | grep -c -v static 0 Maybe because we are afraid of running out of stack? I woulnd't bother anyway. > > +static int > > +netbox_console_loop(struct lua_State *L) > > +{ > > + struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); > > + int fd = lua_tointeger(L, 2); > > + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 3); > > + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 4); > > + double timeout = (!lua_isnoneornil(L, 5) ? > > + lua_tonumber(L, 5) : TIMEOUT_INFINITY); > > 3. You never pass timeout as not nil. It is always TIMEOUT_INFINITY. Fixed in a separate patch: https://lists.tarantool.org/pipermail/tarantool-patches/2021-August/025208.html Incremental diff: -- diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index eec5e5bcc519..d41a8d654a8b 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -1762,12 +1762,11 @@ netbox_dispatch_response_console(struct lua_State *L, static int netbox_console_setup(struct lua_State *L) { - static const char setup_delimiter_cmd[] = + const char *setup_delimiter_cmd = "require('console').delimiter('$EOF$')\n"; - static const size_t setup_delimiter_cmd_len = - sizeof(setup_delimiter_cmd) - 1; - static const char ok_response[] = "---\n...\n"; - static const size_t ok_response_len = sizeof(ok_response) - 1; + const size_t setup_delimiter_cmd_len = strlen(setup_delimiter_cmd); + const char *ok_response = "---\n...\n"; + const size_t ok_response_len = strlen(ok_response); int fd = lua_tointeger(L, 1); struct ibuf *send_buf = (struct ibuf *)lua_topointer(L, 2); struct ibuf *recv_buf = (struct ibuf *)lua_topointer(L, 3); @@ -1782,7 +1781,8 @@ netbox_console_setup(struct lua_State *L) luaL_testcancel(L); goto error; } - if (strncmp(response, ok_response, ok_response_len) != 0) { + if (response_len != ok_response_len || + strncmp(response, ok_response, ok_response_len) != 0) { box_error_raise(ER_NO_CONNECTION, "Unexpected response"); goto error; }
On Wed, Aug 04, 2021 at 01:08:25AM +0200, Vladislav Shpilevoy wrote: > > +static int > > +netbox_iproto_auth(struct lua_State *L) > > +{ > > + size_t user_len; > > + const char *user = lua_tolstring(L, 1, &user_len); > > + size_t password_len; > > + const char *password = lua_tolstring(L, 2, &password_len); > > + size_t salt_len; > > + const char *salt = lua_tolstring(L, 3, &salt_len); > > + if (salt_len < SCRAMBLE_SIZE) > > + return luaL_error(L, "Invalid salt"); > > + struct netbox_registry *registry = luaT_check_netbox_registry(L, 4); > > + int fd = lua_tointeger(L, 5); > > + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 6); > > + struct ibuf *recv_buf = (struct ibuf *) lua_topointer(L, 7); > > + double timeout = (!lua_isnoneornil(L, 8) ? > > + lua_tonumber(L, 8) : TIMEOUT_INFINITY); > > Timeout is never passed, it is always TIMEOUT_INFINITY. The same in > netbox_iproto_schema and netbox_iproto_loop. Fixed in a separate patch: https://lists.tarantool.org/pipermail/tarantool-patches/2021-August/025208.html
On Wed, Aug 04, 2021 at 01:08:40AM +0200, Vladislav Shpilevoy wrote:
> On 23.07.2021 13:07, Vladimir Davydov via Tarantool-patches wrote:
> > So as not to call tree C functions per each request, let's merge them
>
> tree -> three.
Fixed.
On Wed, Aug 04, 2021 at 01:09:52AM +0200, Vladislav Shpilevoy wrote: > > -static int > > -netbox_perform_async_request(struct lua_State *L) > > +static void > > +netbox_perform_async_request_impl(struct lua_State *L, int idx, > > + struct netbox_request *request) > > 1. Impl is a confusing name here. Not clear what it does > exactly. I propose netbox_make_request() or netbox_send_request() > or netbox_push_request() or something likewise. netbox_make_request it is then > > > { > > - struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); > > - netbox_request_create(request); > > - luaL_getmetatable(L, netbox_request_typename); > > - lua_setmetatable(L, -2); > > - > > /* Encode and write the request to the send buffer. */ > > - struct netbox_registry *registry = luaT_check_netbox_registry(L, 1); > > - struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, 2); > > - enum netbox_method method = lua_tointeger(L, 5); > > + struct netbox_registry *registry = luaT_check_netbox_registry(L, idx); > > + struct ibuf *send_buf = (struct ibuf *) lua_topointer(L, idx + 1); > > + enum netbox_method method = lua_tointeger(L, idx + 4); > > assert(method < netbox_method_MAX); > > uint64_t sync = registry->next_sync++; > > - netbox_encode_method(L, 9, method, send_buf, sync); > > + netbox_encode_method(L, idx + 8, method, send_buf, sync); > > > > /* Initialize and register the request object. */ > > request->method = method; > > request->sync = sync; > > - request->buffer = (struct ibuf *) lua_topointer(L, 3); > > - lua_pushvalue(L, 3); > > + request->buffer = (struct ibuf *) lua_topointer(L, idx + 2); > > + lua_pushvalue(L, idx + 2); > > request->buffer_ref = luaL_ref(L, LUA_REGISTRYINDEX); > > - request->skip_header = lua_toboolean(L, 4); > > - lua_pushvalue(L, 6); > > + request->skip_header = lua_toboolean(L, idx + 3); > > + lua_pushvalue(L, idx + 5); > > request->on_push_ref = luaL_ref(L, LUA_REGISTRYINDEX); > > - lua_pushvalue(L, 7); > > + lua_pushvalue(L, idx + 6); > > request->on_push_ctx_ref = luaL_ref(L, LUA_REGISTRYINDEX); > > - if (!lua_isnil(L, 8)) > > - request->format = lbox_check_tuple_format(L, 8); > > + if (!lua_isnil(L, idx + 7)) > > + request->format = lbox_check_tuple_format(L, idx + 7); > > 2. To avoid most of that diff could you make the index relative from > the beginning? Before this commit you could make 'int idx = 1;' and > then for each next parameter use 'idx++'. Then this commit wouldn't > change much here. The arguments are accessed non-sequentially so idx++ won't work. (To encode a request we need arguments 1, 2, 5, and 9... and encoding should be called before creating a request handle, because it may fail. The argument order comes from Lua net.box functions. I don't want to reorder arguments in Lua at this point.) Anyway, I don't think we should care much about intermediate diffs so let's please leave it as is. > > +static int > > +netbox_perform_request(struct lua_State *L) > > +{ > > + double timeout = (!lua_isnil(L, 1) ? > > + lua_tonumber(L, 1) : TIMEOUT_INFINITY); > > 2. This piece of code is repeated 6 times in the file. Maybe > time to make it a function? Not anymore. I removed timeouts from all IO functions. > > - local function perform_async_request(buffer, skip_header, method, on_push, > > - on_push_ctx, format, ...) > > + local function check_active() > > if state ~= 'active' and state ~= 'fetch_schema' then > > local code = last_errno or E_NO_CONNECTION > > local msg = last_error or > > string.format('Connection is not established, state is "%s"', > > state) > > - return nil, box.error.new({code = code, reason = msg}) > > + return box.error.new({code = code, reason = msg}) > > + end > > + return nil > > 3. You can drop this return, it does not do anything (given how > the function is used). Dropped. > > + end > > @@ -284,13 +292,18 @@ local function create_transport(host, port, user, password, callback, > > -- > > local function perform_request(timeout, buffer, skip_header, method, > > on_push, on_push_ctx, format, ...) > > - local request, err = > > - perform_async_request(buffer, skip_header, method, on_push, > > - on_push_ctx, format, ...) > > - if not request then > > + local err = check_active() > > + if err then > > return nil, err > > end > > - return request:wait_result(timeout) > > + -- alert worker to notify it of the queued outgoing data; > > + -- if the buffer wasn't empty, assume the worker was already alerted > > + if send_buf:size() == 0 then > > + worker_fiber:wakeup() > > + end > > 4. Why not extract it all into a function along with check_active? > Such as 'prepare_request()' or something. These blocks until their > 'return's are exactly the same. Sure, done. Incremental diff: -- diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 282e23716905..06e574cdf746 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -1572,8 +1572,8 @@ netbox_new_registry(struct lua_State *L) * - ...: method-specific arguments passed to the encoder */ static void -netbox_perform_async_request_impl(struct lua_State *L, int idx, - struct netbox_request *request) +netbox_make_request(struct lua_State *L, int idx, + struct netbox_request *request) { /* Encode and write the request to the send buffer. */ struct netbox_registry *registry = luaT_check_netbox_registry(L, idx); @@ -1612,7 +1612,7 @@ static int netbox_perform_async_request(struct lua_State *L) { struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); - netbox_perform_async_request_impl(L, 1, request); + netbox_make_request(L, 1, request); luaL_getmetatable(L, netbox_request_typename); lua_setmetatable(L, -2); return 1; @@ -1624,7 +1624,7 @@ netbox_perform_request(struct lua_State *L) double timeout = (!lua_isnil(L, 1) ? lua_tonumber(L, 1) : TIMEOUT_INFINITY); struct netbox_request request; - netbox_perform_async_request_impl(L, 2, &request); + netbox_make_request(L, 2, &request); while (!netbox_request_is_ready(&request)) { if (!netbox_request_wait(&request, &timeout)) { netbox_request_unregister(&request); diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 9da900933fbc..0bb7ff3ba3a7 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -253,7 +253,7 @@ local function create_transport(host, port, user, password, callback, end end - local function check_active() + local function prepare_perform_request() if state ~= 'active' and state ~= 'fetch_schema' then local code = last_errno or E_NO_CONNECTION local msg = last_error or @@ -261,7 +261,11 @@ local function create_transport(host, port, user, password, callback, state) return box.error.new({code = code, reason = msg}) end - return nil + -- alert worker to notify it of the queued outgoing data; + -- if the buffer wasn't empty, assume the worker was already alerted + if send_buf:size() == 0 then + worker_fiber:wakeup() + end end -- @@ -271,15 +275,10 @@ local function create_transport(host, port, user, password, callback, -- local function perform_async_request(buffer, skip_header, method, on_push, on_push_ctx, format, ...) - local err = check_active() + local err = prepare_perform_request() if err then return nil, err end - -- alert worker to notify it of the queued outgoing data; - -- if the buffer wasn't empty, assume the worker was already alerted - if send_buf:size() == 0 then - worker_fiber:wakeup() - end return internal.perform_async_request(requests, send_buf, buffer, skip_header, method, on_push, on_push_ctx, format, ...) @@ -292,15 +291,10 @@ local function create_transport(host, port, user, password, callback, -- local function perform_request(timeout, buffer, skip_header, method, on_push, on_push_ctx, format, ...) - local err = check_active() + local err = prepare_perform_request() if err then return nil, err end - -- alert worker to notify it of the queued outgoing data; - -- if the buffer wasn't empty, assume the worker was already alerted - if send_buf:size() == 0 then - worker_fiber:wakeup() - end return internal.perform_request(timeout, requests, send_buf, buffer, skip_header, method, on_push, on_push_ctx, format, ...)
On Wed, Aug 04, 2021 at 11:20:04PM +0200, Vladislav Shpilevoy wrote: > >>> +static int > >>> +netbox_new_request(struct lua_State *L) > >>> +{ > >>> + struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); > >> > >> 9. Does it help perf if the requests are allocated on mempool? > > > > I tried to allocate requests on mempool. To achieve that, we have to > > turn them into C data. It resulted in a performance degradation, see the > > parallel thread: > > It does not necessarily need to be cdata. You can use > lua_newuserdata(L, sizeof(void *)) and store pointers at the requests which > you allocate on mempool. The downside is that there will be +1 dereference for > each access. The upside is that Lua might have optimizations for small userdata > objects. For example, ffi.new() for objects < 128 bytes is order of magnitude > faster than for bigger objects. Something similar might exist for userdata. Tried that - diff is within stdev with the patched version performing slightly worse: CALL performance over 5 runs: KRPS(WALL) : KRPS(PROC) userdata : 312 +- 7 : 542 +- 20 userdata + mempool : 305 +- 2 : 527 +- 11 Here's the patch I used: -- diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 06e574cdf746..f6dcf1cca920 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -136,6 +136,8 @@ struct netbox_request { struct error *error; }; +struct mempool netbox_request_pool; + static const char netbox_registry_typename[] = "net.box.registry"; static const char netbox_request_typename[] = "net.box.request"; @@ -1346,7 +1348,8 @@ luaT_netbox_registry_reset(struct lua_State *L) static inline struct netbox_request * luaT_check_netbox_request(struct lua_State *L, int idx) { - return luaL_checkudata(L, idx, netbox_request_typename); + return *(struct netbox_request **)luaL_checkudata( + L, idx, netbox_request_typename); } static int @@ -1355,6 +1358,7 @@ luaT_netbox_request_gc(struct lua_State *L) struct netbox_request *request = luaT_check_netbox_request(L, 1); netbox_request_unregister(request); netbox_request_destroy(request); + mempool_free(&netbox_request_pool, request); return 0; } @@ -1611,8 +1615,10 @@ netbox_make_request(struct lua_State *L, int idx, static int netbox_perform_async_request(struct lua_State *L) { - struct netbox_request *request = lua_newuserdata(L, sizeof(*request)); - netbox_make_request(L, 1, request); + struct netbox_request **request_p = lua_newuserdata( + L, sizeof(*request_p)); + *request_p = mempool_alloc(&netbox_request_pool); + netbox_make_request(L, 1, *request_p); luaL_getmetatable(L, netbox_request_typename); lua_setmetatable(L, -2); return 1; @@ -1976,6 +1982,9 @@ netbox_console_loop(struct lua_State *L) int luaopen_net_box(struct lua_State *L) { + mempool_create(&netbox_request_pool, cord_slab_cache(), + sizeof(struct netbox_request)); + lua_pushcfunction(L, luaT_netbox_request_iterator_next); luaT_netbox_request_iterator_next_ref = luaL_ref(L, LUA_REGISTRYINDEX);
Hi! Thanks for the patchset! LGTM.
Vova, We discussed with Vitaliia the fails[1] occured on ARM platform and this is known bug[2], that's on review in mine and Sergos' queues. Anyway, I doubt your changes affects LuaJIT, so I see no sense in blocking your patchset with the fix for #6227. If this is the only issue stopping you from merging, feel free to proceed: if the issue is not gone as a result of resolving #6227, all the fault is on me. P.S. While I was writing this message, Vitaliia restarted the pipeline and it successfully finished[3], so seems this is another flaky. [1]: https://github.com/tarantool/tarantool/runs/3259974815#step:4:4019 [2]: https://github.com/tarantool/tarantool/issues/6227 [3]: https://github.com/tarantool/tarantool/runs/3279854047 -- Best regards, IM
[-- Attachment #1: Type: text/plain, Size: 982 bytes --] Thanks all, QA LGTM -- Vitaliia Ioffe >Понедельник, 9 августа 2021, 14:46 +03:00 от Igor Munkin via Tarantool-patches <tarantool-patches@dev.tarantool.org>: > >Vova, > >We discussed with Vitaliia the fails[1] occured on ARM platform and this >is known bug[2], that's on review in mine and Sergos' queues. > >Anyway, I doubt your changes affects LuaJIT, so I see no sense in >blocking your patchset with the fix for #6227. If this is the only issue >stopping you from merging, feel free to proceed: if the issue is not >gone as a result of resolving #6227, all the fault is on me. > >P.S. While I was writing this message, Vitaliia restarted the pipeline >and it successfully finished[3], so seems this is another flaky. > >[1]: https://github.com/tarantool/tarantool/runs/3259974815#step:4:4019 >[2]: https://github.com/tarantool/tarantool/issues/6227 >[3]: https://github.com/tarantool/tarantool/runs/3279854047 > >-- >Best regards, >IM [-- Attachment #2: Type: text/html, Size: 1737 bytes --]
Pushed to master. Thanks!