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...)
diff --git a/lcorolib.c b/lcorolib.c
index 34462b5..cdb5fed 100644
--- a/lcorolib.c
+++ b/lcorolib.c
@@ -107,29 +107,40 @@
}
-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_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 @@
{"wrap", luaB_cowrap},
{"yield", luaB_yield},
{"isyieldable", luaB_yieldable},
+ {"kill", luaB_kill},
{NULL, NULL}
};
diff --git a/ldo.c b/ldo.c
index bdd7fb6..056fef0 100644
--- a/ldo.c
+++ b/ldo.c
@@ -98,6 +98,10 @@
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;
diff --git a/lfunc.c b/lfunc.c
index 11d2850..bdf3cd2 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -127,17 +127,18 @@
/*
-** 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 @@
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);
}
}
diff --git a/lfunc.h b/lfunc.h
index c9fe131..0ed79c4 100644
--- a/lfunc.h
+++ b/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);
diff --git a/lstate.c b/lstate.c
index 9d39995..5ee024f 100644
--- a/lstate.c
+++ b/lstate.c
@@ -258,7 +258,7 @@
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 @@
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 @@
}
+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;
diff --git a/ltests.c b/ltests.c
index 63d423e..a38a892 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1366,6 +1366,9 @@
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);
}
diff --git a/lua.h b/lua.h
index 6aa184d..95ce5a2 100644
--- a/lua.h
+++ b/lua.h
@@ -147,6 +147,7 @@
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);
diff --git a/lvm.c b/lvm.c
index fc8722a..652095d 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1565,7 +1565,7 @@
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? */
diff --git a/manual/manual.of b/manual/manual.of
index 0e8e3d7..862d032 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -3927,6 +3927,19 @@
}
+@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 @@
@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 @@
}
+@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 @@
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).
+}
+
}
}
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index 7d42ead..5674a4d 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -119,6 +119,51 @@
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()
diff --git a/testes/main.lua b/testes/main.lua
index c7bde0d..b9dcab1 100644
--- a/testes/main.lua
+++ b/testes/main.lua
@@ -254,15 +254,15 @@
-- 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 @@
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 @@
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))