Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
@ 2021-07-23 11:07 Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 01/20] net.box: fix console connection breakage when request is discarded Vladimir Davydov via Tarantool-patches
                   ` (26 more replies)
  0 siblings, 27 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 01/20] net.box: fix console connection breakage when request is discarded
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-28 22:49   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 02/20] net.box: wake up wait_result callers " Vladimir Davydov via Tarantool-patches
                   ` (25 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 02/20] net.box: wake up wait_result callers when request is discarded
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 01/20] net.box: fix console connection breakage when request is discarded Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-29 10:47   ` Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 03/20] net.box: do not check worker_fiber in request:result, is_ready Vladimir Davydov via Tarantool-patches
                   ` (24 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 03/20] net.box: do not check worker_fiber in request:result, is_ready
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 01/20] net.box: fix console connection breakage when request is discarded Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 02/20] net.box: wake up wait_result callers " Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 04/20] net.box: remove decode_push from method_decoder table Vladimir Davydov via Tarantool-patches
                   ` (23 subsequent siblings)
  26 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 04/20] net.box: remove decode_push from method_decoder table
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (2 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 03/20] net.box: do not check worker_fiber in request:result, is_ready Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 05/20] net.box: use decode_tuple instead of decode_get Vladimir Davydov via Tarantool-patches
                   ` (22 subsequent siblings)
  26 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 05/20] net.box: use decode_tuple instead of decode_get
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (3 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 04/20] net.box: remove decode_push from method_decoder table Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 06/20] net.box: rename request.ctx to request.format Vladimir Davydov via Tarantool-patches
                   ` (21 subsequent siblings)
  26 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 06/20] net.box: rename request.ctx to request.format
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (4 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 05/20] net.box: use decode_tuple instead of decode_get Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-28 22:49   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 07/20] net.box: use integer id instead of method name Vladimir Davydov via Tarantool-patches
                   ` (20 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 07/20] net.box: use integer id instead of method name
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (5 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 06/20] net.box: rename request.ctx to request.format Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-28 22:50   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 08/20] net.box: remove useless encode optimization Vladimir Davydov via Tarantool-patches
                   ` (19 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 08/20] net.box: remove useless encode optimization
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (6 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 07/20] net.box: use integer id instead of method name Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 09/20] net.box: rewrite request encoder in C Vladimir Davydov via Tarantool-patches
                   ` (18 subsequent siblings)
  26 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 09/20] net.box: rewrite request encoder in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (7 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 08/20] net.box: remove useless encode optimization Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-28 22:51   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 10/20] lua/utils: make char ptr Lua CTIDs public Vladimir Davydov via Tarantool-patches
                   ` (17 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 10/20] lua/utils: make char ptr Lua CTIDs public
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (8 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 09/20] net.box: rewrite request encoder in C Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C Vladimir Davydov via Tarantool-patches
                   ` (16 subsequent siblings)
  26 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (9 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 10/20] lua/utils: make char ptr Lua CTIDs public Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-27 14:07   ` Cyrill Gorcunov via Tarantool-patches
  2021-07-29 22:39   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 12/20] net.box: rewrite error " Vladimir Davydov via Tarantool-patches
                   ` (15 subsequent siblings)
  26 siblings, 2 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 12/20] net.box: rewrite error decoder in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (10 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-30 22:13   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} " Vladimir Davydov via Tarantool-patches
                   ` (14 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (11 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 12/20] net.box: rewrite error " Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-08-02 21:49   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 14/20] net.box: rename netbox_{prepare, encode}_request to {begin, end} Vladimir Davydov via Tarantool-patches
                   ` (13 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 14/20] net.box: rename netbox_{prepare, encode}_request to {begin, end}
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (12 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} " Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C Vladimir Davydov via Tarantool-patches
                   ` (12 subsequent siblings)
  26 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (13 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 14/20] net.box: rename netbox_{prepare, encode}_request to {begin, end} Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-08-02 21:54   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 16/20] net.box: store next_request_id in C code Vladimir Davydov via Tarantool-patches
                   ` (11 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 16/20] net.box: store next_request_id in C code
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (14 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-08-03 23:06   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 17/20] net.box: rewrite console handlers in C Vladimir Davydov via Tarantool-patches
                   ` (10 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 17/20] net.box: rewrite console handlers in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (15 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 16/20] net.box: store next_request_id in C code Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-08-03 23:07   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 18/20] net.box: rewrite iproto " Vladimir Davydov via Tarantool-patches
                   ` (9 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 18/20] net.box: rewrite iproto handlers in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (16 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 17/20] net.box: rewrite console handlers in C Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-08-03 23:08   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 19/20] net.box: merge new_id, new_request and encode_method Vladimir Davydov via Tarantool-patches
                   ` (8 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 19/20] net.box: merge new_id, new_request and encode_method
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (17 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 18/20] net.box: rewrite iproto " Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-08-03 23:08   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 20/20] net.box: do not create request object in Lua for sync requests Vladimir Davydov via Tarantool-patches
                   ` (7 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* [Tarantool-patches] [PATCH 20/20] net.box: do not create request object in Lua for sync requests
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (18 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 19/20] net.box: merge new_id, new_request and encode_method Vladimir Davydov via Tarantool-patches
@ 2021-07-23 11:07 ` Vladimir Davydov via Tarantool-patches
  2021-08-03 23:09   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-23 12:48 ` [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (6 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 11:07 UTC (permalink / raw)
  To: tarantool-patches

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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (19 preceding siblings ...)
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 20/20] net.box: do not create request object in Lua for sync requests Vladimir Davydov via Tarantool-patches
@ 2021-07-23 12:48 ` Vladimir Davydov via Tarantool-patches
  2021-07-26  7:26 ` Kirill Yukhin via Tarantool-patches
                   ` (5 subsequent siblings)
  26 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-23 12:48 UTC (permalink / raw)
  To: tarantool-patches

https://github.com/tarantool/tarantool/issues/6241

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (20 preceding siblings ...)
  2021-07-23 12:48 ` [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
@ 2021-07-26  7:26 ` Kirill Yukhin via Tarantool-patches
  2021-07-27  9:59   ` Vladimir Davydov via Tarantool-patches
  2021-07-28 22:51 ` Vladislav Shpilevoy via Tarantool-patches
                   ` (4 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Kirill Yukhin via Tarantool-patches @ 2021-07-26  7:26 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches


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


^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-26  7:26 ` Kirill Yukhin via Tarantool-patches
@ 2021-07-27  9:59   ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-27  9:59 UTC (permalink / raw)
  To: alyapunov; +Cc: tarantool-patches

https://github.com/tarantool/tarantool/pull/6258

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C Vladimir Davydov via Tarantool-patches
@ 2021-07-27 14:07   ` Cyrill Gorcunov via Tarantool-patches
  2021-07-27 14:14     ` Vladimir Davydov via Tarantool-patches
  2021-07-29 22:39   ` Vladislav Shpilevoy via Tarantool-patches
  1 sibling, 1 reply; 80+ messages in thread
From: Cyrill Gorcunov via Tarantool-patches @ 2021-07-27 14:07 UTC (permalink / raw)
  To: Vladimir Davydov via Tarantool-patches

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?

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C
  2021-07-27 14:07   ` Cyrill Gorcunov via Tarantool-patches
@ 2021-07-27 14:14     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-27 14:14 UTC (permalink / raw)
  To: Cyrill Gorcunov via Tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 01/20] net.box: fix console connection breakage when request is discarded
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 01/20] net.box: fix console connection breakage when request is discarded Vladimir Davydov via Tarantool-patches
@ 2021-07-28 22:49   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-29 10:40     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-28 22:49 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 06/20] net.box: rename request.ctx to request.format
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 06/20] net.box: rename request.ctx to request.format Vladimir Davydov via Tarantool-patches
@ 2021-07-28 22:49   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-29 10:54     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-28 22:49 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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?

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 07/20] net.box: use integer id instead of method name
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 07/20] net.box: use integer id instead of method name Vladimir Davydov via Tarantool-patches
@ 2021-07-28 22:50   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-29 11:30     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-28 22:50 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 09/20] net.box: rewrite request encoder in C
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 09/20] net.box: rewrite request encoder in C Vladimir Davydov via Tarantool-patches
@ 2021-07-28 22:51   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-29 14:08     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-28 22:51 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (21 preceding siblings ...)
  2021-07-26  7:26 ` Kirill Yukhin via Tarantool-patches
@ 2021-07-28 22:51 ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-29 11:33   ` Vladimir Davydov via Tarantool-patches
  2021-07-29 22:40 ` Vladislav Shpilevoy via Tarantool-patches
                   ` (3 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-28 22:51 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 01/20] net.box: fix console connection breakage when request is discarded
  2021-07-28 22:49   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-07-29 10:40     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-29 10:40 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 02/20] net.box: wake up wait_result callers when request is discarded
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 02/20] net.box: wake up wait_result callers " Vladimir Davydov via Tarantool-patches
@ 2021-07-29 10:47   ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-29 10:47 UTC (permalink / raw)
  To: tarantool-patches

Pushed to master, 1.10, 2.7, 2.8.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 06/20] net.box: rename request.ctx to request.format
  2021-07-28 22:49   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-07-29 10:54     ` Vladimir Davydov via Tarantool-patches
  2021-07-29 22:39       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-29 10:54 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 07/20] net.box: use integer id instead of method name
  2021-07-28 22:50   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-07-29 11:30     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-29 11:30 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-28 22:51 ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-07-29 11:33   ` Vladimir Davydov via Tarantool-patches
  2021-07-29 15:23     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-29 11:33 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 09/20] net.box: rewrite request encoder in C
  2021-07-28 22:51   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-07-29 14:08     ` Vladimir Davydov via Tarantool-patches
  2021-07-29 14:10       ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-29 14:08 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 09/20] net.box: rewrite request encoder in C
  2021-07-29 14:08     ` Vladimir Davydov via Tarantool-patches
@ 2021-07-29 14:10       ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-29 14:10 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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;
 }
 

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-29 11:33   ` Vladimir Davydov via Tarantool-patches
@ 2021-07-29 15:23     ` Vladimir Davydov via Tarantool-patches
  2021-07-29 22:38       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-29 15:23 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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}
 

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-29 15:23     ` Vladimir Davydov via Tarantool-patches
@ 2021-07-29 22:38       ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-30 10:04         ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-29 22:38 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

>>>> 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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C Vladimir Davydov via Tarantool-patches
  2021-07-27 14:07   ` Cyrill Gorcunov via Tarantool-patches
@ 2021-07-29 22:39   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-30  8:44     ` Vladimir Davydov via Tarantool-patches
  1 sibling, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-29 22:39 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 06/20] net.box: rename request.ctx to request.format
  2021-07-29 10:54     ` Vladimir Davydov via Tarantool-patches
@ 2021-07-29 22:39       ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-30  8:15         ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-29 22:39 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (22 preceding siblings ...)
  2021-07-28 22:51 ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-07-29 22:40 ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-30  8:16   ` Vladimir Davydov via Tarantool-patches
  2021-08-03 23:05 ` Vladislav Shpilevoy via Tarantool-patches
                   ` (2 subsequent siblings)
  26 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-29 22:40 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 06/20] net.box: rename request.ctx to request.format
  2021-07-29 22:39       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-07-30  8:15         ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-30  8:15 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-29 22:40 ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-07-30  8:16   ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-30  8:16 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C
  2021-07-29 22:39   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-07-30  8:44     ` Vladimir Davydov via Tarantool-patches
  2021-07-30 22:12       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-30  8:44 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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. */

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-29 22:38       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-07-30 10:04         ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-07-30 10:04 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C
  2021-07-30  8:44     ` Vladimir Davydov via Tarantool-patches
