API сценариев Java - как остановить оценку - PullRequest
6 голосов
/ 21 октября 2009

Я написал сервлет, который получает код сценария Java, обрабатывает его и возвращает ответ. для этого я использовал API сценариев Java

в приведенном ниже коде, если script = "print ('Hello, World')"; код закончится правильно распечатать "привет мир". но если script = "while (true);" скрипт будет зацикливаться бесконечно.

import javax.script.*;
public class EvalScript {
    public static void main(String[] args) throws Exception {
        // create a script engine manager
        ScriptEngineManager factory = new ScriptEngineManager();
        // create a JavaScript engine
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        // evaluate JavaScript code from String
        engine.eval(script);
    }
}

Мой вопрос: как мне убить процесс eval, если он занимает слишком много времени (скажем, 15 секунд)?

спасибо

Ответы [ 6 ]

3 голосов
/ 21 октября 2009

Вот некоторый код, показывающий реализацию Future и Thread.stop (). Это интересная проблема, и она указывает на необходимость использования хука в ScriptEngine для возможности остановки любого скрипта, который он запускает по любой причине. Интересно, не нарушит ли это допущения в большинстве реализаций, поскольку предполагается, что eval() будет выполняться в однопоточной (блокирующей) среде?

В любом случае, результаты выполнения кода ниже:

// exec with Thread.stop()
$ java ExecJavascript 
Java: Starting thread...
JS: Before infinite loop...
Java: ...thread started
Java: Thread alive after timeout, stopping...
Java: ...thread stopped
(program exits)

// exec with Future.cancel()
$ java ExecJavascript 1
Java: Submitting script eval to thread pool...
Java: ...submitted.
JS: Before infinite loop...
Java: Timeout! trying to future.cancel()...
Java: ...future.cancel() executed
(program hangs)

Вот полная программа:

import java.util.concurrent.*;
import javax.script.*;

public class ExecJavascript
{
private static final int TIMEOUT_SEC = 5;
public static void main( final String ... args ) throws Exception 
{
    final ScriptEngine engine = new ScriptEngineManager()
        .getEngineByName("JavaScript");
    final String script = 
        "var out = java.lang.System.out;\n" +
        "out.println( 'JS: Before infinite loop...' );\n" +
        "while( true ) {}\n" +
        "out.println( 'JS: After infinite loop...' );\n";
    if ( args.length == 0 ) {
        execWithThread( engine, script );
    }
    else {
        execWithFuture( engine, script );
    }
}

private static void execWithThread( 
    final ScriptEngine engine, final String script )
{
    final Runnable r = new Runnable() {
        public void run() {
            try {
                engine.eval( script );
            }
            catch ( ScriptException e ) {
                System.out.println( 
                    "Java: Caught exception from eval(): " + e.getMessage() );
            }
        }
    };
    System.out.println( "Java: Starting thread..." );
    final Thread t = new Thread( r );
    t.start();
    System.out.println( "Java: ...thread started" );
    try {
        Thread.currentThread().sleep( TIMEOUT_SEC * 1000 );
        if ( t.isAlive() ) {
            System.out.println( "Java: Thread alive after timeout, stopping..." );
            t.stop();
            System.out.println( "Java: ...thread stopped" );
        }
        else {
            System.out.println( "Java: Thread not alive after timeout." );
        }
    }
    catch ( InterruptedException e ) {
        System.out.println( "Interrupted while waiting for timeout to elapse." );
    }
}

private static void execWithFuture( final ScriptEngine engine, final String script )
    throws Exception
{
    final Callable<Object> c = new Callable<Object>() {
        public Object call() throws Exception {
            return engine.eval( script );
        }
    };
    System.out.println( "Java: Submitting script eval to thread pool..." );
    final Future<Object> f = Executors.newCachedThreadPool().submit( c );
    System.out.println( "Java: ...submitted." );
    try {
        final Object result = f.get( TIMEOUT_SEC, TimeUnit.SECONDS );
    }
    catch ( InterruptedException e ) {
        System.out.println( "Java: Interrupted while waiting for script..." );
    }
    catch ( ExecutionException e ) {
        System.out.println( "Java: Script threw exception: " + e.getMessage() );
    }
    catch ( TimeoutException e ) {
        System.out.println( "Java: Timeout! trying to future.cancel()..." );
        f.cancel( true );
        System.out.println( "Java: ...future.cancel() executed" );
    }
} 
}
3 голосов
/ 21 октября 2009

Запустите оценку в отдельном потоке и прервите его через 15 секунд, используя Thread.interrupt (). Это остановит eval и создаст исключение InterruptedException, которое вы можете перехватить и вернуть статус ошибки.

Лучшим решением было бы иметь какой-то асинхронный интерфейс для механизма сценариев, но, насколько я мог видеть, этого не существует.

EDIT:

