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.
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;
}
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;
}
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.
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.
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:
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.
You can start the thread by using lua_resume. The script can then exit by using yield().
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
possibly useless, but in the lua I use (luaplayer or PGELua), I exit with
os.exit()
or
pge.exit()
If you're using coroutines to start the threads, you could maybe use coroutine.yield() to stop it.
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.
session:destroy();
Use this single line code on that where you are want to destroy lua script.
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;
};
Related
I'm trying to call lua_yield inside a debug hook, and get this error in my output. I'm wanting to yield after a certain number of instructions have been processed and was hoping this was the way to do it.
I'm writing this using some Python ctypes bindings.
yielding
b'test.lua:1: attempt to yield across C-call boundary'
I assumed this should work since I'm using LuaJIT and it has a fully resumable VM.
#lua_Hook
def l_dbg_count(L: lua_State_p, ar: ctypes.POINTER(lua_Debug)):
if ar.contents.event == EventCode.HookCount:
print("yielding")
lua_yield(L, 0)
#main method
def main():
...
lua_sethook(L, l_dbg_count, DebugEventMask.Count, 1)
luaL_loadfile(L, b"test.lua")
ret = lua_pcall(L, 0, 0, 0)
while True:
if ret != LuaError.Ok and ret != LuaError.Yield:
print(lua_tostring(L, -1))
break
elif ret == LuaError.Yield:
print("resuming")
ret = lua_resume(L, None, 0)
lua_close(L)
I first must push a new thread using lua_newthread, then calling luaL_loadfile and instead of lua_pcall, calling lua_resume.
I rewrote this in C to check if there was possible stack unwinding issues from Lua to Python.
void l_dbg_count(lua_State *L, lua_Debug *ar) {
if(ar->event == LUA_HOOKCOUNT) {
printf("yielding\n");
lua_yield(L, 0);
}
}
...
int main(int argc, char **argv) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_sethook(L, l_dbg_count, LUA_MASKCOUNT, 5);
lua_State *L_t = lua_newthread(L);
luaL_loadfile(L_t, "test.lua");
int ret = lua_resume(L_t, 0);
while(true) {
if(ret != 0 && ret != LUA_YIELD) {
fprintf(stderr, "%s", lua_tostring(L_t, -1));
break;
} else if(ret == LUA_YIELD) {
printf("resuming\n");
ret = lua_resume(L_t, 0);
} else {
break;
}
}
lua_close(L);
return EXIT_SUCCESS;
}
This however does break the coroutine library from working it seems, so currently looking into a possible fix for that.
we are developing game server using lua.
the server is single threaded, we'll call lua from c++.
every c++ service will create a lua thread from a global lua state which is shared by all service.
the lua script executed by lua thread will call a c api which will make a rpc call to remote server.
then the lua thread is suspened, because it's c function never return.
when the rpc response get back, we'll continue the c code ,which will return to the lua script.
so, we will have multiple lua thread execute parallel on a same global lua state, but they will never run concurrently. and the suspend is not caused but lua yield function, but from the c side.
is it safe to do something like this?
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include "lua/lua.hpp"
static ucontext_t uctx_main, uctx_func1, uctx_func2;
lua_State* gLvm;
int gCallCnt = 0;
static int proc(lua_State *L) {
int iID = atoi(lua_tostring(L, -1));
printf("begin proc, %s\n", lua_tostring(L, -1));
if(iID == 1)
{
swapcontext(&uctx_func1, &uctx_main);
}
else
{
swapcontext(&uctx_func2, &uctx_main);
}
printf("end proc, %s\n", lua_tostring(L, -1));
return 0;
}
static void func1(void)
{
gCallCnt++;
printf("hello, func1\n");
lua_State*thread = lua_newthread (gLvm);
lua_getglobal(thread, "proc");
char szTmp[20];
sprintf(szTmp, "%d", gCallCnt);
lua_pushstring(thread, szTmp);
int iRet = lua_resume(thread, gLvm, 1);
printf("lua_resume return:%d\n", iRet);
}
static void func2(void)
{
gCallCnt++;
printf("hello, func2\n");
lua_State*thread = lua_newthread (gLvm);
lua_getglobal(thread, "proc");
char szTmp[20];
sprintf(szTmp, "%d", gCallCnt);
lua_pushstring(thread, szTmp);
int iRet = lua_resume(thread, gLvm, 1);
printf("lua_resume return:%d\n", iRet);
}
int main(int argc, char *argv[]){
int iRet = 0;
gLvm = luaL_newstate();
luaL_openlibs(gLvm);
lua_pushcfunction(gLvm, proc);
lua_setglobal(gLvm, "proc");
char func1_stack[16384];
char func2_stack[16384];
getcontext(&uctx_func1);
uctx_func1.uc_stack.ss_sp = func1_stack;
uctx_func1.uc_stack.ss_size = sizeof(func1_stack);
uctx_func1.uc_link = &uctx_main;
makecontext(&uctx_func1, func1, 0);
getcontext(&uctx_func2);
uctx_func2.uc_stack.ss_sp = func2_stack;
uctx_func2.uc_stack.ss_size = sizeof(func2_stack);
uctx_func2.uc_link = &uctx_main;
makecontext(&uctx_func2, func2, 0);
swapcontext(&uctx_main, &uctx_func1);
swapcontext(&uctx_main, &uctx_func2);
swapcontext(&uctx_main, &uctx_func1);
swapcontext(&uctx_main, &uctx_func2);
printf("hello, main\n");
return 0;
}
Setting: I'm using Lua from a C/C++ environment.
I have several lua files on disk. Those are read into memory and some more memory-only lua files become available during runtime. Think e.g. of an editor, with additional unsaved lua files.
So, I have a list<identifier, lua_file_content> in memory. Some of these files have require statements in them. When I try to load all these files to a lua instance (currently via lua_dostring) I get attempt to call global require (a nil value).
Is there a possibility to provide a require function, which replaces the old one and just uses the provided in memory files (those files are on the C side)?
Is there another way of allowing require in these files without having the required files on disk?
An example would be to load the lua stdlib from memory only without altering it. (This is actually my test case.)
Instead of replacing require, why not add a function to package.loaders? The code is nearly the same.
int my_loader(lua_State* state) {
// get the module name
const char* name = lua_tostring(state);
// find if you have such module loaded
if (mymodules.find(name) != mymodules.end())
{
luaL_loadbuffer(state, buffer, size, name);
// the chunk is now at the top of the stack
return 1;
}
// didn't find anything
return 0;
}
// When you load the lua state, insert this into package.loaders
http://www.lua.org/manual/5.1/manual.html#pdf-package.loaders
A pretty straightforward C++ function that would mimic require could be: (pseudocode)
int my_require(lua_State* state) {
// get the module name
const char* name = lua_tostring(state);
// find if you have such module loaded
if (mymodules.find(name) != mymodules.end())
luaL_loadbuffer(state, buffer, size, name);
// the chunk is now at the top of the stack
lua_call(state)
return 1;
}
Expose this function to Lua as require and you're good to go.
I'd also like to add that to completely mimic require's behaviour, you'd probably need to take care of package.loaded, to avoid the code to be loaded twice.
There is no package.loaders in lua 5.2
It called package.searchers now.
#include <stdio.h>
#include <string>
#include <lua.hpp>
std::string module_script;
int MyLoader(lua_State *L)
{
const char *name = luaL_checkstring(L, 1); // Module name
// std::string result = SearchScript(name); // Search your database.
std::string result = module_script; // Just for demo.
if( luaL_loadbuffer(L, result.c_str(), result.size(), name) )
{
printf("%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
return 1;
}
void SetLoader(lua_State* L)
{
lua_register(L, "my_loader", MyLoader);
std::string str;
// str += "table.insert(package.loaders, 2, my_loader) \n"; // Older than lua v5.2
str += "table.insert(package.searchers, 2, my_loader) \n";
luaL_dostring(L, str.c_str());
}
void SetModule()
{
std::string str;
str += "print([[It is add.lua]]) \n";
str += "return { func = function() print([[message from add.lua]]) end } \n";
module_script=str;
}
void LoadMainScript(lua_State* L)
{
std::string str;
str += "dev = require [[add]] \n";
str += "print([[It is main.lua]]) \n";
str += "dev.func() \n";
if ( luaL_loadbuffer(L, str.c_str(), str.size(), "main") )
{
printf("%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
}
int main()
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
SetModule(L); // Write down module in memory. Lua not load it yet.
SetLoader(L);
LoadMainScript(L);
lua_pcall(L,0,0,0);
lua_close(L);
return 0;
}
I load two strings with loadbuffer into one lua_state.
if( luaL_loadbuffer( L, str.c_str(), str.size(), "line") != 0 )
{
printf( "%s\n", lua_tostring ((lua_State *)L, -1));
}
lua_pcall(L, 0, 0, 0);
if( luaL_loadbuffer( L, str2.c_str(), str2.size(), "line2") != 0 )
{
printf( "%s\n", lua_tostring ((lua_State *)L, -1));
}
lua_pcall(L, 0, 0, 0);
For example:
function f ()
print( "Hello World!")
end
and
function g ()
f(
end
The forgotten ) in the second string throws an error:
[string "line2"]:9: unexpected Symbol
But 9 is the line number from string 1 plus string 2. The line number should be 3.
Is there a way to reset the line number counter before call to loadbuffer?
I guess this link describes your situation:
http://www.corsix.org/content/common-lua-pitfall-loading-code
You are loading two chunks of information, calling the chunks will put them consecutive into the global table. The lua_pcall(L, 0, 0, 0); is not calling your f() and g(), but is constructing your lua code sequential.
Your code could possibly be simplified to:
if (luaL_dostring(L, str.c_str()))
{
printf("%s\n", lua_tostring (L, -1));
}
if (luaL_dostring(L, str2.c_str()));
{
printf("%s\n", lua_tostring (L, -1));
}
which also protects against calling a chunk when it fails to load;
You are right Enigma, the code from str2 is appended consecutive. A breakpoint in
static void statement (LexState *ls) {
in lparser.cpp shows LexState.linenumber to be 5 and 7 for str, and 5, 7, 14 and 16 for str2.
So str is lexed and added to the VM twice.
I will find a different way to put a script made of multiple files into one VM.
Just if someone would need it too.
Add this function to lauxlib.h
LUALIB_API int (luaL_loadbuffers) (lua_State *L, size_t count, const char **buff, size_t *sz,
const char **name, const char *mode);
and to lauxlib.c
#include"lzio.h"
#include"ldo.h"
#include"ltable.h"
#include"lgc.h"
LUALIB_API int luaL_loadbuffers (lua_State *L, size_t count, const char **buff, size_t *sz,
const char **name, const char *mode)
{
ZIO z;
int status;
int i;
for( i=0; i<count; i++)
{
LoadS ls;
ls.s = buff[i];
ls.size = sz[i];
lua_lock(L);
luaZ_init(L, &z, getS, &ls);
status = luaD_protectedparser(L, &z, name[i], mode);
if (status == LUA_OK) { /* no errors? */
LClosure *f = clLvalue(L->top - 1); /* get newly created function */
if (f->nupvalues == 1) { /* does it have one upvalue? */
/* get global table from registry */
Table *reg = hvalue(&G(L)->l_registry);
const TValue *gt = luaH_getint(reg, LUA_RIDX_GLOBALS);
/* set global table as 1st upvalue of 'f' (may be LUA_ENV) */
setobj(L, f->upvals[0]->v, gt);
luaC_barrier(L, f->upvals[0], gt);
} // == 1
lua_pcall( L, 0, 0, 0);
}
lua_unlock(L);
if( status != LUA_OK )
break;
}
return status;
}
Every string/file gets its own line numnbering.
It is just a copy, almost, of lua_load in lapi.c. So easy to adjust in a new release of LUA.
Is this the correct way to sync threads without mutex.
This code should be running for a long time
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/memory_order.hpp>
#include <atomic>
std::atomic<long> x =0;
std::atomic<long> y =0;
boost::mutex m1;
// Thread increments
void Thread_Func()
{
for(;;)
{
// boost::mutex::scoped_lock lx(m1);
++x;
++y;
}
}
// Checker Thread
void Thread_Func_X()
{
for(;;)
{
// boost::mutex::scoped_lock lx(m1);
if(y > x)
{
// should never hit until int overflows
std::cout << y << "\\" << x << std::endl;
break;
}
}
}
//Test Application
int main(int argc, char* argv[])
{
boost::thread_group threads;
threads.create_thread(Thread_Func);
threads.create_thread(Thread_Func_X);
threads.join_all();
return 0;
}
Without knowing exactly what you're trying to do, it is hard to say it is the "correct" way. That's valid code, it's a bit janky though.
There is no guarantee that the "Checker" thread will ever see the condition y > x. It's theoretically possible that it will never break. In practice, it will trigger at some point but x might not be LONG_MIN and y LONG_MAX. In other words, it's not guaranteed to trigger just as the overflow happens.