@ 2021-07-30 22:12       ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-02  7:36         ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-30 22:12 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 12/20] net.box: rewrite error decoder in C
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 12/20] net.box: rewrite error " Vladimir Davydov via Tarantool-patches
@ 2021-07-30 22:13   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-02  8:00     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-30 22:13 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C
  2021-07-30 22:12       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-02  7:36         ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-02  7:36 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 12/20] net.box: rewrite error decoder in C
  2021-07-30 22:13   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-02  8:00     ` Vladimir Davydov via Tarantool-patches
  2021-08-02 21:47       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-02  8:00 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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)

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 12/20] net.box: rewrite error decoder in C
  2021-08-02  8:00     ` Vladimir Davydov via Tarantool-patches
@ 2021-08-02 21:47       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-02 21:47 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

Hi! Thanks for the fixes, nice work!

The commits 12 and 14 LGTM.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} in C
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} " Vladimir Davydov via Tarantool-patches
@ 2021-08-02 21:49   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-03 15:44     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-02 21:49 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C Vladimir Davydov via Tarantool-patches
@ 2021-08-02 21:54   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-04 12:30     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-02 21:54 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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()
> 

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} in C
  2021-08-02 21:49   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-03 15:44     ` Vladimir Davydov via Tarantool-patches
  2021-08-03 23:06       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-03 15:44 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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(

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (23 preceding siblings ...)
  2021-07-29 22:40 ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-03 23:05 ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-04 12:40   ` Vladimir Davydov via Tarantool-patches
  2021-08-05 20:59 ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-09 11:22 ` Igor Munkin via Tarantool-patches
  26 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-03 23:05 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} in C
  2021-08-03 15:44     ` Vladimir Davydov via Tarantool-patches
