From fe0f8fd0404e1dfda65b8c14c70cb26ffde26a70 Mon Sep 17 00:00:00 2001
From: Ilya Verbin <iverbin@tarantool.org>
Date: Mon, 28 Aug 2023 21:20:11 +0300
Subject: [PATCH] main: display a message when local console is exited

Consider the following example:
```
tarantool -e "box.cfg{} require('console').start()"
```
When a local console is exited by pressing Ctrl+D, Tarantool seemingly
freezes - console stops to work, typed characters are not echoed. But
the event loop is not stopped because there are background fibers running.
This patch adds a message that Ctrl+C should be pressed in such a case.

Closes #7017

NO_DOC=minor
---
 ...gh-7017-display-message-on-console-exit.md |  4 ++
 src/box/lua/console.c                         | 13 +++++++
 src/box/lua/console.h                         | 10 +++++
 src/box/lua/console.lua                       |  1 +
 src/main.cc                                   |  6 +++
 .../gh_7017_message_on_console_exit_test.lua  | 37 +++++++++++++++++++
 6 files changed, 71 insertions(+)
 create mode 100644 changelogs/unreleased/gh-7017-display-message-on-console-exit.md
 create mode 100644 test/app-luatest/gh_7017_message_on_console_exit_test.lua

diff --git a/changelogs/unreleased/gh-7017-display-message-on-console-exit.md b/changelogs/unreleased/gh-7017-display-message-on-console-exit.md
new file mode 100644
index 0000000000..5a021f3361
--- /dev/null
+++ b/changelogs/unreleased/gh-7017-display-message-on-console-exit.md
@@ -0,0 +1,4 @@
+## bugfix/core
+
+* Now the "Type Ctrl-C to exit Tarantool" message is displayed when a local
+  console is exited, but background fibers are running (gh-7017).
diff --git a/src/box/lua/console.c b/src/box/lua/console.c
index efa49e5626..493f414e3c 100644
--- a/src/box/lua/console.c
+++ b/src/box/lua/console.c
@@ -54,6 +54,8 @@
 #include <string.h>
 #include <strings.h>
 
+bool is_console_exited;
+
 struct rlist on_console_eval = RLIST_HEAD_INITIALIZER(on_console_eval);
 
 static struct luaL_serializer *serializer_yaml;
@@ -727,6 +729,16 @@ lbox_console_run_on_eval(struct lua_State *L)
 	return 0;
 }
 
+/**
+ * Sets `is_console_exited' to true.
+ */
+static int
+lbox_console_console_exited(MAYBE_UNUSED struct lua_State *L)
+{
+	is_console_exited = true;
+	return 0;
+}
+
 int
 console_session_fd(struct session *session)
 {
@@ -897,6 +909,7 @@ tarantool_lua_console_init(struct lua_State *L)
 		{"format_yaml",		lbox_console_format_yaml},
 		{"format_lua",		lbox_console_format_lua},
 		{"run_on_eval",		lbox_console_run_on_eval},
+		{"console_exited",	lbox_console_console_exited},
 		{NULL, NULL}
 	};
 	luaT_newmodule(L, "console.lib", consolelib);
diff --git a/src/box/lua/console.h b/src/box/lua/console.h
index 29348f5af3..d0b25272c4 100644
--- a/src/box/lua/console.h
+++ b/src/box/lua/console.h
@@ -31,12 +31,22 @@
  * SUCH DAMAGE.
  */
 
+#include <stdbool.h>
 #include "small/rlist.h"
 
 #if defined(__cplusplus)
 extern "C" {
 #endif /* defined(__cplusplus) */
 
+/**
+ * If a local console is exited and there are active libev events (e.g. there's
+ * a background fiber running), Tarantool seemingly freezes - console stops to
+ * work, typed characters are not echoed. This flag is used by main() to display
+ * a message to stdout to make things clear.
+ * The flag is false if the local console was never started or still running.
+ */
+extern bool is_console_exited;
+
 /**
  * Triggers invoked on console eval.
  * Passed eval expression string.
diff --git a/src/box/lua/console.lua b/src/box/lua/console.lua
index 59f4f90dc8..05ea4aaf28 100644
--- a/src/box/lua/console.lua
+++ b/src/box/lua/console.lua
@@ -869,6 +869,7 @@ local function start()
     session_internal.create(1, "repl") -- stdin fileno
     repl(self)
     started = false
+    internal.console_exited()
 end
 
 --
diff --git a/src/main.cc b/src/main.cc
index 572808c482..933fef626e 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -76,6 +76,7 @@
 #include "title.h"
 #include <libutil.h>
 #include "box/lua/init.h" /* box_lua_init() */
+#include "box/lua/console.h"
 #include "box/session.h"
 #include "box/memtx_tx.h"
 #include "box/module_cache.h"
@@ -968,6 +969,11 @@ main(int argc, char **argv)
 			if (say_entering_the_event_loop)
 				say_info("entering the event loop");
 			systemd_snotify("READY=1");
+			if (is_console_exited) {
+				printf("Exited console. Type Ctrl-C to exit "
+				       "Tarantool.\n");
+				fflush(stdout);
+			}
 			ev_now_update(loop());
 			ev_run(loop(), 0);
 		}
diff --git a/test/app-luatest/gh_7017_message_on_console_exit_test.lua b/test/app-luatest/gh_7017_message_on_console_exit_test.lua
new file mode 100644
index 0000000000..0df443fe60
--- /dev/null
+++ b/test/app-luatest/gh_7017_message_on_console_exit_test.lua
@@ -0,0 +1,37 @@
+local popen = require('popen')
+local t = require('luatest')
+local g = t.group('gh-7017')
+
+-- Read from ph:stdout and fail if it doesn't contain a `pattern'.
+local function grep_stdout_or_fail(ph, pattern)
+    local output = ''
+    t.helpers.retrying({}, function()
+        local chunk = ph:read({timeout = 0.05})
+        if chunk ~= nil then
+           output = output .. chunk
+        end
+        t.assert_str_contains(output, pattern)
+    end)
+end
+
+-- Check that the message is printed when local console is exited.
+g.test_message_on_console_exit = function()
+    local tarantool_exe = arg[-1]
+    local lua_code = [[ fiber = require('fiber')
+                        fiber.new(function() fiber.sleep(120) end)
+                        print('gh-7017 started')
+                        require('console').start() ]]
+    local ph = popen.new({tarantool_exe, '-e', lua_code},
+                         {stdin = popen.opts.PIPE, stdout = popen.opts.PIPE})
+    t.assert(ph)
+
+    -- Wait for the process startup.
+    grep_stdout_or_fail(ph, "gh-7017 started")
+
+    -- Send EOF to exit console.
+    ph:shutdown({stdin = true})
+
+    -- Check that the message is printed.
+    grep_stdout_or_fail(ph, "Exited console. Type Ctrl-C to exit Tarantool.")
+    ph:close()
+end
-- 
GitLab