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. 24
      testes/main.lua

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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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? */

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,
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).
}
}
}

45
testes/coroutine.lua

@ -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()

24
testes/main.lua

@ -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…
Cancel
Save