Tarantool development patches archive
 help / color / mirror / Atom feed
From: Cyrill Gorcunov via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Cc: tml <tarantool-patches@dev.tarantool.org>
Subject: Re: [Tarantool-patches] [PATCH v21 1/6] box/func: fix modules functions restore
Date: Sat, 10 Apr 2021 18:02:43 +0300	[thread overview]
Message-ID: <YHG+E4w49UnBYwsR@grain> (raw)
In-Reply-To: <98549c9b-2c93-684b-2b97-2e1359fa725e@tarantool.org>

On Sat, Apr 10, 2021 at 01:31:06AM +0200, Vladislav Shpilevoy wrote:
...
> 
> 7. It is better to make these functions return something different from
> the valid module. For example, make them all return 0. Or even make them
> crash.
> 
> See below the diff I suggest you to apply. I didn't rename the test files
> though. Please, do it locally.
...
Thanks a huge for review, Vlad! I updated the series and force pushed it.
Here is a new version.
---
In commit 96938fafb (Add hot function reload for C procedures)
an ability to hot reload of modules has been introduced.
When module is been reloaded his functions are resolved to
new symbols but if something went wrong it is supposed
to restore old symbols from the old module.

Actually current code restores only one function and may
crash if there a bunch of functions to restore. Lets fix it.

Fixes #5968

Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com>
---
 changelogs/unreleased/fix-module-reload.md |   8 ++
 src/box/func.c                             |  13 ++-
 test/box/CMakeLists.txt                    |   4 +
 test/box/func_restore1.c                   |  22 ++++
 test/box/func_restore2.c                   |  17 ++++
 test/box/func_restore3.c                   |  17 ++++
 test/box/func_restore4.c                   |  17 ++++
 test/box/gh-5968-func-restore.result       | 111 +++++++++++++++++++++
 test/box/gh-5968-func-restore.test.lua     |  56 +++++++++++
 9 files changed, 258 insertions(+), 7 deletions(-)
 create mode 100644 changelogs/unreleased/fix-module-reload.md
 create mode 100644 test/box/func_restore1.c
 create mode 100644 test/box/func_restore2.c
 create mode 100644 test/box/func_restore3.c
 create mode 100644 test/box/func_restore4.c
 create mode 100644 test/box/gh-5968-func-restore.result
 create mode 100644 test/box/gh-5968-func-restore.test.lua

