mirror of https://github.com/rusefi/lua.git
Coroutines do not unwind the stack in case of errors
Back to how it was, a coroutine does not unwind its stack in case of errors (and therefore do not close its to-be-closed variables). This allows the stack to be examined after the error. The program can use 'coroutine.kill' to close the variables. The function created by 'coroutine.wrap', however, closes the coroutine's variables in case of errors, as it is impossible to examine the stack any way.
This commit is contained in:
parent
01bded3d8c
commit
389116d8ab
12
lcorolib.c
12
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) {
|
static int auxresume (lua_State *L, lua_State *co, int narg) {
|
||||||
int status, nres;
|
int status, nres;
|
||||||
if (!lua_checkstack(co, narg)) {
|
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));
|
lua_State *co = lua_tothread(L, lua_upvalueindex(1));
|
||||||
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);
|
||||||
|
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? */
|
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_insert(L, -2);
|
||||||
lua_concat(L, 2);
|
lua_concat(L, 2);
|
||||||
}
|
}
|
||||||
|
|
4
ldo.c
4
ldo.c
|
@ -686,10 +686,8 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
|
||||||
if (likely(!errorstatus(status)))
|
if (likely(!errorstatus(status)))
|
||||||
lua_assert(status == L->status); /* normal end or yield */
|
lua_assert(status == L->status); /* normal end or yield */
|
||||||
else { /* unrecoverable error */
|
else { /* unrecoverable error */
|
||||||
status = luaF_close(L, L->stack, status); /* close all upvalues */
|
|
||||||
L->status = cast_byte(status); /* mark thread as 'dead' */
|
L->status = cast_byte(status); /* mark thread as 'dead' */
|
||||||
luaD_seterrorobj(L, status, L->stack + 1); /* push error message */
|
luaD_seterrorobj(L, status, L->top); /* push error message */
|
||||||
L->ci = &L->base_ci; /* back to the original C level */
|
|
||||||
L->ci->top = L->top;
|
L->ci->top = L->top;
|
||||||
}
|
}
|
||||||
*nresults = (status == LUA_YIELD) ? L->ci->u2.nyield
|
*nresults = (status == LUA_YIELD) ? L->ci->u2.nyield
|
||||||
|
|
|
@ -286,7 +286,7 @@ Lua also offers a system of @emph{warnings} @seeF{warn}.
|
||||||
Unlike errors, warnings do not interfere
|
Unlike errors, warnings do not interfere
|
||||||
in any way with program execution.
|
in any way with program execution.
|
||||||
They typically only generate a message to the user,
|
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.
|
plus any values returned by the coroutine main function.
|
||||||
In case of errors, @Lid{coroutine.resume} returns @false
|
In case of errors, @Lid{coroutine.resume} returns @false
|
||||||
plus the error object.
|
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}.
|
A coroutine yields by calling @Lid{coroutine.yield}.
|
||||||
When a coroutine yields,
|
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},
|
@Lid{coroutine.wrap} returns all the values returned by @Lid{coroutine.resume},
|
||||||
except the first one (the boolean error code).
|
except the first one (the boolean error code).
|
||||||
Unlike @Lid{coroutine.resume},
|
Unlike @Lid{coroutine.resume},
|
||||||
@Lid{coroutine.wrap} does not catch errors;
|
the function created by @Lid{coroutine.wrap}
|
||||||
any error is propagated to the caller.
|
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,
|
As an example of how coroutines work,
|
||||||
consider the following code:
|
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,
|
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,
|
||||||
and therefore they will not be closed.
|
and therefore they will not be closed.
|
||||||
(You should use finalizers to handle this case,
|
Similarly, if a coroutine ends with an error,
|
||||||
or else call @Lid{coroutine.kill} to close the variables.)
|
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};
|
Creates a new coroutine, with body @id{f};
|
||||||
@id{f} must be a function.
|
@id{f} must be a function.
|
||||||
Returns a function that resumes the coroutine each time it is called.
|
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}.
|
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.
|
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}.
|
as returned by @Lid{package.searchpath}.
|
||||||
The first searcher always returns the string @St{:preload:}.
|
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]])|
|
@LibEntry{package.searchpath (name, path [, sep [, rep]])|
|
||||||
|
|
|
@ -346,9 +346,13 @@ do
|
||||||
local st, res = coroutine.resume(B)
|
local st, res = coroutine.resume(B)
|
||||||
assert(st == true and res == false)
|
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()
|
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
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -734,18 +734,24 @@ a, b = coroutine.resume(co, 100)
|
||||||
assert(a and b == 30)
|
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)
|
co = coroutine.create(function (x) f(x) end)
|
||||||
a, b = coroutine.resume(co, 3)
|
a, b = coroutine.resume(co, 3)
|
||||||
t = {"'coroutine.yield'", "'f'", "in function <"}
|
t = {"'coroutine.yield'", "'f'", "in function <"}
|
||||||
repeat
|
while coroutine.status(co) == "suspended" do
|
||||||
checktraceback(co, t)
|
checktraceback(co, t)
|
||||||
a, b = coroutine.resume(co)
|
a, b = coroutine.resume(co)
|
||||||
table.insert(t, 2, "'f'") -- one more recursive call to 'f'
|
table.insert(t, 2, "'f'") -- one more recursive call to 'f'
|
||||||
until b
|
end
|
||||||
|
t[1] = "'error'"
|
||||||
checktraceback(co, t)
|
checktraceback(co, t)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -417,12 +417,13 @@ if rawget(_G, "T") then
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- to-be-closed variables in coroutines
|
print "to-be-closed variables in coroutines"
|
||||||
|
|
||||||
do
|
do
|
||||||
-- an error in a coroutine closes variables
|
-- an error in a wrapped coroutine closes variables
|
||||||
local x = false
|
local x = false
|
||||||
local y = false
|
local y = false
|
||||||
local co = coroutine.create(function ()
|
local co = coroutine.wrap(function ()
|
||||||
local *toclose xv = func2close(function () x = true end)
|
local *toclose xv = func2close(function () x = true end)
|
||||||
do
|
do
|
||||||
local *toclose yv = func2close(function () y = true end)
|
local *toclose yv = func2close(function () y = true end)
|
||||||
|
@ -432,14 +433,31 @@ do
|
||||||
error(23) -- error does
|
error(23) -- error does
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local a, b = coroutine.resume(co)
|
local b = co()
|
||||||
assert(a and b == 100 and not x and not y)
|
assert(b == 100 and not x and not y)
|
||||||
a, b = coroutine.resume(co)
|
b = co()
|
||||||
assert(a and b == 200 and not x and y)
|
assert(b == 200 and not x and y)
|
||||||
a, b = coroutine.resume(co)
|
local a, b = pcall(co)
|
||||||
assert(not a and b == 23 and x and y)
|
assert(not a and b == 23 and x and y)
|
||||||
end
|
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
|
-- a suspended coroutine should not close its variables when collected
|
||||||
local co
|
local co
|
||||||
co = coroutine.wrap(function()
|
co = coroutine.wrap(function()
|
||||||
|
@ -449,6 +467,7 @@ co = coroutine.wrap(function()
|
||||||
end)
|
end)
|
||||||
co() -- start coroutine
|
co() -- start coroutine
|
||||||
assert(co == nil) -- eventually it will be collected
|
assert(co == nil) -- eventually it will be collected
|
||||||
|
collectgarbage()
|
||||||
|
|
||||||
|
|
||||||
-- to-be-closed variables in generic for loops
|
-- to-be-closed variables in generic for loops
|
||||||
|
|
Loading…
Reference in New Issue