diff --git a/src/lua/error.lua b/src/lua/error.lua index 85e86599d46f18eb1483c4ac369411575309cd97..b2646cb0101834c0c6bce832fb031cc71c4fe750 100644 --- a/src/lua/error.lua +++ b/src/lua/error.lua @@ -38,6 +38,9 @@ error_set_prev(struct error *e, struct error *prev); const char * box_error_custom_type(const struct error *e); + +void +error_unref(struct error *e); ]] local REFLECTION_CACHE = {} @@ -114,6 +117,12 @@ end local function error_prev(err) local e = err._cause; if e ~= nil then + local INT32_MAX = 2147483647 + if e._refs >= INT32_MAX then + error("Too many references to error object") + end + e._refs = e._refs + 1 + e = ffi.gc(e, ffi.C.error_unref) return e else return nil diff --git a/test/box/error.result b/test/box/error.result index 01920178e1a50c12054dd379d11a46386eb75ce4..59a53013b8650d252610ba5eacb93fe0ee6b1816 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -888,3 +888,71 @@ e = box.error.new({type = string.rep('a', 128)}) | --- | - 63 | ... + +-- gh-4887: accessing 'prev' member also refs it so that after +-- error is gone, its 'prev' is staying alive. +-- +lua_code = [[function(tuple) local json = require('json') return json.encode(tuple) end]] + | --- + | ... +box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) + | --- + | ... +s = box.schema.space.create('withdata') + | --- + | ... +pk = s:create_index('pk') + | --- + | ... +idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) + | --- + | ... + +function test_func() return pcall(s.insert, s, {1}) end + | --- + | ... +ok, err = test_func() + | --- + | ... +preve = err.prev + | --- + | ... +gc_err = setmetatable({preve}, {__mode = 'v'}) + | --- + | ... +err:set_prev(nil) + | --- + | ... +err.prev + | --- + | - null + | ... +collectgarbage('collect') + | --- + | - 0 + | ... +-- Still one reference to err.prev so it should not be collected. +-- +gc_err + | --- + | - - '[string "return function(tuple) local json = require(''..."]:1: attempt to call + | global ''require'' (a nil value)' + | ... +preve = nil + | --- + | ... +collectgarbage('collect') + | --- + | - 0 + | ... +gc_err + | --- + | - [] + | ... + +s:drop() + | --- + | ... +box.schema.func.drop('runtimeerror') + | --- + | ... diff --git a/test/box/error.test.lua b/test/box/error.test.lua index 38f740615448e33e7be54e04320ac7fc0ff90e7f..d63e83539cfd0e9dc326792bb49c6730f8593a75 100644 --- a/test/box/error.test.lua +++ b/test/box/error.test.lua @@ -244,3 +244,29 @@ e:unpack() -- Try too long type name. e = box.error.new({type = string.rep('a', 128)}) #e.type + +-- gh-4887: accessing 'prev' member also refs it so that after +-- error is gone, its 'prev' is staying alive. +-- +lua_code = [[function(tuple) local json = require('json') return json.encode(tuple) end]] +box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) +s = box.schema.space.create('withdata') +pk = s:create_index('pk') +idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) + +function test_func() return pcall(s.insert, s, {1}) end +ok, err = test_func() +preve = err.prev +gc_err = setmetatable({preve}, {__mode = 'v'}) +err:set_prev(nil) +err.prev +collectgarbage('collect') +-- Still one reference to err.prev so it should not be collected. +-- +gc_err +preve = nil +collectgarbage('collect') +gc_err + +s:drop() +box.schema.func.drop('runtimeerror')