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:
Roberto Ierusalimschy 2019-06-05 13:16:25 -03:00
parent 14edd364c3
commit b4d5dff8ec
5 changed files with 56 additions and 28 deletions

View File

@ -75,11 +75,8 @@ static int luaB_auxwrap (lua_State *L) {
int r = auxresume(L, co, lua_gettop(L));
if (r < 0) {
int stat = lua_status(co);
if (stat != LUA_OK && stat != LUA_YIELD) {
stat = 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 (stat != LUA_OK && stat != LUA_YIELD)
lua_resetthread(co); /* close variables in case of errors */
if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */
luaL_where(L, 1); /* add extra info, if available */
lua_insert(L, -2);

View File

@ -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);
}
}
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' */
luaD_seterrorobj(L, status, level);
if (prepclosingmethod(L, uv, s2v(level))) { /* something to call? */
int newstatus = luaD_pcall(L, callclose, NULL, savestack(L, level), 0);
if (newstatus != LUA_OK) /* another error when closing? */
int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0);
if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first 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 */
}

View File

@ -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,
they are closed in the reverse order that they were declared.
If there is any error while running a closing method,
that error is handled like an error in the regular code
where the variable was defined;
in particular,
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,
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,
it does not unwind its stack,
so it does not close any variable.
You should either use finalizers
or call @Lid{coroutine.close} to close the variables in these cases.
However, note that if the coroutine was created
In both cases,
you should either use finalizers
or call @Lid{coroutine.close} to close the variables.
However, if the coroutine was created
through @Lid{coroutine.wrap},
then its corresponding function will close all variables
then its corresponding function will close the coroutine
in case of errors.
}
@ -3932,7 +3939,7 @@ Returns a status code:
@Lid{LUA_OK} for no errors in closing methods,
or an error status otherwise.
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,
closes all its pending to-be-closed variables
and puts the coroutine in a dead state.
The given coroutine must be dead or suspended.
In case of error closing some variable,
returns @false plus the error object;
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:
@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},
or if it has not started running yet;
@T{"normal"} if the coroutine is active but not running

View File

@ -163,15 +163,23 @@ do
assert(not X and coroutine.status(co) == "dead")
-- error closing a coroutine
local x = 0
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)
assert(err == nil); error(111)
end)
coroutine.yield()
end)
coroutine.resume(co)
assert(x == 0)
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

View File

@ -267,14 +267,14 @@ do -- errors in __close
if err then error(4) end
end
local stat, msg = pcall(foo, false)
assert(msg == 1)
assert(log[1] == 10 and log[2] == 3 and log[3] == 2 and log[4] == 2
assert(msg == 3)
assert(log[1] == 10 and log[2] == 3 and log[3] == 3 and log[4] == 3
and #log == 4)
log = {}
local stat, msg = pcall(foo, true)
assert(msg == 1)
assert(log[1] == 4 and log[2] == 3 and log[3] == 2 and log[4] == 2
assert(msg == 4)
assert(log[1] == 4 and log[2] == 4 and log[3] == 4 and log[4] == 4
and #log == 4)
-- error in toclose in vararg function
@ -317,7 +317,7 @@ if rawget(_G, "T") then
local <toclose> x = setmetatable({}, {__close = function ()
T.alloccount(0); local x = {} -- force a memory error
end})
error("a") -- common error inside the function's body
error(1000) -- common error inside the function's body
end
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
-- memory limit will be lifted
local _, msg = pcall(foo)
assert(msg == "not enough memory")
assert(msg == 1000)
local close = func2close(function (self, msg)
T.alloccount()
@ -368,8 +368,7 @@ if rawget(_G, "T") then
end
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
collectgarbage()
@ -453,15 +452,27 @@ end
do
-- error in a wrapped coroutine raising errors when closing a variable
local x = false
local x = 0
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)
error(200)
end)
assert(co() == 100)
local st, msg = pcall(co)
-- should get last error raised
assert(co() == 100); assert(x == 0)
local st, msg = pcall(co); assert(x == 2)
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"))
end