mirror of https://github.com/rusefi/lua.git
Multiple errors in '__toclose' report the first one
When there are multiple errors when closing objects, the error reported by the protected call is the first one, for two reasons: First, other errors may be caused by this one; second, the first error is handled in the original execution context, and therefore has the full traceback.
This commit is contained in:
parent
14edd364c3
commit
b4d5dff8ec
|
@ -75,11 +75,8 @@ static int luaB_auxwrap (lua_State *L) {
|
||||||
int r = auxresume(L, co, lua_gettop(L));
|
int r = auxresume(L, co, lua_gettop(L));
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
int stat = lua_status(co);
|
int stat = lua_status(co);
|
||||||
if (stat != LUA_OK && stat != LUA_YIELD) {
|
if (stat != LUA_OK && stat != LUA_YIELD)
|
||||||
stat = lua_resetthread(co); /* close variables in case of errors */
|
lua_resetthread(co); /* close variables in case of errors */
|
||||||
if (stat != LUA_OK) /* error closing variables? */
|
|
||||||
lua_xmove(co, L, 1); /* get new error object */
|
|
||||||
}
|
|
||||||
if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */
|
if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */
|
||||||
luaL_where(L, 1); /* add extra info, if available */
|
luaL_where(L, 1); /* add extra info, if available */
|
||||||
lua_insert(L, -2);
|
lua_insert(L, -2);
|
||||||
|
|
9
lfunc.c
9
lfunc.c
|
@ -144,13 +144,16 @@ static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) {
|
||||||
luaG_runerror(L, "attempt to close non-closable variable '%s'", vname);
|
luaG_runerror(L, "attempt to close non-closable variable '%s'", vname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { /* there was an error */
|
else { /* must close the object in protected mode */
|
||||||
|
ptrdiff_t oldtop = savestack(L, level + 1);
|
||||||
/* save error message and set stack top to 'level + 1' */
|
/* save error message and set stack top to 'level + 1' */
|
||||||
luaD_seterrorobj(L, status, level);
|
luaD_seterrorobj(L, status, level);
|
||||||
if (prepclosingmethod(L, uv, s2v(level))) { /* something to call? */
|
if (prepclosingmethod(L, uv, s2v(level))) { /* something to call? */
|
||||||
int newstatus = luaD_pcall(L, callclose, NULL, savestack(L, level), 0);
|
int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0);
|
||||||
if (newstatus != LUA_OK) /* another error when closing? */
|
if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */
|
||||||
status = newstatus; /* this will be the new error */
|
status = newstatus; /* this will be the new error */
|
||||||
|
else /* leave original error (or nil) on top */
|
||||||
|
L->top = restorestack(L, oldtop);
|
||||||
}
|
}
|
||||||
/* else no metamethod; ignore this case and keep original error */
|
/* else no metamethod; ignore this case and keep original error */
|
||||||
}
|
}
|
||||||
|
|
|
@ -1541,11 +1541,17 @@ if there was no error, the second argument is @nil.
|
||||||
|
|
||||||
If several to-be-closed variables go out of scope at the same event,
|
If several to-be-closed variables go out of scope at the same event,
|
||||||
they are closed in the reverse order that they were declared.
|
they are closed in the reverse order that they were declared.
|
||||||
|
|
||||||
If there is any error while running a closing method,
|
If there is any error while running a closing method,
|
||||||
that error is handled like an error in the regular code
|
that error is handled like an error in the regular code
|
||||||
where the variable was defined;
|
where the variable was defined;
|
||||||
in particular,
|
in particular,
|
||||||
the other pending closing methods will still be called.
|
the other pending closing methods will still be called.
|
||||||
|
After an error,
|
||||||
|
other errors in closing methods
|
||||||
|
interrupt the respective method,
|
||||||
|
but are otherwise ignored;
|
||||||
|
the error reported is the original one.
|
||||||
|
|
||||||
If a coroutine yields inside a block and is never resumed again,
|
If a coroutine yields inside a block and is never resumed again,
|
||||||
the variables visible at that block will never go out of scope,
|
the variables visible at that block will never go out of scope,
|
||||||
|
@ -1553,11 +1559,12 @@ and therefore they will never be closed.
|
||||||
Similarly, if a coroutine ends with an error,
|
Similarly, if a coroutine ends with an error,
|
||||||
it does not unwind its stack,
|
it does not unwind its stack,
|
||||||
so it does not close any variable.
|
so it does not close any variable.
|
||||||
You should either use finalizers
|
In both cases,
|
||||||
or call @Lid{coroutine.close} to close the variables in these cases.
|
you should either use finalizers
|
||||||
However, note that if the coroutine was created
|
or call @Lid{coroutine.close} to close the variables.
|
||||||
|
However, if the coroutine was created
|
||||||
through @Lid{coroutine.wrap},
|
through @Lid{coroutine.wrap},
|
||||||
then its corresponding function will close all variables
|
then its corresponding function will close the coroutine
|
||||||
in case of errors.
|
in case of errors.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3932,7 +3939,7 @@ Returns a status code:
|
||||||
@Lid{LUA_OK} for no errors in closing methods,
|
@Lid{LUA_OK} for no errors in closing methods,
|
||||||
or an error status otherwise.
|
or an error status otherwise.
|
||||||
In case of error,
|
In case of error,
|
||||||
leave the error object on the stack,
|
leaves the error object on the top of the stack,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6355,6 +6362,7 @@ Closes coroutine @id{co},
|
||||||
that is,
|
that is,
|
||||||
closes all its pending to-be-closed variables
|
closes all its pending to-be-closed variables
|
||||||
and puts the coroutine in a dead state.
|
and puts the coroutine in a dead state.
|
||||||
|
The given coroutine must be dead or suspended.
|
||||||
In case of error closing some variable,
|
In case of error closing some variable,
|
||||||
returns @false plus the error object;
|
returns @false plus the error object;
|
||||||
otherwise returns @true.
|
otherwise returns @true.
|
||||||
|
@ -6412,7 +6420,8 @@ true when the running coroutine is the main one.
|
||||||
|
|
||||||
Returns the status of the coroutine @id{co}, as a string:
|
Returns the status of the coroutine @id{co}, as a string:
|
||||||
@T{"running"},
|
@T{"running"},
|
||||||
if the coroutine is running (that is, it called @id{status});
|
if the coroutine is running
|
||||||
|
(that is, it is the one that called @id{status});
|
||||||
@T{"suspended"}, if the coroutine is suspended in a call to @id{yield},
|
@T{"suspended"}, if the coroutine is suspended in a call to @id{yield},
|
||||||
or if it has not started running yet;
|
or if it has not started running yet;
|
||||||
@T{"normal"} if the coroutine is active but not running
|
@T{"normal"} if the coroutine is active but not running
|
||||||
|
|
|
@ -163,15 +163,23 @@ do
|
||||||
assert(not X and coroutine.status(co) == "dead")
|
assert(not X and coroutine.status(co) == "dead")
|
||||||
|
|
||||||
-- error closing a coroutine
|
-- error closing a coroutine
|
||||||
|
local x = 0
|
||||||
co = coroutine.create(function()
|
co = coroutine.create(function()
|
||||||
|
local <toclose> y = func2close(function (self,err)
|
||||||
|
if (err ~= 111) then os.exit(false) end -- should not happen
|
||||||
|
x = 200
|
||||||
|
error(200)
|
||||||
|
end)
|
||||||
local <toclose> x = func2close(function (self, err)
|
local <toclose> x = func2close(function (self, err)
|
||||||
assert(err == nil); error(111)
|
assert(err == nil); error(111)
|
||||||
end)
|
end)
|
||||||
coroutine.yield()
|
coroutine.yield()
|
||||||
end)
|
end)
|
||||||
coroutine.resume(co)
|
coroutine.resume(co)
|
||||||
|
assert(x == 0)
|
||||||
local st, msg = coroutine.close(co)
|
local st, msg = coroutine.close(co)
|
||||||
assert(not st and coroutine.status(co) == "dead" and msg == 111)
|
assert(st == false and coroutine.status(co) == "dead" and msg == 111)
|
||||||
|
assert(x == 200)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -267,14 +267,14 @@ do -- errors in __close
|
||||||
if err then error(4) end
|
if err then error(4) end
|
||||||
end
|
end
|
||||||
local stat, msg = pcall(foo, false)
|
local stat, msg = pcall(foo, false)
|
||||||
assert(msg == 1)
|
assert(msg == 3)
|
||||||
assert(log[1] == 10 and log[2] == 3 and log[3] == 2 and log[4] == 2
|
assert(log[1] == 10 and log[2] == 3 and log[3] == 3 and log[4] == 3
|
||||||
and #log == 4)
|
and #log == 4)
|
||||||
|
|
||||||
log = {}
|
log = {}
|
||||||
local stat, msg = pcall(foo, true)
|
local stat, msg = pcall(foo, true)
|
||||||
assert(msg == 1)
|
assert(msg == 4)
|
||||||
assert(log[1] == 4 and log[2] == 3 and log[3] == 2 and log[4] == 2
|
assert(log[1] == 4 and log[2] == 4 and log[3] == 4 and log[4] == 4
|
||||||
and #log == 4)
|
and #log == 4)
|
||||||
|
|
||||||
-- error in toclose in vararg function
|
-- error in toclose in vararg function
|
||||||
|
@ -317,7 +317,7 @@ if rawget(_G, "T") then
|
||||||
local <toclose> x = setmetatable({}, {__close = function ()
|
local <toclose> x = setmetatable({}, {__close = function ()
|
||||||
T.alloccount(0); local x = {} -- force a memory error
|
T.alloccount(0); local x = {} -- force a memory error
|
||||||
end})
|
end})
|
||||||
error("a") -- common error inside the function's body
|
error(1000) -- common error inside the function's body
|
||||||
end
|
end
|
||||||
|
|
||||||
stack(5) -- ensure a minimal number of CI structures
|
stack(5) -- ensure a minimal number of CI structures
|
||||||
|
@ -325,7 +325,7 @@ if rawget(_G, "T") then
|
||||||
-- despite memory error, 'y' will be executed and
|
-- despite memory error, 'y' will be executed and
|
||||||
-- memory limit will be lifted
|
-- memory limit will be lifted
|
||||||
local _, msg = pcall(foo)
|
local _, msg = pcall(foo)
|
||||||
assert(msg == "not enough memory")
|
assert(msg == 1000)
|
||||||
|
|
||||||
local close = func2close(function (self, msg)
|
local close = func2close(function (self, msg)
|
||||||
T.alloccount()
|
T.alloccount()
|
||||||
|
@ -368,8 +368,7 @@ if rawget(_G, "T") then
|
||||||
end
|
end
|
||||||
|
|
||||||
local _, msg = pcall(test)
|
local _, msg = pcall(test)
|
||||||
assert(msg == 1000)
|
assert(msg == "not enough memory") -- reported error is the first one
|
||||||
|
|
||||||
|
|
||||||
do -- testing 'toclose' in C string buffer
|
do -- testing 'toclose' in C string buffer
|
||||||
collectgarbage()
|
collectgarbage()
|
||||||
|
@ -453,15 +452,27 @@ end
|
||||||
|
|
||||||
do
|
do
|
||||||
-- error in a wrapped coroutine raising errors when closing a variable
|
-- error in a wrapped coroutine raising errors when closing a variable
|
||||||
local x = false
|
local x = 0
|
||||||
local co = coroutine.wrap(function ()
|
local co = coroutine.wrap(function ()
|
||||||
local <toclose> xv = func2close(function () error("XXX") end)
|
local <toclose> xx = func2close(function () x = x + 1; error("YYY") end)
|
||||||
|
local <toclose> xv = func2close(function () x = x + 1; error("XXX") end)
|
||||||
coroutine.yield(100)
|
coroutine.yield(100)
|
||||||
error(200)
|
error(200)
|
||||||
end)
|
end)
|
||||||
assert(co() == 100)
|
assert(co() == 100); assert(x == 0)
|
||||||
local st, msg = pcall(co)
|
local st, msg = pcall(co); assert(x == 2)
|
||||||
-- should get last error raised
|
assert(not st and msg == 200) -- should get first error raised
|
||||||
|
|
||||||
|
x = 0
|
||||||
|
co = coroutine.wrap(function ()
|
||||||
|
local <toclose> xx = func2close(function () x = x + 1; error("YYY") end)
|
||||||
|
local <toclose> xv = func2close(function () x = x + 1; error("XXX") end)
|
||||||
|
coroutine.yield(100)
|
||||||
|
return 200
|
||||||
|
end)
|
||||||
|
assert(co() == 100); assert(x == 0)
|
||||||
|
local st, msg = pcall(co); assert(x == 2)
|
||||||
|
-- should get first error raised
|
||||||
assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
|
assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue