如何终止Rhino脚本?Java应用中Rhino用户脚本超时终止方案咨询
Absolutely, you can enforce a timeout for Rhino scripts to stop runaway execution (like infinite loops) by running the script in a separate thread and interrupting it once your time limit is hit. Here’s a practical, tested approach tailored to your use case:
Core Concept
Rhino executes scripts in the same thread that triggers Context.evaluateString(), so to add a timeout, we need to offload script execution to a background thread. Using Java’s ExecutorService and Future allows us to wait for the script to finish, and cancel the thread if it exceeds your 30-second limit.
Complete Implementation Code
Here’s a working example that wraps Rhino execution with a 30-second timeout:
import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; import java.util.concurrent.*; public class RhinoTimeoutManager { // Use a single-thread executor for script execution (adjust pool size if needed) private static final ExecutorService scriptExecutor = Executors.newSingleThreadExecutor(); public static Object runScriptWithTimeout(String userScript, long timeoutSeconds) throws Exception { // Submit script execution as a callable task Future<Object> scriptTask = scriptExecutor.submit(() -> { Context rhinoCtx = Context.enter(); try { // Force interpreter mode (critical for reliable interrupts) rhinoCtx.setOptimizationLevel(-1); Scriptable globalScope = rhinoCtx.initStandardObjects(); // Execute the user's script return rhinoCtx.evaluateString(globalScope, userScript, "UserSubmittedScript", 1, null); } finally { // Always clean up Rhino's thread-local context Context.exit(); } }); try { // Wait for completion or trigger timeout return scriptTask.get(timeoutSeconds, TimeUnit.SECONDS); } catch (TimeoutException e) { // Cancel the task and interrupt the running thread scriptTask.cancel(true); throw new RuntimeException("Script timed out after " + timeoutSeconds + " seconds"); } catch (ExecutionException e) { // Re-throw any errors from the script itself throw new RuntimeException("Script failed to execute", e.getCause()); } } public static void main(String[] args) { // Test with an infinite loop script String badScript = "while(true) { /* Infinite loop */ }"; try { runScriptWithTimeout(badScript, 30); } catch (Exception e) { System.err.println(e.getMessage()); // Should print timeout message } finally { scriptExecutor.shutdown(); } } }
Key Details to Note
- Interpreter Mode: Setting
optimizationLevel(-1)disables Rhino’s bytecode compilation. Compiled scripts don’t check for thread interrupts as often, so interpreter mode ensures the script can be stopped quickly when timed out. - Thread Interruption: Calling
future.cancel(true)sends an interrupt signal to the script’s thread. Rhino’s interpreter checks for interrupts during loop iterations and other execution points, so the script will terminate almost immediately. - Resource Cleanup: The
finallyblock ensuresContext.exit()is called every time, preventing leaks of Rhino’s thread-local resources. - Edge Cases: If your script calls native Java methods that don’t respond to interrupts (e.g., blocking I/O that ignores
Thread.interrupted()), the timeout might not work instantly. For pure JavaScript code, though, this method is fully reliable.
Alternative: Context Factory with Instruction Monitoring
If you prefer not to use threads, you can use a custom ContextFactory to check for timeouts based on script instruction counts:
import org.mozilla.javascript.Context; import org.mozilla.javascript.ContextFactory; public class TimeoutAwareContextFactory extends ContextFactory { private final long timeoutMillis; private long executionStartTime; public TimeoutAwareContextFactory(long timeoutMillis) { this.timeoutMillis = timeoutMillis; } @Override protected void observeInstructionCount(Context cx, int instructionCount) { super.observeInstructionCount(cx, instructionCount); if (System.currentTimeMillis() - executionStartTime > timeoutMillis) { throw new RuntimeException("Script exceeded timeout limit"); } } @Override protected Context makeContext() { executionStartTime = System.currentTimeMillis(); Context cx = super.makeContext(); // Check timeout every 1000 instructions (adjust for overhead vs responsiveness) cx.setInstructionObserverThreshold(1000); return cx; } }
To use this factory:
// Set the custom factory as global before executing scripts ContextFactory.initGlobal(new TimeoutAwareContextFactory(30000)); // 30 seconds // Proceed with normal Rhino execution... Context ctx = Context.enter(); try { Scriptable scope = ctx.initStandardObjects(); ctx.evaluateString(scope, userScript, "UserScript", 1, null); } finally { Context.exit(); }
This method works without threads but relies on Rhino’s instruction counter to check timeouts. It’s great for pure JS scripts but less reliable if the script calls long-running native code.
内容的提问来源于stack exchange,提问作者smoyano

