diff --git a/lcorolib.c b/lcorolib.c index f7c9e165..a21880d5 100644 --- a/lcorolib.c +++ b/lcorolib.c @@ -25,6 +25,10 @@ static lua_State *getco (lua_State *L) { } +/* +** Resumes a coroutine. Returns the number of results for non-error +** cases or -1 for errors. +*/ static int auxresume (lua_State *L, lua_State *co, int narg) { int status, nres; if (!lua_checkstack(co, narg)) { @@ -74,8 +78,14 @@ static int luaB_auxwrap (lua_State *L) { lua_State *co = lua_tothread(L, lua_upvalueindex(1)); 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 (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */ - luaL_where(L, 1); /* add extra info */ + luaL_where(L, 1); /* add extra info, if available */ lua_insert(L, -2); lua_concat(L, 2); } diff --git a/ldo.c b/ldo.c index 2a98c397..d474c0a0 100644 --- a/ldo.c +++ b/ldo.c @@ -686,10 +686,8 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, if (likely(!errorstatus(status))) lua_assert(status == L->status); /* normal end or yield */ else { /* unrecoverable error */ - status = luaF_close(L, L->stack, status); /* close all upvalues */ L->status = cast_byte(status); /* mark thread as 'dead' */ - luaD_seterrorobj(L, status, L->stack + 1); /* push error message */ - L->ci = &L->base_ci; /* back to the original C level */ + luaD_seterrorobj(L, status, L->top); /* push error message */ L->ci->top = L->top; } *nresults = (status == LUA_YIELD) ? L->ci->u2.nyield diff --git a/manual/manual.of b/manual/manual.of index 5f265708..cf44b4f2 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -286,7 +286,7 @@ Lua also offers a system of @emph{warnings} @seeF{warn}. Unlike errors, warnings do not interfere in any way with program execution. They typically only generate a message to the user, -although this behavior can be adapted from C @see{lua_setwarnf}. +although this behavior can be adapted from C @seeC{lua_setwarnf}. } @@ -835,6 +835,9 @@ In case of normal termination, plus any values returned by the coroutine main function. In case of errors, @Lid{coroutine.resume} returns @false plus the error object. +In this case, the coroutine does not unwind its stack, +so that it is possible to inspect it after the error +with the debug API. A coroutine yields by calling @Lid{coroutine.yield}. When a coroutine yields, @@ -858,8 +861,10 @@ go as extra arguments to @Lid{coroutine.resume}. @Lid{coroutine.wrap} returns all the values returned by @Lid{coroutine.resume}, except the first one (the boolean error code). Unlike @Lid{coroutine.resume}, -@Lid{coroutine.wrap} does not catch errors; -any error is propagated to the caller. +the function created by @Lid{coroutine.wrap} +propagates any error to the caller. +In this case, +the function also kills the coroutine @seeF{coroutine.kill}. As an example of how coroutines work, consider the following code: @@ -1534,8 +1539,15 @@ the other pending closing methods will still be called. If a coroutine yields inside a block and is never resumed again, the variables visible at that block will never go out of scope, and therefore they will not be closed. -(You should use finalizers to handle this case, -or else call @Lid{coroutine.kill} to close the variables.) +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.kill} to close the variables in these cases. +However, note that if the coroutine was created +through @Lid{coroutine.wrap}, +then its corresponding function will close all variables +in case of errors. } @@ -6406,11 +6418,12 @@ or if it has stopped with an error. Creates a new coroutine, with body @id{f}; @id{f} must be a function. Returns a function that resumes the coroutine each time it is called. -Any arguments passed to the function behave as the +Any arguments passed to this function behave as the extra arguments to @id{resume}. -Returns the same values returned by @id{resume}, +The function returns the same values returned by @id{resume}, except the first boolean. -In case of error, propagates the error. +In case of error, +the function kills the coroutine and propagates the error. } @@ -6668,6 +6681,10 @@ the file path where the module was found, as returned by @Lid{package.searchpath}. The first searcher always returns the string @St{:preload:}. +Searchers should raise no errors and have no side effects in Lua. +(They may have side effects in C, +for instance by linking the application with a library.) + } @LibEntry{package.searchpath (name, path [, sep [, rep]])| diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 35ff27fb..9dd501e7 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -346,9 +346,13 @@ do local st, res = coroutine.resume(B) assert(st == true and res == false) - A = coroutine.wrap(function() return pcall(A, 1) end) + local X = false + A = coroutine.wrap(function() + local *toclose _ = setmetatable({}, {__close = function () X = true end}) + return pcall(A, 1) + end) st, res = A() - assert(not st and string.find(res, "non%-suspended")) + assert(not st and string.find(res, "non%-suspended") and X == true) end diff --git a/testes/db.lua b/testes/db.lua index 95275fb4..3d94f776 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -734,18 +734,24 @@ a, b = coroutine.resume(co, 100) assert(a and b == 30) --- check traceback of suspended coroutines +-- check traceback of suspended (or dead with error) coroutines + +function f(i) + if i == 0 then error(i) + else coroutine.yield(); f(i-1) + end +end -function f(i) coroutine.yield(i == 0); f(i - 1) end co = coroutine.create(function (x) f(x) end) a, b = coroutine.resume(co, 3) t = {"'coroutine.yield'", "'f'", "in function <"} -repeat +while coroutine.status(co) == "suspended" do checktraceback(co, t) a, b = coroutine.resume(co) table.insert(t, 2, "'f'") -- one more recursive call to 'f' -until b +end +t[1] = "'error'" checktraceback(co, t) diff --git a/testes/locals.lua b/testes/locals.lua index de47ae31..814d1b16 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -417,12 +417,13 @@ if rawget(_G, "T") then end --- to-be-closed variables in coroutines +print "to-be-closed variables in coroutines" + do - -- an error in a coroutine closes variables + -- an error in a wrapped coroutine closes variables local x = false local y = false - local co = coroutine.create(function () + local co = coroutine.wrap(function () local *toclose xv = func2close(function () x = true end) do local *toclose yv = func2close(function () y = true end) @@ -432,14 +433,31 @@ do error(23) -- error does end) - local a, b = coroutine.resume(co) - assert(a and b == 100 and not x and not y) - a, b = coroutine.resume(co) - assert(a and b == 200 and not x and y) - a, b = coroutine.resume(co) + local b = co() + assert(b == 100 and not x and not y) + b = co() + assert(b == 200 and not x and y) + local a, b = pcall(co) assert(not a and b == 23 and x and y) end + +do + -- error in a wrapped coroutine raising errors when closing a variable + local x = false + local co = coroutine.wrap(function () + local *toclose xv = func2close(function () error("XXX") end) + coroutine.yield(100) + error(200) + end) + assert(co() == 100) + local st, msg = pcall(co) +print(msg) + -- should get last error raised + assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX")) +end + + -- a suspended coroutine should not close its variables when collected local co co = coroutine.wrap(function() @@ -449,6 +467,7 @@ co = coroutine.wrap(function() end) co() -- start coroutine assert(co == nil) -- eventually it will be collected +collectgarbage() -- to-be-closed variables in generic for loops