From da5ca51bd3582c6f50c0f3306ed6ff5787d8433b Mon Sep 17 00:00:00 2001 From: Cyrill Gorcunov <gorcunov@gmail.com> Date: Fri, 4 Oct 2019 10:49:24 +0300 Subject: [PATCH] box/console: Fix hang in remote console lua mode If we change output mode on remote machine via text-based session node a (server) ------ require('console').listen('unix/:/tmp/X.sock') ... node b (client) ------ require('console').connect('unix/:/tmp/X.sock') connected to unix/:/tmp/X.sock ... unix/:/tmp/X.sock> \set output lua the client get stuck forever, it is because the text wire protocol waits for yaml eos terminator which of course never hit the peer, because lua mode uses own one. Thus to fix this problem we have to preprocess the text we're passing to the server, just like we do in local console. So we reuse command parsing code and remember current output terminator in text_connection_mt instance. Another problem is that named default output mode. There could be a mixed environment where server operates in default lua mode but client connects with yaml mode. To break a tie we yield "\set output" command with current output mode when establishing a connection. Since the output format is per-session bound this doesn't affect any other connections on a server. Part-of #3834 Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com> (cherry picked from commit c358398c7dc3777712c0dbec2c1b21e69bce2a4a) --- src/box/lua/console.lua | 57 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/box/lua/console.lua b/src/box/lua/console.lua index a068d79a36..efd1cddd89 100644 --- a/src/box/lua/console.lua +++ b/src/box/lua/console.lua @@ -170,6 +170,24 @@ local function current_output() return d end +-- +-- Return current EOS value for currently +-- active output format. +local function current_eos() + return output_eos[current_output()["fmt"]] +end + +-- +-- Map output format descriptor into a "\set" command. +local function output_to_cmd_string(desc) + if desc["opts"] then + string.format("\\set output %s,%s", desc["fmt"], desc["opts"]) + else + string.format("\\set output %s", desc["fmt"]) + end + return cmd +end + local function format(status, ...) local d = current_output() return output_handlers[d["fmt"]](status, d["opts"], ...) @@ -359,15 +377,39 @@ local text_connection_mt = { -- @retval nil Error. -- read = function(self) - local ret = self._socket:read(output_eos["yaml"]) + local ret = self._socket:read(self.eos) if ret and ret ~= '' then return ret end end, -- + -- The command might modify EOS so + -- we have to parse and preprocess it + -- first to not stuck in :read() forever. + -- + -- Same time the EOS is per connection + -- value so we keep it inside the metatable + -- instace, and because we can't use same + -- name as output_eos() function we call + -- it simplier as self.eos. + preprocess_eval = function(self, text) + local command = get_command(text) + if command == nil then + return + end + local nr_items, items = parse_operators(command) + if nr_items == 3 then + local err, fmt, opts = output_parse(items[3]) + if not err then + self.eos = output_eos[fmt] + end + end + end, + -- -- Write + Read. -- eval = function(self, text) + self:preprocess_eval(text) text = text..'$EOF$\n' if not self:write(text) then error(self:set_error()) @@ -422,9 +464,18 @@ local function wrap_text_socket(connection, url, print_f) host = url.host or 'localhost', port = url.service, print_f = print_f, + eos = current_eos(), }, text_connection_mt) - if not conn:write('require("console").delimiter("$EOF$")\n') or - not conn:read() then + -- + -- Prepare the connection: setup EOS symbol + -- by executing the \set command on remote machine + -- explicitly, and then setup a delimiter. + local cmd_set_output = output_to_cmd_string(current_output()) .. '\n' + if not conn:write(cmd_set_output) or not conn:read() then + conn:set_error() + end + local cmd_delimiter = 'require("console").delimiter("$EOF$")\n' + if not conn:write(cmd_delimiter) or not conn:read() then conn:set_error() end return conn -- GitLab