27

How do you end a long running Lua script?

I have two threads, one runs the main program and the other controls a user supplied Lua script. I need to kill the thread that's running Lua, but first I need the script to exit.

Is there a way to force a script to exit?

I have read that the suggested approach is to return a Lua exception. However, it's not garanteed that the user's script will ever call an api function ( it could be in a tight busy loop). Further, the user could prevent errors from causing his script to exit by using a pcall.

JasonFruit
  • 7,764
  • 5
  • 46
  • 61
deft_code
  • 57,255
  • 29
  • 141
  • 224

10 Answers10

19

You could use setjmp and longjump, just like the Lua library does internally. That will get you out of pcalls and stuff just fine without need to continuously error, preventing the script from attempting to handle your bogus errors and still getting you out of execution. (I have no idea how well this plays with threads though.)

#include <stdio.h>
#include <setjmp.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

jmp_buf place;

void hook(lua_State* L, lua_Debug *ar)
{
    static int countdown = 10;
    if (countdown > 0)
    {
        --countdown;
        printf("countdown: %d!\n", countdown);
    }
    else
    {
        longjmp(place, 1);
    }
}

int main(int argc, const char *argv[])
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    lua_sethook(L, hook, LUA_MASKCOUNT, 100);

    if (setjmp(place) == 0)
        luaL_dostring(L, "function test() pcall(test) print 'recursing' end pcall(test)");

    lua_close(L);
    printf("Done!");
    return 0;
}
Alex
  • 14,973
  • 13
  • 59
  • 94
  • 1
    This is the solution I used. I'm still a little worried that Lua might be leaking something, but It's good enough for now. – deft_code Aug 22 '11 at 14:09
  • You might wanna take look at https://github.com/amilamad/preemptive-task-scheduler-for-lua project. its preemptive scheduler for lua. It uses a lua_yeild function inside the hook. So you can suspend your lua thread. It also uses longjmp inside but its is much safer. – amilamad Jan 17 '18 at 01:15
  • Correct me if I'm wrong, but the lua_sethook only works for Lua instructions. If you register a C function to be called from the Lua side, the hook won't be called during the function. – pandaman1234 Mar 21 '18 at 14:07
  • Its been a long time since I wrote this answer, but I'm almost certain I tested the solution before posting it. Give it a try and see if it doesn't work. – Alex Mar 22 '18 at 05:48
8

You could set a variable somewhere in your program and call it something like forceQuitLuaScript. Then, you use a hook, described here to run every n instructions. After n instructions, it'll run your hook which just checks if forceQuitLuaScript is set, and if it is do any clean up you need to do and kill the thread.

Edit: Here's a cheap example of how it could work, only this is single threaded. This is just to illustrate how you might handle pcall and such:

#include <stdlib.h>
#include "lauxlib.h"

void hook(lua_State* L, lua_Debug *ar)
{
    static int countdown = 10;
    if (countdown > 0)
    {
        --countdown;
        printf("countdown: %d!\n", countdown);
    }
    else
    {
        // From now on, as soon as a line is executed, error
        // keep erroring until you're script reaches the top
        lua_sethook(L, hook, LUA_MASKLINE, 0); 
        luaL_error(L, "");
    }
}

int main(int argc, const char *argv[])
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    lua_sethook(L, hook, LUA_MASKCOUNT, 100);
    // Infinitely recurse into pcalls
    luaL_dostring(L, "function test() pcall(test) print 'recursing' end pcall(test)");
    lua_close(L);
    printf("Done!");
    return 0;
}
Alex
  • 14,973
  • 13
  • 59
  • 94
  • I think the part that I was missing was to have the hook function continually return an error once I decide the script needs to die. – deft_code Aug 16 '11 at 18:30
  • 1
    Actually, I think have a better solution. I'll post it in a different answer so this one can stay up. – Alex Aug 16 '11 at 19:06
2

The way to end a script is to raise an error by calling error. However, if the user has called the script via pcall then this error will be caught.

lhf
  • 70,581
  • 9
  • 108
  • 149
  • I would like to provide the user's script with the `pcall` function, so that they can use exceptions internally. Is there a work around? An uncatchable error maybe? – deft_code Aug 15 '11 at 18:57
  • 3
    @deft_code: there are no uncatchable errors in Lua if you use `pcall`. However, you can redefine `pcall` to catch a particular error value that you use to exit scripts and pass anything along otherwise. – lhf Aug 15 '11 at 23:52
  • @lhf: That's a very nice suggestion, to redefine pcall()! – Niccolo M. Sep 01 '13 at 05:58
2

It seems like you could terminate the thread externally (from your main thread) since the lua script is user supplied and you can't signal it to exit.

If that isn't an option, you could try the debug API. You could use lua_sethook to enable you to regain control assuming you have a way to gracefully terminate your thread in the hook.

