2009-10-21 8 views
6

Napisałem serwlet, który odbiera kod skryptu Java i przetwarza go i zwraca odpowiedź. w tym celu użyłem kodu JavaScript API w języku JavaInterfejs API JavaScript JavaScript - jak zatrzymać ewaluację

w poniższym kodzie, jeśli script = "print (" Hello, World ")"; kod zakończy się prawidłowo i wydrukuj "Witaj świecie". , ale jeśli script = "while (true);" skrypt będzie bez końca pętla.

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); 
    } 
} 

moje pytanie brzmi: jak zabić proces eval na wypadek, gdyby trwało to zbyt długo (powiedzmy 15 sekund)?

dzięki

Odpowiedz

3

przeprowadzeniu oceny w oddzielnym wątku i przerwać ją po 15 s przy użyciu Thread.interrupt(). Spowoduje to zatrzymanie eval i wygeneruje wyjątek InterruptedException, który można przechwycić i zwrócić status awarii.

Lepszym rozwiązaniem byłoby posiadanie jakiegoś asynchronicznego interfejsu do silnika skryptów, ale o ile mogłem zobaczyć, to nie istnieje.

EDIT:

Jak sfussenegger zauważył, przerywając nie działa z silnikiem skryptowym, ponieważ nigdy nie śpi albo wchodzi każde państwo czekać, aby przerwał. Niether mógłbym znaleźć jakieś okresowe wywołania zwrotne w obiektach ScriptContext lub Bindings, które mogłyby być używane jako haczyk do sprawdzania przerw. Istnieje jedna metoda, która działa: Thread.stop(). Z wielu powodów jest to przestarzałe iz natury niebezpieczne, ale dla kompletności opublikuję tutaj mój kod testowy wraz z implementacją Chrisa Wintersa dla porównania. wersja Chrisa będzie limitu czasu, ale pozostawić uruchomiony wątek tła, przerwanie() nie robi nic i stop() zabija wątek i wznawia sterowanie do głównego wątku:

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

-1 Jeśli bieżnik nie czeka, nie będzie wyjątku. Dlatego nie będzie działać z nieskończoną pętlą. Kod musiałby sprawdzić 'Thread.currentThread(). Interrupted()' aby twoje sugestie działały. – sfussenegger

+0

Argh, tęskniłem za tym. Pozostaje tylko przestarzałe zatrzymanie(), wierzę? –

+0

zmienił mój głos – sfussenegger

3

Oto niektóre kodu wykazujące przyszłego wdrożenia i wątek .stop() jeden. Jest to interesujący problem i wskazuje na potrzebę przechwycenia skryptu ScriptEngine, aby móc zatrzymać dowolny skrypt uruchamiany z dowolnego powodu. Zastanawiam się, czy złamałoby to założenia większości implementacji, ponieważ zakładają, że eval() zostanie wykonane w środowisku jednowątkowego (blokującego)?

Zresztą wyniki wykonując poniższy kod:

// 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) 

Oto pełny program:

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"); 
    } 
} 
} 
+2

Powoduje przywrócenie kontroli po 15 sekundach z wyjątkiem przekroczenia limitu czasu, ale pozostawia wątek w tle, więc pozostaje pytanie, jak je przerwać. –

+0

Duh! Dzięki za komentarz, dodałem Przyszłość anulowania. –

+1

Witam, dziękuję za eleganckie rozwiązanie, ale nawet po usunięciu Przyszłości wątek w tle nadal zużywa zasoby procesora. , więc pytanie pozostaje nadal .. – special0ne

2

Jeśli nie chcą używać Thread.stop() (i naprawdę powinno być), wydaje się, że nie ma sposobu, aby osiągnąć swoje wymagania za pomocą interfejsu API javax.script.

Jeśli użyjesz silnika Rhino bezpośrednio, a rzeczywista wydajność nie jest zbyt ważna, możesz zaimplementować przechwycenie w Context.observeInstructionCount, aby przerwać lub przedwcześnie zakończyć wykonywanie skryptu. Haczyk jest wywoływany dla każdej wykonanej instrukcji JavaScript po osiągnięciu progu (liczba instrukcji) ustawionego za pomocą metody setInstructionObserverThreshold. Musisz samemu zmierzyć czas wykonania, ponieważ dostarczana jest tylko liczba wykonywanych instrukcji, co może mieć istotny wpływ na wydajność. Nie jestem pewien, ale hak można również wywołać tylko wtedy, gdy silnik skryptowy działa w trybie interpretacji, a nie, jeśli kod JavaScript jest skompilowany.

0

Kontekst.observeInstructionCountCount jest wywoływany tylko w trybie interpretacji, więc jest to znaczące osiągnięcie wydajności.Mam nadzieję, że zespół Rhino wymyśli lepszy sposób.

-1

Wiem, że jest to starszy wątek, ale mam bardziej bezpośrednie rozwiązanie dla zatrzymania JavaScript eval: Wywołaj funkcję "wyjścia", którą zapewnia Nashorn.

Wewnątrz klasy I wykorzystać do uruchomienia silnik skryptowy, I obejmują:

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; 
    } 

teraz, zadzwoń:

myAwesomeClass.shutdownNow(); 

Skrypt natychmiast zatrzymać.

+1

Downvoting. Wywołanie "exit" nie powoduje zamknięcia mechanizmu skryptu, ale całej maszyny JVM. –

+0

Nie zgadzam się; tak nie było w moim projekcie. Silnik skryptu zatrzymał się, reszta programu kontynuowała działanie na maszynie JVM. – user761576

0

Skrypty Nashorn są kompilowane do ".class" plików & ładowanych w locie. Tak więc ocena skryptu jest podobna do ładowania skompilowanej klasy Java .class i uruchamiania jej. O ile nie zaprogramujesz jawnie przerwania, nie możesz zatrzymać oceny skryptu. Nie ma "interpretera skryptów", który "odpytuje" o status przerwań. Musisz jawnie wywołać Thread.sleep lub inny interfejs API języka Java, który zostanie przerwany z innego wątku.

Powiązane problemy