lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



On 12/08/14 06:33 PM, Sean Conner wrote:
It was thus said that the Great Patrick Donnelly once stated:
On Aug 11, 2014 6:07 PM, "Jay Carlson" <nop@nop.com> wrote:
On Aug 11, 2014 1:48 PM, "Thiago L." <fakedme@gmail.com> wrote:

On 11/08/14 02:34 PM, Jay Carlson wrote:
Why do people like MediaWiki disable coroutines, anyway?
Because you can't interrupt running coroutines?

coroutine.resume(coroutine.create(function() while true do end end)) --
good luck interrupting me
OK, that's a good reason. (OTOH, if you were going to tear down the
runtime in response to an interrupt I'm not sure it matters.)

In this context, coroutine.resume is a fancy pcall. If they are using debug
hooks to "interrupt", then child threads inherit the hook from the parent:

http://www.lua.org/source/5.2/lstate.c.html#lua_newthread

If they mean signals, then that's just bad design with or without
coroutines. [Actually, signals and hooks are both terrible for interrupting
a user script. The only secure option is a separate process.]
   I think I'll have to agree here, after working on this today.

   Observe the following:

[spc]saltmine:~>lua
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
while true do end
^Cinterrupted!
stack traceback:
         stdin:1: in main chunk
         [C]: ?
coroutine.resume(coroutine.create(function() while true do end end))
^C^C
[spc]saltmine:~>

The basic Lua interpreter uses the Signal Handler SetHook Hack (TM) to gain
control when the user hits ^C [1].

static void lstop (lua_State *L, lua_Debug *ar) {
   (void)ar;  /* unused arg. */
   lua_sethook(L, NULL, 0, 0);
   luaL_error(L, "interrupted!");
}


static void laction (int i) {
   signal(i, SIG_DFL); /* if another SIGINT happens before lstop,
                               terminate process (default action) */
   lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT,1);
}

	...
	signal(SIGINT, laction);
	...

   So, when doing "while true do end" at the Lua prompt, we're running in the
main thread [2] of Lua, so globalL == L in lstop(), and thus, we get the
"interrupted!" message when we hit ^C.

   In the second case, where we do
"coroutine.resume(coroutine.create(function() while true do end end))", we
set the hook for globalL, but that's not the Lua state that is "current."
Each Lua state has its own hook, but that isn't explicitely stated in the
manual [3].  So the Lua interpreter hooks globalL, but we're not running the
thread in gloablL, but another thread. The coroutine-thread-L has no hook,
and thus, the hook routine is never called.  The first ^C is handled by
laction(), which sets the default signal action back to its default state in
case it can't handle it.  It takes a second ^C to terminate the program (and
this is what we see happening).

   Okay, that explains the behavior.  Is there any way to fix it?

   I don't think so.  There doesn't appear to be any way of getting from
gloablL to the coroutine-L fast.  Yes, one could sweep through
L->l_G->rootgc looking for LUA_TTHREAD objects, but there is no clear
indication which one is the "current" one, and I don't really relish the
idea of setting hooks on all possible LUA_TTHREADs I find, only to reset
them back.

   -spc

[1]	On Unix systems, this sents a SIGINT to the foreground process in
	the terminal.

[2]	Using terminology from the manual.

[3]	You have to read between the lines.  It sets the hook for a Lua
	state, using terminology like "is called when the interpreter calls
	a function" or "is called after the interpreter executes every count
	instructions."

	lua_newthread() returns a new Lua state, and in fact, the
	documentation states: "The new thread returned by this function
	shares with the original thread its global environment, but has an
	independent execution stack." Nothing about hooks being per
	lua_State.  You have to infer that from the documentation (or read
	the source).

-- mt = "main thread"
-- co = a coroutine
-- t[1] = pcall status
-- t[2] = coroutine.resume status (or error if t[1] is false)
-- etc
local mt = coroutine.running()
local co = coroutine.create(stuff)
debug.sethook(co, function(...)
    if debug.gethook(mt) then
        return debug.gethook(mt)()
    end
end)
local t = table.pack(pcall(coroutine.resume,co))
if t[1] and t[2] then
    return select(3,table.unpack(t))
elseif t[1] and not t[2] then
    return t[2], t[3]
elseif not t[1] then
    return t[1], t[2]
end