diff --git a/lcorolib.c b/lcorolib.c index 156839e6..4d47ea28 100644 --- a/lcorolib.c +++ b/lcorolib.c @@ -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); diff --git a/lfunc.c b/lfunc.c index 3e044b65..55114992 100644 --- a/lfunc.c +++ b/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); } } - 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 */ } diff --git a/manual/manual.of b/manual/manual.of index 4c9c20b2..2c0957b9 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -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 diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 198a5870..f2c0da8b 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -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 y = func2close(function (self,err) + if (err ~= 111) then os.exit(false) end -- should not happen + x = 200 + error(200) + end) local 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 diff --git a/testes/locals.lua b/testes/locals.lua index 7834d7da..dccda28f 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -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 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 xv = func2close(function () error("XXX") end) + local xx = func2close(function () x = x + 1; error("YYY") end) + local 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 xx = func2close(function () x = x + 1; error("YYY") end) + local 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