Skip to content

Commit a34a7ac

Browse files
committed
storage: preserve original error on function fail
In Tarantool versions prior to 3.0.0-beta1-18, `net_box.self.call()` cannot invoke C stored or Lua persistent functions. Therefore, if such a function is registered in `box.func`, vshard executes it via `func.call` instead. If such a function raises an error during an uncommitted transaction, 'Transaction is active...' error is going to be raised, which will mask the original error. This patch fixes that bug by checking the function call result. If the function returns an error and a transaction is still active, vshard now explicitly rolls back the transaction. Additionally, this patch fixes inconsistent return types. Previously, functions registered in the schema returned raw cdata (such as tuples), while other functions returned data converted via msgpack. This patch ensures consistency by performing a msgpack conversion for registered functions as well. Fixes #614 Fixes #630 NO_DOC=bugfix
1 parent 9bd1290 commit a34a7ac

2 files changed

Lines changed: 78 additions & 1 deletion

File tree

test/storage-luatest/storage_1_test.lua

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,3 +674,57 @@ test_group.test_info_disable_consistency = function(g)
674674
ilt.assert(res.is_enabled)
675675
end, {global_cfg})
676676
end
677+
678+
local function test_error_msg_is_preserved_template(add_to_schema, g)
679+
g.replica_1_a:exec(function(add_to_schema)
680+
rawset(_G, 'test_fail', function()
681+
box.begin()
682+
error('test_error')
683+
end)
684+
if add_to_schema then
685+
box.schema.func.create('test_fail')
686+
end
687+
local status, err = ivshard.storage.call(1, 'read', 'test_fail', {})
688+
ilt.assert_not(status)
689+
local err_msg = tostring(err)
690+
ilt.assert_str_contains(err_msg, 'test_error')
691+
ilt.assert_not_str_contains(err_msg, 'Transaction is active')
692+
rawset(_G, 'test_fail', nil)
693+
if add_to_schema then
694+
box.schema.func.drop('test_fail')
695+
end
696+
end, {add_to_schema})
697+
end
698+
699+
test_group.test_error_msg_is_preserved = function(g)
700+
test_error_msg_is_preserved_template(false, g)
701+
test_error_msg_is_preserved_template(true, g)
702+
end
703+
704+
test_group.test_local_call_return_type_consistency = function(g)
705+
g.replica_1_a:exec(function()
706+
local test_function = (function()
707+
return box.tuple.new({100, 200, 'tuple'})
708+
end)
709+
rawset(_G, 'test_return_tuple1', test_function)
710+
rawset(_G, 'test_return_tuple2', test_function)
711+
box.schema.func.create('test_return_tuple1')
712+
local status1, result1 = ivshard.storage.call(1,
713+
'read',
714+
'test_return_tuple1',
715+
{})
716+
local status2, result2 = ivshard.storage.call(1,
717+
'read',
718+
'test_return_tuple2',
719+
{})
720+
ilt.assert(status1)
721+
ilt.assert(status2)
722+
ilt.assert_equals(type(result1), type(result2))
723+
ilt.assert_equals(type(result1), 'table')
724+
ilt.assert_equals(result1, result2)
725+
ilt.assert_equals(result1, {100, 200, 'tuple'})
726+
box.schema.func.drop('test_return_tuple1')
727+
_G.test_return_tuple1 = nil
728+
_G.test_return_tuple2 = nil
729+
end)
730+
end

vshard/storage/init.lua

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ local trigger = require('internal.trigger')
77
local ffi = require('ffi')
88
local json_encode = require('json').encode
99
local yaml_encode = require('yaml').encode
10+
local msgpack = require('msgpack')
1011
local fiber_clock = lfiber.clock
1112
local fiber_yield = lfiber.yield
1213
local netbox_self = netbox.self
@@ -260,6 +261,23 @@ else
260261
end
261262
end
262263

264+
--
265+
-- This is copy-paste from the net_box.lua handle_eval_result.
266+
--
267+
local function handle_results(status, ...)
268+
if not status then
269+
box.rollback()
270+
return status, box.error.new(box.error.PROC_LUA, (...))
271+
end
272+
local results = {...}
273+
for i = 1, select('#', ...) do
274+
if type(results[i]) == 'cdata' then
275+
results[i] = msgpack.decode(msgpack.encode(results[i]))
276+
end
277+
end
278+
return status, unpack(results)
279+
end
280+
263281
--
264282
-- Invoke a function on this instance. Arguments are unpacked into the function
265283
-- as arguments.
@@ -284,7 +302,12 @@ local_call = function(func_name, args)
284302
if not func then
285303
return pcall(netbox_self_call, netbox_self, func_name, args)
286304
end
287-
return pcall(func.call, func, args)
305+
-- If the function is called directly, fails, and leaves an uncommitted
306+
-- transaction, then in Tarantool versions before 3.0.0-beta1-18,
307+
-- the original error is replaced by the "Transaction is active" error.
308+
-- We manually check if the function returned an error. If so, we
309+
-- rollback the transaction to reveal the original error.
310+
return handle_results(pcall(func.call, func, args))
288311
end
289312

290313
end

0 commit comments

Comments
 (0)