mirror of
https://github.com/lua/lua
synced 2025-01-15 05:39:18 +03:00
'__close' method may be called again in case of error
An error in a closing method may be caused by a lack of resources, such as memory or stack space, and the error may free enough resources (by unwinding the stack) to allow the method to work if called again. If the closing method is already running after some error (including its own), it is not called again.
This commit is contained in:
parent
298f383ffc
commit
c220b0a5d0
22
lfunc.c
22
lfunc.c
@ -133,7 +133,8 @@ static int prepclosingmethod (lua_State *L, TValue *obj, TValue *err) {
|
||||
** the 'level' of the upvalue being closed, as everything after
|
||||
** that won't be used again.
|
||||
*/
|
||||
static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) {
|
||||
static int callclosemth (lua_State *L, StkId level, int status) {
|
||||
TValue *uv = s2v(level); /* value being closed */
|
||||
if (likely(status == LUA_OK)) {
|
||||
if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */
|
||||
callclose(L, NULL); /* call closing method */
|
||||
@ -145,9 +146,10 @@ static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) {
|
||||
}
|
||||
}
|
||||
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);
|
||||
ptrdiff_t oldtop;
|
||||
level++; /* space for error message */
|
||||
oldtop = savestack(L, level + 1); /* top will be after that */
|
||||
luaD_seterrorobj(L, status, level); /* set error message */
|
||||
if (prepclosingmethod(L, uv, s2v(level))) { /* something to call? */
|
||||
int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0);
|
||||
if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */
|
||||
@ -203,18 +205,18 @@ int luaF_close (lua_State *L, StkId level, int status) {
|
||||
StkId upl = uplevel(uv);
|
||||
TValue *slot = &uv->u.value; /* new position for value */
|
||||
lua_assert(upl < L->top);
|
||||
if (uv->tt == LUA_TUPVALTBC && status != NOCLOSINGMETH) {
|
||||
/* must run closing method */
|
||||
ptrdiff_t levelrel = savestack(L, level);
|
||||
status = callclosemth(L, upl, status); /* may change the stack */
|
||||
level = restorestack(L, levelrel);
|
||||
}
|
||||
luaF_unlinkupval(uv);
|
||||
setobj(L, slot, uv->v); /* move value to upvalue slot */
|
||||
uv->v = slot; /* now current value lives here */
|
||||
if (!iswhite(uv))
|
||||
gray2black(uv); /* closed upvalues cannot be gray */
|
||||
luaC_barrier(L, uv, slot);
|
||||
if (uv->tt == LUA_TUPVALTBC && status != NOCLOSINGMETH) {
|
||||
/* must run closing method */
|
||||
ptrdiff_t levelrel = savestack(L, level);
|
||||
status = callclosemth(L, uv->v, upl, status); /* may change the stack */
|
||||
level = restorestack(L, levelrel);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
@ -1548,14 +1548,15 @@ 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.
|
||||
where the variable was defined.
|
||||
However, Lua may call the method one more time.
|
||||
|
||||
After an error,
|
||||
other errors in closing methods
|
||||
the other pending closing methods will still be called.
|
||||
Errors in these methods
|
||||
interrupt the respective method,
|
||||
but are otherwise ignored;
|
||||
the error reported is the original one.
|
||||
the error reported is only the original one.
|
||||
|
||||
If a coroutine yields and is never resumed again,
|
||||
some variables may never go out of scope,
|
||||
|
@ -295,18 +295,23 @@ do -- errors in __close
|
||||
local <toclose> y =
|
||||
func2close(function (self, msg) log[#log + 1] = msg; error(2) end)
|
||||
local <toclose> z =
|
||||
func2close(function (self, msg) log[#log + 1] = msg or 10; error(3) end)
|
||||
func2close(function (self, msg)
|
||||
log[#log + 1] = (msg or 10) + 1;
|
||||
error(3)
|
||||
end)
|
||||
if err then error(4) end
|
||||
end
|
||||
local stat, msg = pcall(foo, false)
|
||||
assert(msg == 3)
|
||||
assert(log[1] == 10 and log[2] == 3 and log[3] == 3 and log[4] == 3
|
||||
and #log == 4)
|
||||
-- 'z' close is called twice
|
||||
assert(log[1] == 11 and log[2] == 4 and log[3] == 3 and log[4] == 3
|
||||
and log[5] == 3 and #log == 5)
|
||||
|
||||
log = {}
|
||||
local stat, msg = pcall(foo, true)
|
||||
assert(msg == 4)
|
||||
assert(log[1] == 4 and log[2] == 4 and log[3] == 4 and log[4] == 4
|
||||
-- 'z' close is called once
|
||||
assert(log[1] == 5 and log[2] == 4 and log[3] == 4 and log[4] == 4
|
||||
and #log == 4)
|
||||
|
||||
-- error in toclose in vararg function
|
||||
@ -495,15 +500,17 @@ do
|
||||
local st, msg = pcall(co); assert(x == 2)
|
||||
assert(not st and msg == 200) -- should get first error raised
|
||||
|
||||
x = 0
|
||||
local x = 0
|
||||
local y = 0
|
||||
co = coroutine.wrap(function ()
|
||||
local <toclose> xx = func2close(function () x = x + 1; error("YYY") end)
|
||||
local <toclose> xx = func2close(function () y = y + 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)
|
||||
local st, msg = pcall(co)
|
||||
assert(x == 2 and y == 1) -- first close is called twice
|
||||
-- should get first error raised
|
||||
assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user