add eval-timeout to run code with a timeout
This commit is contained in:
parent
1fc18a6025
commit
462ac5530b
|
@ -131,6 +131,13 @@ or if you don't need the internal parameters::
|
||||||
(js/set! scope "add" (js/wrap-plain-fn add))
|
(js/set! scope "add" (js/wrap-plain-fn add))
|
||||||
(js/eval scope "add(1, 2)") 3.0)))
|
(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?
|
license?
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,7 @@
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||||
:dependencies [[org.clojure/clojure "1.4.0"]
|
: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"])
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
(ns clj-rhino
|
(ns clj-rhino
|
||||||
(:refer-clojure :exclude (eval get get-in set!))
|
(:refer-clojure :exclude (eval get get-in set!))
|
||||||
(:import [org.mozilla.javascript Context UniqueTag NativeArray NativeObject
|
(:import [org.mozilla.javascript Context UniqueTag NativeArray NativeObject
|
||||||
BaseFunction]))
|
BaseFunction]
|
||||||
|
[org.marianoguerra.rhino TimedContextFactory]))
|
||||||
|
|
||||||
(defprotocol RhinoConvertible
|
(defprotocol RhinoConvertible
|
||||||
(-to-rhino [object scope ctx] "convert a value to a rhino compatible type"))
|
(-to-rhino [object scope ctx] "convert a value to a rhino compatible type"))
|
||||||
|
@ -135,6 +136,15 @@
|
||||||
(or filename "<eval>")
|
(or filename "<eval>")
|
||||||
(or line-number 1) sec-domain))))
|
(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]
|
(defn undefined? [value]
|
||||||
"return true if value is undefined"
|
"return true if value is undefined"
|
||||||
(= value (. UniqueTag NOT_FOUND)))
|
(= value (. UniqueTag NOT_FOUND)))
|
61
src/java/org/marianoguerra/rhino/TimedContextFactory.java
Normal file
61
src/java/org/marianoguerra/rhino/TimedContextFactory.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -167,6 +167,10 @@
|
||||||
(js/set! scope "add" (js/wrap-plain-fn add))
|
(js/set! scope "add" (js/wrap-plain-fn add))
|
||||||
(is (= (js/eval scope "add(1, 2)") 3.0))))))
|
(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"
|
(testing "native clojure functions can be added"
|
||||||
(js/with-context
|
(js/with-context
|
||||||
(fn [ctx]
|
(fn [ctx]
|
||||||
|
|
Reference in a new issue