Lua / Java / LuaJ - handling or interrupting endless loops and threads

I use LuaJ to run user-created Lua scripts in Java. However, running a Lua script that never returns causes the Java thread to freeze. It also makes the flow uninterrupted. I am running Lua script with:

JsePlatform.standardGlobals().loadFile("badscript.lua").call(); 

badscript.lua contains while true do end .

I would like to be able to automatically terminate scripts that are stuck in volatile loops, and also allow users to manually complete their Lua scripts while they are running. I read about debug.sethook and pcall , although I'm not sure how to use them properly for my purposes. I also heard that the sandbox is the best alternative, although this is a bit out of my interest.

This question can also be extended only for Java threads. I did not find the final information about interrupting Java threads stuck in while (true); .

The online demonstration of Lua was very , but it seems that the detection and termination of bad scripts is done in a CGI script, not Lua. Can I use Java to invoke a CGI script, which in turn calls a Lua script? I am not sure if this will allow users to manually complete their scripts. I have lost the link to the source code of Lua demo, but I have it at hand. This is the magic line:

 tee -a $LOG | (ulimit -t 1 ; $LUA demo.lua 2>&1 | head -c 8k) 

Can someone point me in the right direction?

Some sources:

+4
source share
4 answers

I struggled with the same problem, and after some deepening through the implementation of the debug library, I created a solution similar to David Lewis's suggestion, but did this by providing my own DebugLibrary:

 package org.luaj.vm2.lib; import org.luaj.vm2.LuaValue; import org.luaj.vm2.Varargs; public class CustomDebugLib extends DebugLib { public boolean interrupted = false; @Override public void onInstruction(int pc, Varargs v, int top) { if (interrupted) { throw new ScriptInterruptException(); } super.onInstruction(pc, v, top); } public static class ScriptInterruptException extends RuntimeException {} } 

Just execute your script from within a new thread and set interrupted to true to stop execution. The exception will be encapsulated as the cause of LuaError on throw.

+5
source

There are problems, but it is very important to answer your question.

The following proof of concept demonstrates the basic level of sandboxing and throttling of arbitrary user code. It starts ~ 250 instructions of a poorly created "user input", and then discards the coroutine. You can use a mechanism such as the one in this answer to request Java and conditionally get a hook inside the function, instead of yielding each time.

SandboxTest.java:

 public static void main(String[] args) { Globals globals = JsePlatform.debugGlobals(); LuaValue chunk = globals.loadfile("res/test.lua"); chunk.call(); } 

Res / test.lua:

 function sandbox(fn) -- read script and set the environment f = loadfile(fn, "t") debug.setupvalue(f, 1, {print = print}) -- create a coroutine and have it yield every 50 instructions local co = coroutine.create(f) debug.sethook(co, coroutine.yield, "", 50) -- demonstrate stepped execution, 5 'ticks' for i = 1, 5 do print("tick") coroutine.resume(co) end end sandbox("res/badfile.lua") 

Res / badfile.lua:

 while 1 do print("", "badfile") end 

Unfortunately, while the control flow works as intended, something like the β€œthrown” coroutines should receive garbage collection does not work correctly. The corresponding LuaThread in Java hangs forever in a wait loop, supporting the process. Details here:

How can I discard the LuaJ coroutine LuaThread?

+2
source

I have never used Luaj before, but could you put your one line

 JsePlatform.standardGlobals().loadFile("badscript.lua").call(); 

To a new thread that you can then terminate from the main thread?

This will require you to make some kind of thread (class) of the supervisor and pass all the running scripts to it in order to control and eventually stop working if they do not end on their own.

0
source

EDIT: I have not found a way to safely terminate LuaJ threads without modifying LuaJ itself. The following was what I came up with, although it does not work with LuaJ. However, it can be easily modified to do its job in pure Lua. I can switch to Python bindings for Java, as LuaJ threads are so problematic.

--- I came up with the following, but it does not work with LuaJ ---

Here is a possible solution. I register a hook with debug.sethook that fires on "count" events (these events occur even in while true do end ). I also pass in my own Java ScriptState object that I created that contains a boolean flag indicating whether the script should complete or not. The Java object is requested in a Lua hook , which throws an error to close the script if the flag is set (change: throwing an error does not actually end the script) . The completion flag can also be set from the Lua script.

If you want to automatically end infinite infinite loops, it is simple enough to implement a timer system that records the last time the call was made in ScriptState and then automatically terminates the script if enough time passes without calling the API (edit: this only works in in case the thread may be interrupted) . If you want to kill endless loops but not interrupt some locking operations, you can configure the ScriptState to include other status information that allows you to temporarily suspend auto-completion, etc.

Here is my interpreter.lua , which can be used to call another script and interrupt it if / when necessary. It calls Java methods, so it will not work without LuaJ (or some other Lua-Java library) unless it changes (edit: again, it can easily be modified to work in pure Lua) .

 function hook_line(e) if jthread:getDone() then -- I saw someone else use error(), but an infinite loop still seems to evade it. -- os.exit() seems to take care of it well. os.exit() end end function inithook() -- the hook will run every 100 million instructions. -- the time it takes for 100 million instructions to occur -- is based on computer speed and the calling environment debug.sethook(hook_line, "", 1e8) local ret = dofile(jLuaScript) debug.sethook() return ret end args = { ... } if jthread == nil then error("jthread object is nil. Please set it in the Java environment.",2) elseif jLuaScript == nil then error("jLuaScript not set. Please set it in the Java environment.",2) else local x,y = xpcall(inithook, debug.traceback) end 

Here's the ScriptState class that saves the flag and main() to demonstrate:

 public class ScriptState { private AtomicBoolean isDone = new AtomicBoolean(true); public boolean getDone() { return isDone.get(); } public void setDone(boolean v) { isDone.set(v); } public static void main(String[] args) { Thread t = new Thread() { public void run() { System.out.println("J: Lua script started."); ScriptState s = new ScriptState(); Globals g = JsePlatform.debugGlobals(); g.set("jLuaScript", "res/main.lua"); g.set("jthread", CoerceJavaToLua.coerce(s)); try { g.loadFile("res/_interpreter.lua").call(); } catch (Exception e) { System.err.println("There was a Lua error!"); e.printStackTrace(); } } }; t.start(); try { t.join(); } catch (Exception e) { System.err.println("Error waiting for thread"); } System.out.println("J: End main"); } } 

res/main.lua contains the target Lua code to run. Use environment variables or parameters to pass additional information to the script, as usual. Remember to use JsePlatform.debugGlobals() instead of JsePlatform.standardGlobals() if you want to use the debug library in Lua.

EDIT: I just noticed that os.exit() not only terminates the Lua script, but also calls the process. This seems to be the equivalent of System.exit() . error() will throw an error, but will not end the Lua script. I am trying to find a solution for this now.

0
source

Source: https://habr.com/ru/post/1489982/


All Articles