Как указывал sfussenegger, прерывание не работает с обработчиком сценариев, поскольку оно никогда не спит или не переходит в какое-либо состояние ожидания для прерывания. Я не мог бы найти какой-либо периодический обратный вызов в объектах ScriptContext или Bindings, который можно было бы использовать в качестве ловушки для проверки прерываний. Однако есть один метод, который работает: Thread.stop (). Это устарело и небезопасно по ряду причин, но для полноты я опубликую здесь свой тестовый код вместе с реализацией Криса Уинтерса для сравнения. Время ожидания версии Криса истечет, но фоновый поток будет запущен, interrupt () ничего не сделает, а stop () убьет поток и возобновит управление основным потоком:

import javax.script.*;
import java.util.concurrent.*;

class ScriptRunner implements Runnable {

    private String script;
    public ScriptRunner(String script) {
            this.script = script;
    }

    public ScriptRunner() {
            this("while(true);");
    }

    public void run() {
            try {
            // create a script engine manager
            ScriptEngineManager factory = new ScriptEngineManager();
            // create a JavaScript engine
            ScriptEngine engine = factory.getEngineByName("JavaScript");
            // evaluate JavaScript code from String
            System.out.println("running script :'" + script + "'");
            engine.eval(script);
            System.out.println("stopped running script");
            } catch(ScriptException se) {
                    System.out.println("caught exception");
                    throw new RuntimeException(se);
            }
            System.out.println("exiting run");
    }
}

public class Inter {

    public void run() {
            try {
             Executors.newCachedThreadPool().submit(new ScriptRunner()).get(15, TimeUnit.SECONDS);
            } catch(Exception e) {
                    throw new RuntimeException(e);
            }
    }

    public void run2() {
            try {
            Thread t = new Thread(new ScriptRunner());
            t.start();
            Thread.sleep(1000);
            System.out.println("interrupting");
            t.interrupt();
            Thread.sleep(5000);
            System.out.println("stopping");
            t.stop();
            } catch(InterruptedException ie) {
                    throw new RuntimeException(ie);
            }
    }

    public static void main(String[] args) {
            new Inter().run();
    }
}
2 голосов
/ 21 октября 2009

Если вы не хотите использовать Thread.stop () (и вам это действительно нужно), похоже, нет способа удовлетворить ваши требования с помощью API javax.script.

Если вы используете механизм Rhino напрямую и фактическая производительность не слишком важна, вы можете реализовать ловушку в Context.observeInstructionCount для прерывания или преждевременного прекращения выполнения скрипта. Хук вызывается для каждой исполняемой инструкции JavaScript после того, как вы достигнете порога (количества команд), установленного с помощью setInstructionObserverThreshold. Вы должны сами измерить время выполнения, поскольку вам предоставляется только количество выполненных инструкций, и это может оказать существенное влияние на производительность. Я не уверен, но ловушка также может быть вызвана, только если механизм сценария работает в интерпретируемом режиме, а не если код JavaScript скомпилирован.

0 голосов
/ 20 января 2016

Сценарии Nashorn компилируются в «файлы» .class и загружаются на лету. Таким образом, оценка сценария аналогична загрузке скомпилированного Java .class и его запуску. Если вы не запрограммировали явно для прерывания, вы не можете остановить оценку скрипта. Не существует "интерпретатора сценариев", который "опрашивает" статус прерывания. Вы должны явно вызвать Thread.sleep или другой API Java, который будет прерван из другого потока.

0 голосов
/ 30 мая 2015

Я знаю, что это старый поток, но у меня есть более прямое решение для остановки JavaScript eval: вызов функции "exit", которую обеспечивает Nashorn.

Внутри класса, который я использую для запуска механизма сценариев, я включаю:

private Invocable invocable_ = null;
private final ExecutorService pool_ = Executors.newFixedThreadPool(1);  

public boolean runScript(String fileName)
    {
    pool_.submit(new Callable<Boolean>() 
        {       
        ScriptEngine engine 
            = new ScriptEngineManager().getEngineByName("nashorn");
        public Boolean call() throws Exception
            {
            try
                {
                invocable_ = (Invocable)engine;
                engine.eval(
                        new InputStreamReader(
                                new FileInputStream(fileName), 
                                Charset.forName("UTF-8")) );
                return true;
                }
            catch (ScriptException ex)
                {
                ...
                return false;
                } 
            catch (FileNotFoundException ex)
                {
                ...
                return false;
                }                   
            }
        });

    return true;
    }

public void
shutdownNow()
    {

    try
        {
        invocable_.invokeFunction("exit");
        } 
    catch (ScriptException ex)
        {
        ...
        } 
    catch (NoSuchMethodException ex)
        {
        ...
        }

    pool_.shutdownNow();
    invocable_ = null;
    }

Теперь звоните:

myAwesomeClass.shutdownNow();

Сценарий немедленно остановится.

0 голосов
/ 30 марта 2013

Context.observeInstructionCount вызывается только в интерпретируемом режиме, так что это значительное снижение производительности. Я очень надеюсь, что команда Rhino придумает лучший способ.

...