@ 2021-08-03 23:06       ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-04 13:56         ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-03 23:06 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

>>> +        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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 16/20] net.box: store next_request_id in C code
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 16/20] net.box: store next_request_id in C code Vladimir Davydov via Tarantool-patches
@ 2021-08-03 23:06   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-04 16:25     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-03 23:06 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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;
>  };

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 17/20] net.box: rewrite console handlers in C
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 17/20] net.box: rewrite console handlers in C Vladimir Davydov via Tarantool-patches
@ 2021-08-03 23:07   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 11:53     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-03 23:07 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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);
> +	}
>  }

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 18/20] net.box: rewrite iproto handlers in C
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 18/20] net.box: rewrite iproto " Vladimir Davydov via Tarantool-patches
@ 2021-08-03 23:08   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 11:54     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-03 23:08 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 19/20] net.box: merge new_id, new_request and encode_method
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 19/20] net.box: merge new_id, new_request and encode_method Vladimir Davydov via Tarantool-patches
@ 2021-08-03 23:08   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 11:55     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-03 23:08 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 20/20] net.box: do not create request object in Lua for sync requests
  2021-07-23 11:07 ` [Tarantool-patches] [PATCH 20/20] net.box: do not create request object in Lua for sync requests Vladimir Davydov via Tarantool-patches
@ 2021-08-03 23:09   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 12:23     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-03 23:09 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

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

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C
  2021-08-02 21:54   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-04 12:30     ` Vladimir Davydov via Tarantool-patches
  2021-08-04 15:35       ` Vladimir Davydov via Tarantool-patches
  2021-08-04 21:20       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 2 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-04 12:30 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-08-03 23:05 ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-04 12:40   ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-04 12:40 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} in C
  2021-08-03 23:06       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-04 13:56         ` Vladimir Davydov via Tarantool-patches
  2021-08-04 21:18           ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-04 13:56 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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) --

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C
  2021-08-04 12:30     ` Vladimir Davydov via Tarantool-patches
@ 2021-08-04 15:35       ` Vladimir Davydov via Tarantool-patches
  2021-08-04 16:14         ` Vladimir Davydov via Tarantool-patches
  2021-08-04 21:20       ` Vladislav Shpilevoy via Tarantool-patches
  1 sibling, 1 reply; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-04 15:35 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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 },

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C
  2021-08-04 15:35       ` Vladimir Davydov via Tarantool-patches
@ 2021-08-04 16:14         ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-04 16:14 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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 },

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 16/20] net.box: store next_request_id in C code
  2021-08-03 23:06   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-04 16:25     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-04 16:25 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} in C
  2021-08-04 13:56         ` Vladimir Davydov via Tarantool-patches
