javascriptjavainfinite-loopgraalvmgraaljs

How to exit infinite JS execution loop when reading/loading Javascript in Java using GraalVM?


I found sandbox options as a way to set sandbox.MaxCPUTime in the graalVM documentation, to limit how long the thread runs - https://www.graalvm.org/reference-manual/embed-languages/

I've tried the following code -

                try (Context context = Context.newBuilder("js")
                        .allowExperimentalOptions(true)
                        .option("sandbox.MaxCPUTime", "10s")
                        .option("sandbox.MaxCPUTimeCheckInterval", "5ms")
                        .build())
                {
                    try {
                        context.eval("js", "while(true);");
                        assert false;
                    } catch (PolyglotException e) {
                        // Triggered after 500ms;
                        // Context is closed and can no longer be used
                        // Error message: Maximum CPU time limit of 500ms exceeded.
                        assert e.isCancelled();
                        assert e.isResourceExhausted();
                    }
                    context.close(true);
                }

This has been failing for me with the error -

java.lang.IllegalArgumentException: Could not find option with name sandbox.MaxCPUTime.

Is there a better way to achieve this or a way I can make these sandbox options work?


Solution

  • You may want to use a more generic solution, that could potentially work with other scripting engines (e.g. rhino or nashorn), regardless of the built-in features:

    final ExecutorService executor = Executors.newSingleThreadExecutor();
    
    final Context context = Context.newBuilder("js").build();
    final Future<Object> futureResult = executor.submit(() -> context.eval("js", "while(true);"));
    
    try {
        final Object result = futureResult.get(10, TimeUnit.SECONDS);
        System.out.println("Script evaluated within 10 seconds, result: " + result);
    } catch (TimeoutException e) {
        context.interrupt(Duration.ZERO);
        System.out.println("Script not evaluated within 10 seconds, interrupted.");
    }
    
    System.out.println("Done.");
    

    Other advantage of this solution is that it allows you to use a thread-pool, hence giving you more control over how concurrent scripts are executed (e.g. you can limit the number of scripts being executed at the same time to one, or to the number of available CPU-cores, etc.).

    Please note though, that the time limit specified in the example is the time that elapsed since the submission of the task, not the time that was actually spent on the execution of the given script.