mirror of https://github.com/rusefi/lua.git
New functions 'lua_resetthread' and 'coroutine.kill'
New functions to reset/kill a thread/coroutine, mainly (only?) to close any pending to-be-closed variable. ('lua_resetthread' also allows a thread to be reused...)
This commit is contained in:
parent
3b06f983ae
commit
fdc25a1ebf
58
lcorolib.c
58
lcorolib.c
|
@ -107,29 +107,40 @@ static int luaB_yield (lua_State *L) {
|
|||
}
|
||||
|
||||
|
||||
static int luaB_costatus (lua_State *L) {
|
||||
lua_State *co = getco(L);
|
||||
if (L == co) lua_pushliteral(L, "running");
|
||||
#define COS_RUN 0
|
||||
#define COS_DEAD 1
|
||||
#define COS_YIELD 2
|
||||
#define COS_NORM 3
|
||||
|
||||
|
||||
static const char *statname[] = {"running", "dead", "suspended", "normal"};
|
||||
|
||||
|
||||
static int auxstatus (lua_State *L, lua_State *co) {
|
||||
if (L == co) return COS_RUN;
|
||||
else {
|
||||
switch (lua_status(co)) {
|
||||
case LUA_YIELD:
|
||||
lua_pushliteral(L, "suspended");
|
||||
break;
|
||||
return COS_YIELD;
|
||||
case LUA_OK: {
|
||||
lua_Debug ar;
|
||||
if (lua_getstack(co, 0, &ar) > 0) /* does it have frames? */
|
||||
lua_pushliteral(L, "normal"); /* it is running */
|
||||
if (lua_getstack(co, 0, &ar)) /* does it have frames? */
|
||||
return COS_NORM; /* it is running */
|
||||
else if (lua_gettop(co) == 0)
|
||||
lua_pushliteral(L, "dead");
|
||||
return COS_DEAD;
|
||||
else
|
||||
lua_pushliteral(L, "suspended"); /* initial state */
|
||||
break;
|
||||
return COS_YIELD; /* initial state */
|
||||
}
|
||||
default: /* some error occurred */
|
||||
lua_pushliteral(L, "dead");
|
||||
break;
|
||||
return COS_DEAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int luaB_costatus (lua_State *L) {
|
||||
lua_State *co = getco(L);
|
||||
lua_pushstring(L, statname[auxstatus(L, co)]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -147,6 +158,28 @@ static int luaB_corunning (lua_State *L) {
|
|||
}
|
||||
|
||||
|
||||
static int luaB_kill (lua_State *L) {
|
||||
lua_State *co = getco(L);
|
||||
int status = auxstatus(L, co);
|
||||
switch (status) {
|
||||
case COS_DEAD: case COS_YIELD: {
|
||||
status = lua_resetthread(co);
|
||||
if (status == LUA_OK) {
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
lua_pushboolean(L, 0);
|
||||
lua_xmove(co, L, 1); /* copy error message */
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
default: /* normal or running coroutine */
|
||||
return luaL_error(L, "cannot kill a %s coroutine", statname[status]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const luaL_Reg co_funcs[] = {
|
||||
{"create", luaB_cocreate},
|
||||
{"resume", luaB_coresume},
|
||||
|
@ -155,6 +188,7 @@ static const luaL_Reg co_funcs[] = {
|
|||
{"wrap", luaB_cowrap},
|
||||
{"yield", luaB_yield},
|
||||
{"isyieldable", luaB_yieldable},
|
||||
{"kill", luaB_kill},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
4
ldo.c
4
ldo.c
|
@ -98,6 +98,10 @@ void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {
|
|||
setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling"));
|
||||
break;
|
||||
}
|
||||
case CLOSEPROTECT: {
|
||||
setnilvalue(s2v(oldtop)); /* no error message */
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
setobjs2s(L, oldtop, L->top - 1); /* error message on current top */
|
||||
break;
|
||||
|
|
18
lfunc.c
18
lfunc.c
|
@ -127,17 +127,18 @@ static int prepclosingmethod (lua_State *L, TValue *obj, TValue *err) {
|
|||
|
||||
|
||||
/*
|
||||
** Prepare and call a closing method. If status is OK, code is
|
||||
** still inside the original protected call, and so any error
|
||||
** will be handled there. Otherwise, a previous error already
|
||||
** activated original protected call, and so the call to the
|
||||
** closing method must be protected here.
|
||||
** Prepare and call a closing method. If status is OK, code is still
|
||||
** inside the original protected call, and so any error will be handled
|
||||
** there. Otherwise, a previous error already activated original
|
||||
** protected call, and so the call to the closing method must be
|
||||
** protected here. (A status = CLOSEPROTECT behaves like a previous
|
||||
** error, to also run the closing method in protected mode).
|
||||
** If status is OK, the call to the closing method will be pushed
|
||||
** at the top of the stack. Otherwise, values are pushed after
|
||||
** the 'level' of the upvalue being closed, as everything after
|
||||
** that won't be used again.
|
||||
*/
|
||||
static int closeupval (lua_State *L, TValue *uv, StkId level, int status) {
|
||||
static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) {
|
||||
if (likely(status == LUA_OK)) {
|
||||
if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */
|
||||
callclose(L, NULL); /* call closing method */
|
||||
|
@ -207,9 +208,10 @@ int luaF_close (lua_State *L, StkId level, int status) {
|
|||
if (!iswhite(uv))
|
||||
gray2black(uv); /* closed upvalues cannot be gray */
|
||||
luaC_barrier(L, uv, slot);
|
||||
if (status >= 0 && uv->tt == LUA_TUPVALTBC) { /* must be closed? */
|
||||
if (uv->tt == LUA_TUPVALTBC && status != NOCLOSINGMETH) {
|
||||
/* must run closing method */
|
||||
ptrdiff_t levelrel = savestack(L, level);
|
||||
status = closeupval(L, uv->v, upl, status); /* may realloc. the stack */
|
||||
status = callclosemth(L, uv->v, upl, status); /* may change the stack */
|
||||
level = restorestack(L, levelrel);
|
||||
}
|
||||
}
|
||||
|
|
11
lfunc.h
11
lfunc.h
|
@ -42,6 +42,17 @@
|
|||
#define MAXMISS 10
|
||||
|
||||
|
||||
/*
|
||||
** Special "status" for 'luaF_close'
|
||||
*/
|
||||
|
||||
/* close upvalues without running their closing methods */
|
||||
#define NOCLOSINGMETH (-1)
|
||||
|
||||
/* close upvalues running all closing methods in protected mode */
|
||||
#define CLOSEPROTECT (-2)
|
||||
|
||||
|
||||
LUAI_FUNC Proto *luaF_newproto (lua_State *L);
|
||||
LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems);
|
||||
LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems);
|
||||
|
|
27
lstate.c
27
lstate.c
|
@ -258,7 +258,7 @@ static void preinit_thread (lua_State *L, global_State *g) {
|
|||
|
||||
static void close_state (lua_State *L) {
|
||||
global_State *g = G(L);
|
||||
luaF_close(L, L->stack, -1); /* close all upvalues for this thread */
|
||||
luaF_close(L, L->stack, CLOSEPROTECT); /* close all upvalues */
|
||||
luaC_freeallobjects(L); /* collect all objects */
|
||||
if (ttisnil(&g->nilvalue)) /* closing a fully built state? */
|
||||
luai_userstateclose(L);
|
||||
|
@ -301,7 +301,7 @@ LUA_API lua_State *lua_newthread (lua_State *L) {
|
|||
|
||||
void luaE_freethread (lua_State *L, lua_State *L1) {
|
||||
LX *l = fromstate(L1);
|
||||
luaF_close(L1, L1->stack, -1); /* close all upvalues for this thread */
|
||||
luaF_close(L1, L1->stack, NOCLOSINGMETH); /* close all upvalues */
|
||||
lua_assert(L1->openupval == NULL);
|
||||
luai_userstatefree(L, L1);
|
||||
freestack(L1);
|
||||
|
@ -309,6 +309,29 @@ void luaE_freethread (lua_State *L, lua_State *L1) {
|
|||
}
|
||||
|
||||
|
||||
int lua_resetthread (lua_State *L) {
|
||||
CallInfo *ci;
|
||||
int status;
|
||||
lua_lock(L);
|
||||
ci = &L->base_ci;
|
||||
status = luaF_close(L, L->stack, CLOSEPROTECT);
|
||||
setnilvalue(s2v(L->stack)); /* 'function' entry for basic 'ci' */
|
||||
if (status != CLOSEPROTECT) /* real errors? */
|
||||
luaD_seterrorobj(L, status, L->stack + 1);
|
||||
else {
|
||||
status = LUA_OK;
|
||||
L->top = L->stack + 1;
|
||||
}
|
||||
ci->callstatus = CIST_C;
|
||||
ci->func = L->stack;
|
||||
ci->top = L->top + LUA_MINSTACK;
|
||||
L->ci = ci;
|
||||
L->status = status;
|
||||
lua_unlock(L);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
|
||||
int i;
|
||||
lua_State *L;
|
||||
|
|
3
ltests.c
3
ltests.c
|
@ -1366,6 +1366,9 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
|
|||
else if EQ("newthread") {
|
||||
lua_newthread(L1);
|
||||
}
|
||||
else if EQ("resetthread") {
|
||||
lua_pushinteger(L1, lua_resetthread(L1));
|
||||
}
|
||||
else if EQ("newuserdata") {
|
||||
lua_newuserdata(L1, getnum);
|
||||
}
|
||||
|
|
1
lua.h
1
lua.h
|
@ -147,6 +147,7 @@ extern const char lua_ident[];
|
|||
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
|
||||
LUA_API void (lua_close) (lua_State *L);
|
||||
LUA_API lua_State *(lua_newthread) (lua_State *L);
|
||||
LUA_API int (lua_resetthread) (lua_State *L);
|
||||
|
||||
LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);
|
||||
|
||||
|
|
2
lvm.c
2
lvm.c
|
@ -1565,7 +1565,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
|
|||
if (nparams1) /* vararg function? */
|
||||
delta = ci->u.l.nextraargs + nparams1;
|
||||
/* close upvalues from current call */
|
||||
luaF_close(L, base, -1); /* (no to-be-closed vars. here) */
|
||||
luaF_close(L, base, LUA_OK);
|
||||
updatestack(ci);
|
||||
}
|
||||
if (!ttisfunction(s2v(ra))) { /* not a function? */
|
||||
|
|
|
@ -3927,6 +3927,19 @@ and then pops the top element.
|
|||
|
||||
}
|
||||
|
||||
@APIEntry{int lua_resetthread (lua_State *L);|
|
||||
@apii{0,?,-}
|
||||
|
||||
Resets a thread, cleaning its call stack and closing all pending
|
||||
to-be-closed variables.
|
||||
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,
|
||||
|
||||
}
|
||||
|
||||
@APIEntry{int lua_resume (lua_State *L, lua_State *from, int nargs,
|
||||
int *nresults);|
|
||||
@apii{?,?,-}
|
||||
|
@ -3948,11 +3961,8 @@ or returned by the body function.
|
|||
@Lid{LUA_OK} if the coroutine finishes its execution
|
||||
without errors,
|
||||
or an error code in case of errors @seeC{lua_pcall}.
|
||||
|
||||
In case of errors,
|
||||
the stack is not unwound,
|
||||
so you can use the debug API over it.
|
||||
The error object is on the top of the stack.
|
||||
the error object is on the top of the stack.
|
||||
|
||||
To resume a coroutine,
|
||||
you remove all results from the last @Lid{lua_yield},
|
||||
|
@ -6285,6 +6295,17 @@ it is not inside a non-yieldable @N{C function}.
|
|||
|
||||
}
|
||||
|
||||
@LibEntry{coroutine.kill(co)|
|
||||
|
||||
Kills coroutine @id{co},
|
||||
closing all its pending to-be-closed variables
|
||||
and putting the coroutine in a dead state.
|
||||
In case of error closing some variable,
|
||||
returns @false plus the error object;
|
||||
otherwise returns @true.
|
||||
|
||||
}
|
||||
|
||||
@LibEntry{coroutine.resume (co [, val1, @Cdots])|
|
||||
|
||||
Starts or continues the execution of coroutine @id{co}.
|
||||
|
@ -8648,6 +8669,11 @@ has been removed.
|
|||
When needed, this metamethod must be explicitly defined.
|
||||
}
|
||||
|
||||
@item{
|
||||
When a coroutine finishes with an error,
|
||||
its stack is unwound (to run any pending closing methods).
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -119,6 +119,51 @@ end
|
|||
assert(#a == 25 and a[#a] == 97)
|
||||
x, a = nil
|
||||
|
||||
|
||||
-- coroutine kill
|
||||
do
|
||||
-- ok to kill a dead coroutine
|
||||
local co = coroutine.create(print)
|
||||
assert(coroutine.resume(co, "testing 'coroutine.kill'"))
|
||||
assert(coroutine.status(co) == "dead")
|
||||
assert(coroutine.kill(co))
|
||||
|
||||
-- cannot kill the running coroutine
|
||||
local st, msg = pcall(coroutine.kill, coroutine.running())
|
||||
assert(not st and string.find(msg, "running"))
|
||||
|
||||
local main = coroutine.running()
|
||||
|
||||
-- cannot kill a "normal" coroutine
|
||||
;(coroutine.wrap(function ()
|
||||
local st, msg = pcall(coroutine.kill, main)
|
||||
assert(not st and string.find(msg, "normal"))
|
||||
end))()
|
||||
|
||||
-- to-be-closed variables in coroutines
|
||||
local X
|
||||
co = coroutine.create(function ()
|
||||
local *toclose x = function (err) assert(err == nil); X = false end
|
||||
X = true
|
||||
coroutine.yield()
|
||||
end)
|
||||
coroutine.resume(co)
|
||||
assert(X)
|
||||
assert(coroutine.kill(co))
|
||||
assert(not X and coroutine.status(co) == "dead")
|
||||
|
||||
-- error killing a coroutine
|
||||
co = coroutine.create(function()
|
||||
local *toclose x = function (err) assert(err == nil); error(111) end
|
||||
coroutine.yield()
|
||||
end)
|
||||
coroutine.resume(co)
|
||||
local st, msg = coroutine.kill(co)
|
||||
assert(not st and coroutine.status(co) == "dead" and msg == 111)
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- yielding across C boundaries
|
||||
|
||||
co = coroutine.wrap(function()
|
||||
|
|
|
@ -254,15 +254,15 @@ NoRun("error object is a table value", [[lua %s]], prog)
|
|||
|
||||
|
||||
-- chunk broken in many lines
|
||||
s = [=[ --
|
||||
function f ( x )
|
||||
s = [=[ --
|
||||
function f ( x )
|
||||
local a = [[
|
||||
xuxu
|
||||
]]
|
||||
local b = "\
|
||||
xuxu\n"
|
||||
if x == 11 then return 1 + 12 , 2 + 20 end --[[ test multiple returns ]]
|
||||
return x + 1
|
||||
return x + 1
|
||||
--\\
|
||||
end
|
||||
return( f( 100 ) )
|
||||
|
@ -272,10 +272,10 @@ s = string.gsub(s, ' ', '\n\n') -- change all spaces for newlines
|
|||
prepfile(s)
|
||||
RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
|
||||
checkprogout("101\n13\t22\n\n")
|
||||
|
||||
|
||||
prepfile[[#comment in 1st line without \n at the end]]
|
||||
RUN('lua %s', prog)
|
||||
|
||||
|
||||
prepfile[[#test line number when file starts with comment line
|
||||
debug = require"debug"
|
||||
print(debug.getinfo(1).currentline)
|
||||
|
@ -306,6 +306,20 @@ NoRun("", "lua %s", prog) -- no message
|
|||
prepfile("os.exit(false, true)")
|
||||
NoRun("", "lua %s", prog) -- no message
|
||||
|
||||
|
||||
-- to-be-closed variables in main chunk
|
||||
prepfile[[
|
||||
local *toclose x = function (err)
|
||||
assert(err == 120)
|
||||
print("Ok")
|
||||
end
|
||||
local *toclose e1 = function () error(120) end
|
||||
os.exit(true, true)
|
||||
]]
|
||||
RUN('lua %s > %s', prog, out)
|
||||
checkprogout("Ok")
|
||||
|
||||
|
||||
-- remove temporary files
|
||||
assert(os.remove(prog))
|
||||
assert(os.remove(otherprog))
|
||||
|
|
Loading…
Reference in New Issue