diff --git a/changelogs/unreleased/fix-module-reload.md b/changelogs/unreleased/fix-module-reload.md
new file mode 100644
index 000000000..e44118aa8
--- /dev/null
+++ b/changelogs/unreleased/fix-module-reload.md
@@ -0,0 +1,8 @@
+## bugfix/core
+
+* Fix crash in case of reloading a compiled module when the
+  new module lacks some of functions which were present in the
+  former code. In turn this event triggers a fallback procedure
+  where we restore old functions but instead of restoring each
+  function we process a sole entry only leading to the crash
+  later when these restored functions are called (gh-5968).
diff --git a/src/box/func.c b/src/box/func.c
index 9909cee45..94b14c56c 100644
--- a/src/box/func.c
+++ b/src/box/func.c
@@ -387,13 +387,14 @@ module_reload(const char *package, const char *package_end, struct module **modu
 
 	struct func_c *func, *tmp_func;
 	rlist_foreach_entry_safe(func, &old_module->funcs, item, tmp_func) {
+		/* Move immediately for restore sake. */
+		rlist_move(&new_module->funcs, &func->item);
 		struct func_name name;
 		func_split_name(func->base.def->name, &name);
 		func->func = module_sym(new_module, name.sym);
 		if (func->func == NULL)
 			goto restore;
 		func->module = new_module;
-		rlist_move(&new_module->funcs, &func->item);
 	}
 	module_cache_del(package, package_end);
 	if (module_cache_put(new_module) != 0)
@@ -403,10 +404,10 @@ module_reload(const char *package, const char *package_end, struct module **modu
 	return 0;
 restore:
 	/*
-	 * Some old-dso func can't be load from new module, restore old
-	 * functions.
+	 * Some old functions are not found in the new module,
+	 * thus restore all migrated functions back to the original.
 	 */
-	do {
+	rlist_foreach_entry_safe(func, &new_module->funcs, item, tmp_func) {
 		struct func_name name;
 		func_split_name(func->base.def->name, &name);
 		func->func = module_sym(old_module, name.sym);
@@ -420,9 +421,7 @@ module_reload(const char *package, const char *package_end, struct module **modu
 		}
 		func->module = old_module;
 		rlist_move(&old_module->funcs, &func->item);
-	} while (func != rlist_first_entry(&old_module->funcs,
-					   struct func_c, item));
-	assert(rlist_empty(&new_module->funcs));
+	}
 	module_delete(new_module);
 	return -1;
 }
diff --git a/test/box/CMakeLists.txt b/test/box/CMakeLists.txt
index 06bfbbe9d..4216a0ba9 100644
--- a/test/box/CMakeLists.txt
+++ b/test/box/CMakeLists.txt
@@ -2,4 +2,8 @@ include_directories(${MSGPUCK_INCLUDE_DIRS})
 build_module(function1 function1.c)
 build_module(reload1 reload1.c)
 build_module(reload2 reload2.c)
+build_module(func_restore1 func_restore1.c)
+build_module(func_restore2 func_restore2.c)
+build_module(func_restore3 func_restore3.c)
+build_module(func_restore4 func_restore4.c)
 build_module(tuple_bench tuple_bench.c)
diff --git a/test/box/func_restore1.c b/test/box/func_restore1.c
new file mode 100644
index 000000000..f2d0aaf78
--- /dev/null
+++ b/test/box/func_restore1.c
@@ -0,0 +1,22 @@
+#include "module.h"
+
+int
+echo_1(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	const char *mp1 = "\x01";
+	return box_return_mp(ctx, mp1, mp1 + 1);
+}
+
+int
+echo_2(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	const char *mp1 = "\x02";
+	return box_return_mp(ctx, mp1, mp1 + 1);
+}
+
+int
+echo_3(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	const char *mp1 = "\x03";
+	return box_return_mp(ctx, mp1, mp1 + 1);
+}
diff --git a/test/box/func_restore2.c b/test/box/func_restore2.c
new file mode 100644
index 000000000..c77ff4b42
--- /dev/null
+++ b/test/box/func_restore2.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+#include "module.h"
+
+int
+echo_1(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	abort();
+	return 0;
+}
+
+int
+echo_2(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	abort();
+	return 0;
+}
diff --git a/test/box/func_restore3.c b/test/box/func_restore3.c
new file mode 100644
index 000000000..7dea37817
--- /dev/null
+++ b/test/box/func_restore3.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+#include "module.h"
+
+int
+echo_2(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	abort();
+	return 0;
+}
+
+int
+echo_3(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	abort();
+	return 0;
+}
diff --git a/test/box/func_restore4.c b/test/box/func_restore4.c
new file mode 100644
index 000000000..545d9e8e3
--- /dev/null
+++ b/test/box/func_restore4.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+#include "module.h"
+
+int
+echo_1(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	abort();
+	return 0;
+}
+
+int
+echo_3(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	abort();
+	return 0;
+}
diff --git a/test/box/gh-5968-func-restore.result b/test/box/gh-5968-func-restore.result
new file mode 100644
index 000000000..0914e780a
--- /dev/null
+++ b/test/box/gh-5968-func-restore.result
@@ -0,0 +1,111 @@
+-- test-run result file version 2
+--
+-- gh-5968: Test that compiled C function can be restored to the former
+-- values when a new module can't be loaded for some reason (say there are
+-- missing functions).
+--
+build_path = os.getenv("BUILDDIR")
+ | ---
+ | ...
+old_cpath = package.cpath
+ | ---
+ | ...
+package.cpath = build_path..'/test/box/?.so;'..build_path..'/test/box/?.dylib;'..package.cpath
+ | ---
+ | ...
+
+fio = require('fio')
+ | ---
+ | ...
+
+ext = (jit.os == "OSX" and ".dylib" or ".so")
+ | ---
+ | ...
+
+path_func_restore = fio.pathjoin(build_path, "test/box/func_restore") .. ext
+ | ---
+ | ...
+path_func_good = fio.pathjoin(build_path, "test/box/func_restore1") .. ext
+ | ---
+ | ...
+
+_ = pcall(fio.unlink(path_func_restore))
+ | ---
+ | ...
+fio.symlink(path_func_good, path_func_restore)
+ | ---
+ | - true
+ | ...
+
+box.schema.func.create('func_restore.echo_1', {language = "C"})
+ | ---
+ | ...
+box.schema.func.create('func_restore.echo_2', {language = "C"})
+ | ---
+ | ...
+box.schema.func.create('func_restore.echo_3', {language = "C"})
+ | ---
+ | ...
+
+box.schema.user.grant('guest', 'execute', 'function', 'func_restore.echo_3')
+ | ---
+ | ...
+box.schema.user.grant('guest', 'execute', 'function', 'func_restore.echo_2')
+ | ---
+ | ...
+box.schema.user.grant('guest', 'execute', 'function', 'func_restore.echo_1')
+ | ---
+ | ...
+
+assert(box.func['func_restore.echo_3']:call() == 3)
+ | ---
+ | - true
+ | ...
+assert(box.func['func_restore.echo_2']:call() == 2)
+ | ---
+ | - true
+ | ...
+assert(box.func['func_restore.echo_1']:call() == 1)
+ | ---
+ | - true
+ | ...
+
+function run_restore(path)                                      \
+    pcall(fio.unlink(path_func_restore))                        \
+    fio.symlink(path, path_func_restore)                        \
+                                                                \
+    local ok, _ = pcall(box.schema.func.reload, "func_restore") \
+    assert(not ok)                                              \
+                                                                \
+    assert(box.func['func_restore.echo_1']:call() == 1)         \
+    assert(box.func['func_restore.echo_2']:call() == 2)         \
+    assert(box.func['func_restore.echo_3']:call() == 3)         \
+end
+ | ---
+ | ...
+
+bad_modules = {                                                 \
+    fio.pathjoin(build_path, "test/box/func_restore2") .. ext,  \
+    fio.pathjoin(build_path, "test/box/func_restore3") .. ext,  \
+    fio.pathjoin(build_path, "test/box/func_restore4") .. ext,  \
+}
+ | ---
+ | ...
+
+for k, v in ipairs(bad_modules) do run_restore(v) end
+ | ---
+ | ...
+
+box.schema.func.drop('func_restore.echo_1')
+ | ---
+ | ...
+box.schema.func.drop('func_restore.echo_2')
+ | ---
+ | ...
+box.schema.func.drop('func_restore.echo_3')
+ | ---
+ | ...
+
+package.cpath = old_cpath
+ | ---
+ | ...
diff --git a/test/box/gh-5968-func-restore.test.lua b/test/box/gh-5968-func-restore.test.lua
new file mode 100644
index 000000000..c710db8b4
--- /dev/null
+++ b/test/box/gh-5968-func-restore.test.lua
@@ -0,0 +1,56 @@
+--
+-- gh-5968: Test that compiled C function can be restored to the former
+-- values when a new module can't be loaded for some reason (say there are
+-- missing functions).
+--
+build_path = os.getenv("BUILDDIR")
+old_cpath = package.cpath
+package.cpath = build_path..'/test/box/?.so;'..build_path..'/test/box/?.dylib;'..package.cpath
+
+fio = require('fio')
+
+ext = (jit.os == "OSX" and ".dylib" or ".so")
+
+path_func_restore = fio.pathjoin(build_path, "test/box/func_restore") .. ext
+path_func_good = fio.pathjoin(build_path, "test/box/func_restore1") .. ext
+
+_ = pcall(fio.unlink(path_func_restore))
+fio.symlink(path_func_good, path_func_restore)
+
+box.schema.func.create('func_restore.echo_1', {language = "C"})
+box.schema.func.create('func_restore.echo_2', {language = "C"})
+box.schema.func.create('func_restore.echo_3', {language = "C"})
+
+box.schema.user.grant('guest', 'execute', 'function', 'func_restore.echo_3')
+box.schema.user.grant('guest', 'execute', 'function', 'func_restore.echo_2')
+box.schema.user.grant('guest', 'execute', 'function', 'func_restore.echo_1')
+
+assert(box.func['func_restore.echo_3']:call() == 3)
+assert(box.func['func_restore.echo_2']:call() == 2)
+assert(box.func['func_restore.echo_1']:call() == 1)
+
+function run_restore(path)                                      \
+    pcall(fio.unlink(path_func_restore))                        \
+    fio.symlink(path, path_func_restore)                        \
+                                                                \
+    local ok, _ = pcall(box.schema.func.reload, "func_restore") \
+    assert(not ok)                                              \
+                                                                \
+    assert(box.func['func_restore.echo_1']:call() == 1)         \
+    assert(box.func['func_restore.echo_2']:call() == 2)         \
+    assert(box.func['func_restore.echo_3']:call() == 3)         \
+end
+
+bad_modules = {                                                 \
+    fio.pathjoin(build_path, "test/box/func_restore2") .. ext,  \
+    fio.pathjoin(build_path, "test/box/func_restore3") .. ext,  \
+    fio.pathjoin(build_path, "test/box/func_restore4") .. ext,  \
+}
+
+for k, v in ipairs(bad_modules) do run_restore(v) end
+
+box.schema.func.drop('func_restore.echo_1')
+box.schema.func.drop('func_restore.echo_2')
+box.schema.func.drop('func_restore.echo_3')
+
+package.cpath = old_cpath
-- 
2.30.2


  reply	other threads:[~2021-04-10 15:02 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-04-08 16:41 [Tarantool-patches] [PATCH v21 0/6] box: implement box.lib Lua module Cyrill Gorcunov via Tarantool-patches
2021-04-08 16:41 ` [Tarantool-patches] [PATCH v21 1/6] box/func: fix modules functions restore Cyrill Gorcunov via Tarantool-patches
2021-04-09 23:31   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-10 15:02     ` Cyrill Gorcunov via Tarantool-patches [this message]
2021-04-08 16:41 ` [Tarantool-patches] [PATCH v21 2/6] box/func: module_reload -- drop redundant argument Cyrill Gorcunov via Tarantool-patches
2021-04-08 16:41 ` [Tarantool-patches] [PATCH v21 3/6] box/module_cache: introduce modules subsystem Cyrill Gorcunov via Tarantool-patches
2021-04-09 23:54   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-10 14:59     ` Cyrill Gorcunov via Tarantool-patches
2021-04-08 16:41 ` [Tarantool-patches] [PATCH v21 4/6] box/schema.func: switch to new module api Cyrill Gorcunov via Tarantool-patches
2021-04-09 23:55   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-10 15:00     ` Cyrill Gorcunov via Tarantool-patches
2021-04-08 16:41 ` [Tarantool-patches] [PATCH v21 5/6] box: implement box.lib module Cyrill Gorcunov via Tarantool-patches
2021-04-11 15:38   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-11 22:38     ` Cyrill Gorcunov via Tarantool-patches
2021-04-12 22:08       ` Vladislav Shpilevoy via Tarantool-patches
2021-04-12 22:34         ` Cyrill Gorcunov via Tarantool-patches
2021-04-08 16:41 ` [Tarantool-patches] [PATCH v21 6/6] test: add box.lib test Cyrill Gorcunov via Tarantool-patches
2021-04-08 18:53   ` Cyrill Gorcunov via Tarantool-patches
2021-04-11 15:43     ` Vladislav Shpilevoy via Tarantool-patches
2021-04-11 21:56       ` Cyrill Gorcunov via Tarantool-patches
2021-04-11 22:36       ` Cyrill Gorcunov via Tarantool-patches
2021-04-12 22:08         ` Vladislav Shpilevoy via Tarantool-patches
2021-04-13  7:10           ` Cyrill Gorcunov via Tarantool-patches
2021-04-13 21:53 ` [Tarantool-patches] [PATCH v21 0/6] box: implement box.lib Lua module Vladislav Shpilevoy via Tarantool-patches
2021-04-14  8:07 ` Kirill Yukhin via Tarantool-patches

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=YHG+E4w49UnBYwsR@grain \
    --to=tarantool-patches@dev.tarantool.org \
    --cc=gorcunov@gmail.com \
    --cc=v.shpilevoy@tarantool.org \
    --subject='Re: [Tarantool-patches] [PATCH v21 1/6] box/func: fix modules functions restore' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox