Browse Source

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...)
pull/22/head
Roberto Ierusalimschy 6 years ago
parent
commit
fdc25a1ebf
  1. 58
      lcorolib.c
  2. 4
      ldo.c
  3. 18
      lfunc.c
  4. 11
      lfunc.h
  5. 27
      lstate.c
  6. 3
      ltests.c
  7. 1
      lua.h
  8. 2
      lvm.c
  9. 34
      manual/manual.of
  10. 45
      testes/coroutine.lua
  11. 14
      testes/main.lua

58
lcorolib.c

@ -107,29 +107,40 @@ static int luaB_yield (lua_State *L) {
} }
static int luaB_costatus (lua_State *L) { #define COS_RUN 0
lua_State *co = getco(L); #define COS_DEAD 1
if (L == co) lua_pushliteral(L, "running"); #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 { else {
switch (lua_status(co)) { switch (lua_status(co)) {
case LUA_YIELD: case LUA_YIELD:
lua_pushliteral(L, "suspended"); return COS_YIELD;
break;
case LUA_OK: { case LUA_OK: {
lua_Debug ar; lua_Debug ar;
if (lua_getstack(co, 0, &ar) > 0) /* does it have frames? */ if (lua_getstack(co, 0, &ar)) /* does it have frames? */
lua_pushliteral(L, "normal"); /* it is running */ return COS_NORM; /* it is running */
else if (lua_gettop(co) == 0) else if (lua_gettop(co) == 0)
lua_pushliteral(L, "dead"); return COS_DEAD;
else else
lua_pushliteral(L, "suspended"); /* initial state */ return COS_YIELD; /* initial state */
break;
} }
default: /* some error occurred */ default: /* some error occurred */
lua_pushliteral(L, "dead"); return COS_DEAD;
break;
} }
} }
}
static int luaB_costatus (lua_State *L) {
lua_State *co = getco(L);
lua_pushstring(L, statname[auxstatus(L, co)]);
return 1; 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[] = { static const luaL_Reg co_funcs[] = {
{"create", luaB_cocreate}, {"create", luaB_cocreate},
{"resume", luaB_coresume}, {"resume", luaB_coresume},
@ -155,6 +188,7 @@ static const luaL_Reg co_funcs[] = {
{"wrap", luaB_cowrap}, {"wrap", luaB_cowrap},
{"yield", luaB_yield}, {"yield", luaB_yield},
{"isyieldable", luaB_yieldable}, {"isyieldable", luaB_yieldable},
{"kill", luaB_kill},
{NULL, NULL} {NULL, NULL}
}; };

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")); setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling"));
break; break;
} }
case CLOSEPROTECT: {
setnilvalue(s2v(oldtop)); /* no error message */
break;
}
default: { default: {
setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ setobjs2s(L, oldtop, L->top - 1); /* error message on current top */
break; break;

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 ** Prepare and call a closing method. If status is OK, code is still
** still inside the original protected call, and so any error ** inside the original protected call, and so any error will be handled
** will be handled there. Otherwise, a previous error already ** there. Otherwise, a previous error already activated original
** activated original protected call, and so the call to the ** protected call, and so the call to the closing method must be
** closing method must be protected here. ** 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 ** If status is OK, the call to the closing method will be pushed
** at the top of the stack. Otherwise, values are pushed after ** at the top of the stack. Otherwise, values are pushed after
** the 'level' of the upvalue being closed, as everything after ** the 'level' of the upvalue being closed, as everything after
** that won't be used again. ** 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 (likely(status == LUA_OK)) {
if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */ if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */
callclose(L, NULL); /* call closing method */ callclose(L, NULL); /* call closing method */
@ -207,9 +208,10 @@ int luaF_close (lua_State *L, StkId level, int status) {
if (!iswhite(uv)) if (!iswhite(uv))
gray2black(uv); /* closed upvalues cannot be gray */ gray2black(uv); /* closed upvalues cannot be gray */
luaC_barrier(L, uv, slot); 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); 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); level = restorestack(L, levelrel);
} }
} }

11
lfunc.h

@ -42,6 +42,17 @@
#define MAXMISS 10 #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 Proto *luaF_newproto (lua_State *L);
LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems); LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems);
LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems); LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems);

27
lstate.c

@ -258,7 +258,7 @@ static void preinit_thread (lua_State *L, global_State *g) {
static void close_state (lua_State *L) { static void close_state (lua_State *L) {
global_State *g = G(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 */ luaC_freeallobjects(L); /* collect all objects */
if (ttisnil(&g->nilvalue)) /* closing a fully built state? */ if (ttisnil(&g->nilvalue)) /* closing a fully built state? */
luai_userstateclose(L); 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) { void luaE_freethread (lua_State *L, lua_State *L1) {
LX *l = fromstate(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); lua_assert(L1->openupval == NULL);
luai_userstatefree(L, L1); luai_userstatefree(L, L1);
freestack(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) { LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
int i; int i;
lua_State *L; lua_State *L;

3
ltests.c

@ -1366,6 +1366,9 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
else if EQ("newthread") { else if EQ("newthread") {
lua_newthread(L1); lua_newthread(L1);
} }
else if EQ("resetthread") {
lua_pushinteger(L1, lua_resetthread(L1));
}
else if EQ("newuserdata") { else if EQ("newuserdata") {
lua_newuserdata(L1, getnum); lua_newuserdata(L1, getnum);
} }

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 lua_State *(lua_newstate) (lua_Alloc f, void *ud);
LUA_API void (lua_close) (lua_State *L); LUA_API void (lua_close) (lua_State *L);
LUA_API lua_State *(lua_newthread) (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); LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);

2
lvm.c

@ -1565,7 +1565,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
if (nparams1) /* vararg function? */ if (nparams1) /* vararg function? */
delta = ci->u.l.nextraargs + nparams1; delta = ci->u.l.nextraargs + nparams1;
/* close upvalues from current call */ /* close upvalues from current call */
luaF_close(L, base, -1); /* (no to-be-closed vars. here) */ luaF_close(L, base, LUA_OK);
updatestack(ci); updatestack(ci);
} }
if (!ttisfunction(s2v(ra))) { /* not a function? */ if (!ttisfunction(s2v(ra))) { /* not a function? */

34
manual/manual.of

@ -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, @APIEntry{int lua_resume (lua_State *L, lua_State *from, int nargs,
int *nresults);| int *nresults);|
@apii{?,?,-} @apii{?,?,-}
@ -3948,11 +3961,8 @@ or returned by the body function.
@Lid{LUA_OK} if the coroutine finishes its execution @Lid{LUA_OK} if the coroutine finishes its execution
without errors, without errors,
or an error code in case of errors @seeC{lua_pcall}. or an error code in case of errors @seeC{lua_pcall}.
In case of errors, In case of errors,
the stack is not unwound, the error object is on the top of the stack.
so you can use the debug API over it.
The error object is on the top of the stack.
To resume a coroutine, To resume a coroutine,
you remove all results from the last @Lid{lua_yield}, 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])| @LibEntry{coroutine.resume (co [, val1, @Cdots])|
Starts or continues the execution of coroutine @id{co}. Starts or continues the execution of coroutine @id{co}.
@ -8648,6 +8669,11 @@ has been removed.
When needed, this metamethod must be explicitly defined. 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).
}
} }
} }

45
testes/coroutine.lua

@ -119,6 +119,51 @@ end
assert(#a == 25 and a[#a] == 97) assert(#a == 25 and a[#a] == 97)
x, a = nil 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 -- yielding across C boundaries
co = coroutine.wrap(function() co = coroutine.wrap(function()

14
testes/main.lua

@ -306,6 +306,20 @@ NoRun("", "lua %s", prog) -- no message
prepfile("os.exit(false, true)") prepfile("os.exit(false, true)")
NoRun("", "lua %s", prog) -- no message 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 -- remove temporary files
assert(os.remove(prog)) assert(os.remove(prog))
assert(os.remove(otherprog)) assert(os.remove(otherprog))

Loading…
Cancel
Save