[Tarantool-patches] [PATCH 1/3] box: check whether box is loaded in box.execute()
Alexander Turenko
alexander.turenko at tarantool.org
Wed May 13 01:18:03 MSK 2020
From: Maria <maria.khaydich at tarantool.org>
box.execute() initializes box if it is not initialized. For this sake,
box.execute() is another function (so called <box_load_and_execute>)
when box is not loaded: it loads box and calls 'real' box.execute().
However it is not enough: <box_load_and_execute> may be saved by a user
before box initialization and called after box loading or during box
loading from a separate fiber.
Note: calling <box_load_and_execute> during box loading is safe now, but
calling of box.execute() is not: the 'real' box.execute() does not
verify whether box is configured. It will be fixed in a further commit.
This commit changes <box_load_and_execute> to verify whether box is
initialized and to load box only when it is not loaded. Also it adds
appropriate locking around load_cfg() invocation from
<box_load_and_execute>.
While we're here, clarified contracts of functions that set box
configuration options.
Closes #4231
Co-developed-by: Alexander Turenko <alexander.turenko at tarantool.org>
---
src/box/lua/load_cfg.lua | 69 +++++++++++++++++--
.../gh-4231-box-execute-idempotence.test.lua | 37 ++++++++++
.../gh-4231-box-execute-locking.test.lua | 46 +++++++++++++
3 files changed, 148 insertions(+), 4 deletions(-)
create mode 100755 test/box-tap/gh-4231-box-execute-idempotence.test.lua
create mode 100755 test/box-tap/gh-4231-box-execute-locking.test.lua
diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index 5d818addf..0b4a14377 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -230,6 +230,11 @@ local function check_replicaset_uuid()
end
-- dynamically settable options
+--
+-- Note: An option should be in `dynamic_cfg_skip_at_load` table
+-- or should be verified in box_check_config(). Otherwise
+-- load_cfg() may report an error, but box will be configured in
+-- fact.
local dynamic_cfg = {
listen = private.cfg_set_listen,
replication = private.cfg_set_replication,
@@ -550,6 +555,16 @@ setmetatable(box, {
end
})
+-- Whether box is loaded.
+--
+-- `false` when box is not configured or when the initialization
+-- is in progress.
+--
+-- `true` when box is configured.
+--
+-- Use locked() wrapper to obtain reliable results.
+local box_is_configured = false
+
local function load_cfg(cfg)
cfg = upgrade_cfg(cfg, translate_cfg)
cfg = prepare_cfg(cfg, default_cfg, template_cfg, modify_cfg)
@@ -560,6 +575,16 @@ local function load_cfg(cfg)
box.cfg = locked(load_cfg) -- restore original box.cfg
return box.error() -- re-throw exception from check_cfg()
end
+
+ -- NB: After this point the function should not raise an
+ -- error.
+ --
+ -- This is important to have right `box_is_configured` (this
+ -- file) and `is_box_configured` (box.cc) values.
+ --
+ -- It also would be counter-intuitive to receive an error from
+ -- box.cfg({<...>}), but find that box is actually configured.
+
-- Restore box members after initial configuration
for k, v in pairs(box_configured) do
box[k] = v
@@ -573,7 +598,13 @@ local function load_cfg(cfg)
end,
__call = locked(reload_cfg),
})
+
+ -- This call either succeeds or calls to panic() / exit().
private.cfg_load()
+
+ -- This block does not raise an error: all necessary checks
+ -- already performed in private.cfg_check(). See `dynamic_cfg`
+ -- comment.
for key, fun in pairs(dynamic_cfg) do
local val = cfg[key]
if val ~= nil then
@@ -588,21 +619,51 @@ local function load_cfg(cfg)
end
end
end
+
if not box.cfg.read_only and not box.cfg.replication then
box.schema.upgrade{auto = true}
end
+
+ box_is_configured = true
end
box.cfg = locked(load_cfg)
--
-- This makes possible do box.execute without calling box.cfg
--- manually. The load_cfg call overwrites following table and
--- metatable.
+-- manually. The load_cfg() call overwrites box.execute, see
+-- <box_configured> variable.
+--
+-- box.execute is <box_load_and_execute> when box is not loaded,
+-- <lbox_execute> otherwise. <box_load_and_execute> loads box and
+-- calls <lbox_execute>.
--
-function box.execute(...)
- load_cfg()
+local box_load_and_execute
+box_load_and_execute = function(...)
+ -- Configure box if it is not configured, no-op otherwise (not
+ -- reconfiguration).
+ --
+ -- We should check whether box is configured, because a user
+ -- may save <box_load_and_execute> function before box loading
+ -- and call it afterwards.
+ if not box_is_configured then
+ locked(function()
+ -- Re-check, because after releasing of the lock the
+ -- box state may be changed. We should call load_cfg()
+ -- only once.
+ if not box_is_configured then
+ load_cfg()
+ end
+ end)()
+ end
+
+ -- At this point box should be configured and box.execute()
+ -- function should be replaced with lbox_execute().
+ assert(type(box.cfg) ~= 'function')
+ assert(box.execute ~= box_load_and_execute)
+
return box.execute(...)
end
+box.execute = box_load_and_execute
-- gh-810:
-- hack luajit default cpath
diff --git a/test/box-tap/gh-4231-box-execute-idempotence.test.lua b/test/box-tap/gh-4231-box-execute-idempotence.test.lua
new file mode 100755
index 000000000..940451521
--- /dev/null
+++ b/test/box-tap/gh-4231-box-execute-idempotence.test.lua
@@ -0,0 +1,37 @@
+#!/usr/bin/env tarantool
+
+--
+-- gh-4231: box.execute should be an idempotent function meaning
+-- its effect should be the same if the user chooses to save it
+-- before explicit box.cfg{} invocation and use the saved version
+-- afterwards.
+--
+
+local tap = require('tap')
+local test = tap.test('gh-4231-box-execute-idempotence')
+test:plan(3)
+
+local box_load_and_execute = box.execute
+
+-- box is not initialized after invalid box.cfg() call and
+-- box.execute() should initialize it first.
+pcall(box.cfg, {listen = 'invalid'})
+local ok, res = pcall(box.execute, 'SELECT 1')
+test:ok(ok, 'verify box.execute() after invalid box.cfg() call', {err = res})
+
+-- Make test cases independent: if the box.execute() call above
+-- fails, initialize box explicitly for the next test cases.
+box.cfg{}
+
+-- This call should be successful and should skip box
+-- (re)initialization.
+local ok, res = pcall(box_load_and_execute, 'SELECT 1')
+test:ok(ok, 'verify box_load_and_execute after successful box.cfg() call',
+ {err = res})
+
+-- Just in case: verify usual box.execute() after
+-- box_load_and_execute().
+local ok, res = pcall(box.execute, 'SELECT 1')
+test:ok(ok, 'verify box.execute() after box_load_and_execute()', {err = res})
+
+os.exit(test:check() and 0 or 1)
diff --git a/test/box-tap/gh-4231-box-execute-locking.test.lua b/test/box-tap/gh-4231-box-execute-locking.test.lua
new file mode 100755
index 000000000..52c60f764
--- /dev/null
+++ b/test/box-tap/gh-4231-box-execute-locking.test.lua
@@ -0,0 +1,46 @@
+#!/usr/bin/env tarantool
+
+-- gh-4231: run box_load_and_execute() (it is box.execute value
+-- before box will be loaded) in several fibers in parallel and
+-- ensure that it returns correct results (i.e. that the function
+-- waits until box will be fully configured).
+
+local fiber = require('fiber')
+local tap = require('tap')
+
+local box_load_and_execute = box.execute
+local fiber_count = 10
+local results = fiber.channel(fiber_count)
+
+local function select_from_vindex()
+ local res = box_load_and_execute('SELECT * FROM "_vindex"')
+ results:put(res)
+end
+
+local test = tap.test('gh-4231-box-execute-locking')
+
+test:plan(fiber_count)
+
+local exp_metadata = {
+ {name = 'id', type = 'unsigned'},
+ {name = 'iid', type = 'unsigned'},
+ {name = 'name', type = 'string'},
+ {name = 'type', type = 'string'},
+ {name = 'opts', type = 'map'},
+ {name = 'parts', type = 'array'},
+}
+
+for _ = 1, fiber_count do
+ fiber.create(select_from_vindex)
+end
+for i = 1, fiber_count do
+ local res = results:get()
+ test:test(('result %d'):format(i), function(test)
+ test:plan(2)
+ test:is_deeply(res.metadata, exp_metadata, 'verify metadata')
+ local rows_is_ok = type(res.rows) == 'table' and #res.rows > 0
+ test:ok(rows_is_ok, 'verify rows')
+ end)
+end
+
+os.exit(test:check() and 0 or 1)
--
2.25.0
More information about the Tarantool-patches
mailing list