2009-10-21 14 views
6

Ho scritto un servlet che recupera un codice di script java e lo elabora e restituisce la risposta. per questo ho usato l'API java scriptingAPI java scripting - come interrompere la valutazione

nel codice qui sotto se script = "print ('Hello, World')"; il codice terminerà correttamente stampando "Ciao mondo". ma se script = "while (true);" lo script verrà ripetuto all'infinito.

la mia domanda è come faccio a uccidere il processo eval nel caso in cui ci voglia troppo tempo (diciamo 15 sec)?

grazie

risposta

3

Eseguire la valutazione in un thread separato e interrompere dopo 15s usando Thread.interrupt(). Ciò interromperà l'eval e genererà un InterruptedException, che è possibile rilevare e restituire uno stato di errore.

Una soluzione migliore sarebbe quella di avere una sorta di interfaccia asynch per il motore di scripting, ma per quanto ho potuto vedere questo non esiste.

EDIT:

Come sfussenegger sottolineato, interrompendo non funziona con il motore di script, dal momento che non dorme mai o entra in uno stato di attesa per ottenere interrotto. Niether potrei trovare alcuna callback periodica negli oggetti ScriptContext o Bindings che potrebbero essere usati come un hook per verificare eventuali interruzioni. C'è un metodo che funziona, però: Thread.stop(). È deprecato e intrinsecamente non sicuro per una serie di motivi, ma per completezza posterò il mio codice di test qui insieme all'implementazione di Chris Winters per il confronto. la versione di Chris sarà timeout ma lasciare il thread in background in esecuzione, l'interrupt() non fa nulla e la fermata() uccide il filo e riprende il controllo al thread principale:

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 Se il battistrada non è in attesa, non ci sarà un'eccezione. Quindi, non funzionerà con un ciclo infinito. Il codice dovrebbe controllare 'Thread.currentThread(). Interrupted()' perché i tuoi suggerimenti funzionino. – sfussenegger

+0

Argh, mancava quello. Ciò lascia solo la fermata deprecata() credo? –

+0

ha cambiato il mio voto – sfussenegger

3

Ecco po 'di codice che mostra l'attuazione Futuro e il filo .stop() uno. Questo è un problema interessante e sottolinea la necessità di un hook in uno ScriptEngine per poter interrompere qualunque script sia in esecuzione per qualsiasi motivo. Mi chiedo se questo potrebbe infrangere le ipotesi nella maggior parte delle implementazioni dal momento che assumono che eval() verrà eseguito in un ambiente a thread singolo (bloccante)?

In ogni caso, i risultati dell'esecuzione del codice qui sotto:

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

Ecco il programma completo:

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

Riprende il controllo dopo 15 secondi con un'eccezione di timeout, ma lascia in esecuzione il thread in background, quindi rimane la domanda su come interromperlo. –

+0

Duh! Grazie per il commento, ha aggiunto l'annullamento futuro. –

+1

hi, grazie per l'elegante soluzione, ma anche dopo la cancellazione di Future il thread in background è ancora in esecuzione consumando le risorse della CPU. quindi la domanda rimane ancora .. – special0ne

2

Se si è disposti a utilizzare Thread.stop() (e in realtà dovrebbe essere), sembra non esserci alcun modo per raggiungere i tuoi requisiti con l'API javax.script.

Se si utilizza direttamente il motore di Rhino e le prestazioni effettive non sono troppo importanti, è possibile implementare un hook in Context.observeInstructionCount per interrompere o interrompere prematuramente l'esecuzione dello script. L'hook viene invocato per ogni istruzione JavaScript eseguita dopo aver raggiunto la soglia (conteggio istruzioni) impostata con setInstructionObserverThreshold. È necessario misurare da sé il tempo di esecuzione, poiché viene fornito solo il numero di istruzioni eseguite e questo può avere un impatto sulle prestazioni rilevante. Non sono sicuro, ma l'hook può anche essere invocato solo se il motore di script viene eseguito in modalità interpretata e non se il codice JavaScript è compilato.

0

Context.observeInstructionCount viene chiamato solo in modalità interpretata, quindi si tratta di un notevole calo di prestazioni.Spero davvero che il team di Rhino abbia un modo migliore.

-1

So che questo è un thread precedente, ma ho una soluzione più diretta per arrestare la valutazione JavaScript: Richiama la funzione "esci" fornita da Nashorn.

All'interno della classe che uso per eseguire il motore di script, mi sono:

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

Ora, chiamata:

myAwesomeClass.shutdownNow(); 

Lo script si fermerà immediatamente.

+1

Downvoting. La chiamata "exit" non esce dal motore di script ma dall'intera JVM. –

+0

Non sono d'accordo; questo non era il caso nel mio progetto. Il motore di script si fermò, il resto del mio programma continuò a funzionare sulla JVM. – user761576

0

Gli script Nashorn sono compilati in. File "file" & caricati al volo. Quindi, la valutazione degli script è simile al caricamento di una classe .class compilata e alla stessa esecuzione. A meno che non si programma esplicitamente per l'interruzione, non è possibile interrompere la valutazione dello script. Non esiste un "interprete di script" che "esegue il polling" per lo stato di interruzione. Devi chiamare Thread.sleep o altre API Java che devono essere interrotte da un altro thread.

Problemi correlati