sylvanaar
  • 8,096
  • 37
  • 59
  • I cannot gracefully exit my thread unless the script exits. In particular, I need the the thread to unwind its stack after the `lua_pcall` returns. See [C++0x thread interruption](http://stackoverflow.com/questions/2790346/c0x-thread-interruption) for why I can't just kill the thread. – deft_code Aug 02 '11 at 21:47
2

I haven't found a way to cleanly kill a thread that is executing a long running lua script without relying on some intervention from the script itself. Here are some approaches I have taken in the past:

  1. If the script is long running it is most likely in some loop. The script can check the value of some global variable on each iteration. By setting this variable from outside of the script you can then terminate the thread.
  2. You can start the thread by using lua_resume. The script can then exit by using yield().
  3. You could provide your own implementation of pcall that checks for a specific type of error. The script could then call error() with a custom error type that your version of pcall could watch for:

    function()
        local there_is_an_error = do_something()
        if (there_is_an_error) then
            error({code = 900, msg = "Custom error"})
        end
    end
    
Mike M.
  • 543
  • 4
  • 13
2

possibly useless, but in the lua I use (luaplayer or PGELua), I exit with

os.exit() 

or

pge.exit()
Richard Sparrow
  • 101
  • 1
  • 12
1

You might wanna take look at https://github.com/amilamad/preemptive-task-scheduler-for-lua project. its preemptive scheduler for lua. It uses a lua_yeild function inside the hook. So you can suspend your lua thread. It also uses longjmp inside but its is much safer.

amilamad
  • 470
  • 6
  • 9
1

If you're using coroutines to start the threads, you could maybe use coroutine.yield() to stop it.

SDuke
  • 437
  • 2
  • 4
  • 15
  • What if the user's script is in a coroutine when I try to yield? I think the users coroutine would return rather than the global script in that case. – deft_code Aug 15 '11 at 18:59
0

session:destroy();

Use this single line code on that where you are want to destroy lua script.

0
lua_KFunction cont(lua_State* L);
int my_yield_with_res(lua_State* L, int res) {
    cout << " my_yield_with_res \n" << endl;
    return lua_yieldk(L, 0, lua_yield(L, res), cont(L));/* int lua_yieldk(lua_State * L, int res, lua_KContext ctx, lua_KFunction k);
    Приостанавливает выполнение сопрограммы(поток). Когда функция C вызывает lua_yieldk, работающая
    сопрограмма приостанавливает свое выполнение и вызывает lua_resume, которая начинает возврат данной сопрограммы.
    Параметр res - это число значений из стека, которые будут переданы в качестве результатов в lua_resume.
    Когда сопрограмма снова возобновит выполнение, Lua вызовет заданную функцию продолжения k для продолжения выполнения
    приостановленной C функции(смотрите §4.7). */
};
int hookFunc(lua_State* L, lua_Debug* ar) {
    cout << " hookFunc \n" << endl;
    return my_yield_with_res(L, 0);// хук./
};

lua_KFunction cont(lua_State* L) {// функция продолжения.
    cout << " hooh off \n" << endl;
    lua_sethook(L, (lua_Hook)hookFunc, LUA_MASKCOUNT, 0);// отключить хук foo.
    return 0;
};

struct Func_resume {
    Func_resume(lua_State* L, const char* funcrun, unsigned int Args) : m_L(L), m_funcrun(funcrun), m_Args(Args) {}
    //имена функций, кол-во агрументов.
private:
    void func_block(lua_State* L, const char* functionName, unsigned int Count, unsigned int m_Args) {
        lua_sethook(m_L, (lua_Hook)hookFunc, LUA_MASKCOUNT, Count); //вызов функции с заданной паузой.
        if (m_Args == 0) {
            lua_getglobal(L, functionName);// получить имя функции.
            lua_resume(L, L, m_Args);
        }
        if (m_Args != 0) {
            int size = m_Args + 1;
            lua_getglobal(L, functionName);
            for (int i = 1; i < size; i++) {
                lua_pushvalue(L, i);
            }
            lua_resume(L, L, m_Args);
        }
    };
public:
    void Update(float dt) {
        unsigned int Count = dt * 100.0;// Время работы потока.
        func_block(m_L, m_funcrun, Count, m_Args);
    };
    ~Func_resume() {}
private:
    lua_State* m_L;
    const char* m_funcrun; // имя функции.
    unsigned int m_Count;// число итерации.
    unsigned int m_Args;
};

const char* LUA = R"(
function main(y) 
  --print(" func main arg, a = ".. a.." y = ".. y)      
for i = 1, y do
  print(" func main count = ".. i)      
  end
end
)";
int main(int argc, char* argv[]) {
    lua_State* L = luaL_newstate();/*Функция создает новое Lua состояние. */
    luaL_openlibs(L);
    luaL_dostring(L, LUA);
    //..pushlua(L, 12);
    pushlua(L, 32);
    //do {
        Func_resume func_resume(L, "main", 2);
        func_resume.Update(1.7);
        lua_close(L);
//  } while (LUA_OK != lua_status(L)); // Пока  поток не завершен.


    return 0;
};