@ 2021-08-04 21:18           ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05  8:37             ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-04 21:18 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

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.


^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C
  2021-08-04 12:30     ` Vladimir Davydov via Tarantool-patches
  2021-08-04 15:35       ` Vladimir Davydov via Tarantool-patches
@ 2021-08-04 21:20       ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 12:46         ` Vladimir Davydov via Tarantool-patches
  1 sibling, 1 reply; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-04 21:20 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

>>> +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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} in C
  2021-08-04 21:18           ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05  8:37             ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-05  8:37 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 17/20] net.box: rewrite console handlers in C
  2021-08-03 23:07   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 11:53     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-05 11:53 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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;
 	}

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 18/20] net.box: rewrite iproto handlers in C
  2021-08-03 23:08   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 11:54     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-05 11:54 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 19/20] net.box: merge new_id, new_request and encode_method
  2021-08-03 23:08   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 11:55     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-05 11:55 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 20/20] net.box: do not create request object in Lua for sync requests
  2021-08-03 23:09   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 12:23     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-05 12:23 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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, ...)

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C
  2021-08-04 21:20       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 12:46         ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-05 12:46 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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);
 

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (24 preceding siblings ...)
  2021-08-03 23:05 ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 20:59 ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-09 11:22 ` Igor Munkin via Tarantool-patches
  26 siblings, 0 replies; 80+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-05 20:59 UTC (permalink / raw)
  To: Vladimir Davydov, tarantool-patches

