add eval-timeout to run code with a timeout

This commit is contained in:
Mariano Guerra 2013-02-07 16:49:05 +01:00
parent 1fc18a6025
commit 462ac5530b
5 changed files with 87 additions and 2 deletions

View file

@ -131,6 +131,13 @@ or if you don't need the internal parameters::
(js/set! scope "add" (js/wrap-plain-fn add))
(js/eval scope "add(1, 2)") 3.0)))
excecute code with timeout::
(def scope (js/new-safe-scope))
(js/eval-timeout scope "while (true);" 1000)
; will throw a Error exception
license?
--------

View file

@ -4,4 +4,7 @@
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.4.0"]
[org.mozilla/rhino "1.7R4"]])
[org.mozilla/rhino "1.7R4"]]
:source-paths ["src/clj"]
:java-source-paths ["src/java"])

View file

@ -1,7 +1,8 @@
(ns clj-rhino
(:refer-clojure :exclude (eval get get-in set!))
(:import [org.mozilla.javascript Context UniqueTag NativeArray NativeObject
BaseFunction]))
BaseFunction]
[org.marianoguerra.rhino TimedContextFactory]))
(defprotocol RhinoConvertible
(-to-rhino [object scope ctx] "convert a value to a rhino compatible type"))
@ -135,6 +136,15 @@
(or filename "<eval>")
(or line-number 1) sec-domain))))
(defn eval-timeout [scope code timeout-ms & {:keys [filename line-number sec-domain]}]
(let [filename (or filename "<eval>")
line-number (or line-number 1)
factory (TimedContextFactory. timeout-ms)
ctx (.enterContext factory)]
(try
(.evaluateString ctx scope code filename line-number sec-domain)
(finally (Context/exit)))))
(defn undefined? [value]
"return true if value is undefined"
(= value (. UniqueTag NOT_FOUND)))

View file

@ -0,0 +1,61 @@
package org.marianoguerra.rhino;
import org.mozilla.javascript.*;
public class TimedContextFactory extends ContextFactory {
// Custom Context to store execution time.
private static class TimedContext extends Context {
long startTime;
public TimedContext(ContextFactory factory) {
super(factory);
}
}
private long timeoutMillis;
private int ticksInterval;
public TimedContextFactory(long timeoutMillis) {
this(timeoutMillis, 10000);
}
public TimedContextFactory(long timeoutMillis, int ticksInterval) {
this.timeoutMillis = timeoutMillis;
this.ticksInterval = ticksInterval;
}
// Override makeContext()
protected Context makeContext() {
TimedContext cx = new TimedContext(this);
// Make Rhino runtime to call observeInstructionCount
// each ticksInterval bytecode instructions
cx.setInstructionObserverThreshold(this.ticksInterval);
return cx;
}
// Override observeInstructionCount(Context, int)
protected void observeInstructionCount(Context cx,
int instructionCount) {
TimedContext mcx = (TimedContext)cx;
long currentTime = System.currentTimeMillis();
if (currentTime - mcx.startTime > this.timeoutMillis) {
// More then timeoutMillis from Context creation time:
// it is time to stop the script.
// Throw Error instance to ensure that script will never
// get control back through catch or finally.
throw new Error();
}
}
// Override doTopCall(Callable, Context, Scriptable, Scriptable, Object[])
protected Object doTopCall(Callable callable,
Context cx, Scriptable scope,
Scriptable thisObj, Object[] args) {
TimedContext mcx = (TimedContext)cx;
mcx.startTime = System.currentTimeMillis();
return super.doTopCall(callable, cx, scope, thisObj, args);
}
}

View file

@ -167,6 +167,10 @@
(js/set! scope "add" (js/wrap-plain-fn add))
(is (= (js/eval scope "add(1, 2)") 3.0))))))
(testing "eval-timeout times out on infinite loop"
(let [scope (js/new-safe-scope)]
(is (thrown? Error (js/eval-timeout scope "while (true);" 1000)))))
(testing "native clojure functions can be added"
(js/with-context
(fn [ctx]