Hi! Thanks for the patchset!

LGTM.

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
                   ` (25 preceding siblings ...)
  2021-08-05 20:59 ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-09 11:22 ` Igor Munkin via Tarantool-patches
  2021-08-09 11:48   ` Vitaliia Ioffe via Tarantool-patches
  26 siblings, 1 reply; 80+ messages in thread
From: Igor Munkin via Tarantool-patches @ 2021-08-09 11:22 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

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

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches]  [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-08-09 11:22 ` Igor Munkin via Tarantool-patches
@ 2021-08-09 11:48   ` Vitaliia Ioffe via Tarantool-patches
  2021-08-09 13:56     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 1 reply; 80+ messages in thread
From: Vitaliia Ioffe via Tarantool-patches @ 2021-08-09 11:48 UTC (permalink / raw)
  To: Igor Munkin; +Cc: tarantool-patches

[-- 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 --]

^ permalink raw reply	[flat|nested] 80+ messages in thread

* Re: [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C
  2021-08-09 11:48   ` Vitaliia Ioffe via Tarantool-patches
@ 2021-08-09 13:56     ` Vladimir Davydov via Tarantool-patches
  0 siblings, 0 replies; 80+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-09 13:56 UTC (permalink / raw)
  To: Vitaliia Ioffe; +Cc: tarantool-patches

Pushed to master. Thanks!

^ permalink raw reply	[flat|nested] 80+ messages in thread

end of thread, other threads:[~2021-08-09 13:56 UTC | newest]

Thread overview: 80+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-23 11:07 [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 01/20] net.box: fix console connection breakage when request is discarded Vladimir Davydov via Tarantool-patches
2021-07-28 22:49   ` Vladislav Shpilevoy via Tarantool-patches
2021-07-29 10:40     ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 02/20] net.box: wake up wait_result callers " Vladimir Davydov via Tarantool-patches
2021-07-29 10:47   ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 03/20] net.box: do not check worker_fiber in request:result, is_ready Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 04/20] net.box: remove decode_push from method_decoder table Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 05/20] net.box: use decode_tuple instead of decode_get Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 06/20] net.box: rename request.ctx to request.format Vladimir Davydov via Tarantool-patches
2021-07-28 22:49   ` Vladislav Shpilevoy via Tarantool-patches
2021-07-29 10:54     ` Vladimir Davydov via Tarantool-patches
2021-07-29 22:39       ` Vladislav Shpilevoy via Tarantool-patches
2021-07-30  8:15         ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 07/20] net.box: use integer id instead of method name Vladimir Davydov via Tarantool-patches
2021-07-28 22:50   ` Vladislav Shpilevoy via Tarantool-patches
2021-07-29 11:30     ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 08/20] net.box: remove useless encode optimization Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 09/20] net.box: rewrite request encoder in C Vladimir Davydov via Tarantool-patches
2021-07-28 22:51   ` Vladislav Shpilevoy via Tarantool-patches
2021-07-29 14:08     ` Vladimir Davydov via Tarantool-patches
2021-07-29 14:10       ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 10/20] lua/utils: make char ptr Lua CTIDs public Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 11/20] net.box: rewrite response decoder in C Vladimir Davydov via Tarantool-patches
2021-07-27 14:07   ` Cyrill Gorcunov via Tarantool-patches
2021-07-27 14:14     ` Vladimir Davydov via Tarantool-patches
2021-07-29 22:39   ` Vladislav Shpilevoy via Tarantool-patches
2021-07-30  8:44     ` Vladimir Davydov via Tarantool-patches
2021-07-30 22:12       ` Vladislav Shpilevoy via Tarantool-patches
2021-08-02  7:36         ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 12/20] net.box: rewrite error " Vladimir Davydov via Tarantool-patches
2021-07-30 22:13   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-02  8:00     ` Vladimir Davydov via Tarantool-patches
2021-08-02 21:47       ` Vladislav Shpilevoy via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 13/20] net.box: rewrite send_and_recv_{iproto, console} " Vladimir Davydov via Tarantool-patches
2021-08-02 21:49   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-03 15:44     ` Vladimir Davydov via Tarantool-patches
2021-08-03 23:06       ` Vladislav Shpilevoy via Tarantool-patches
2021-08-04 13:56         ` Vladimir Davydov via Tarantool-patches
2021-08-04 21:18           ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05  8:37             ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 14/20] net.box: rename netbox_{prepare, encode}_request to {begin, end} Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 15/20] net.box: rewrite request implementation in C Vladimir Davydov via Tarantool-patches
2021-08-02 21:54   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-04 12:30     ` Vladimir Davydov via Tarantool-patches
2021-08-04 15:35       ` Vladimir Davydov via Tarantool-patches
2021-08-04 16:14         ` Vladimir Davydov via Tarantool-patches
2021-08-04 21:20       ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 12:46         ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 16/20] net.box: store next_request_id in C code Vladimir Davydov via Tarantool-patches
2021-08-03 23:06   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-04 16:25     ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 17/20] net.box: rewrite console handlers in C Vladimir Davydov via Tarantool-patches
2021-08-03 23:07   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 11:53     ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 18/20] net.box: rewrite iproto " Vladimir Davydov via Tarantool-patches
2021-08-03 23:08   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 11:54     ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 19/20] net.box: merge new_id, new_request and encode_method Vladimir Davydov via Tarantool-patches
2021-08-03 23:08   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 11:55     ` Vladimir Davydov via Tarantool-patches
2021-07-23 11:07 ` [Tarantool-patches] [PATCH 20/20] net.box: do not create request object in Lua for sync requests Vladimir Davydov via Tarantool-patches
2021-08-03 23:09   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 12:23     ` Vladimir Davydov via Tarantool-patches
2021-07-23 12:48 ` [Tarantool-patches] [PATCH 00/20] Rewrite performance critical parts of net.box in C Vladimir Davydov via Tarantool-patches
2021-07-26  7:26 ` Kirill Yukhin via Tarantool-patches
2021-07-27  9:59   ` Vladimir Davydov via Tarantool-patches
2021-07-28 22:51 ` Vladislav Shpilevoy via Tarantool-patches
2021-07-29 11:33   ` Vladimir Davydov via Tarantool-patches
2021-07-29 15:23     ` Vladimir Davydov via Tarantool-patches
2021-07-29 22:38       ` Vladislav Shpilevoy via Tarantool-patches
2021-07-30 10:04         ` Vladimir Davydov via Tarantool-patches
2021-07-29 22:40 ` Vladislav Shpilevoy via Tarantool-patches
2021-07-30  8:16   ` Vladimir Davydov via Tarantool-patches
2021-08-03 23:05 ` Vladislav Shpilevoy via Tarantool-patches
2021-08-04 12:40   ` Vladimir Davydov via Tarantool-patches
2021-08-05 20:59 ` Vladislav Shpilevoy via Tarantool-patches
2021-08-09 11:22 ` Igor Munkin via Tarantool-patches
2021-08-09 11:48   ` Vitaliia Ioffe via Tarantool-patches
2021-08-09 13:56     ` Vladimir Davydov via Tarantool-patches

Tarantool development patches archive

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://lists.tarantool.org/tarantool-patches/0 tarantool-patches/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 tarantool-patches tarantool-patches/ https://lists.tarantool.org/tarantool-patches \
		tarantool-patches@dev.tarantool.org.
	public-inbox-index tarantool-patches

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git