mirror of
https://github.com/rubyjs/therubyrhino
synced 2023-03-27 23:21:34 -04:00
support for a timeout limit when executing JS within a context
This commit is contained in:
parent
b4ac2b3339
commit
036e38cd49
2 changed files with 122 additions and 15 deletions
|
@ -154,6 +154,24 @@ module Rhino
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def timeout_limit
|
||||||
|
restrictable? ? @native.timeout_limit : false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set the duration (in seconds e.g. 1.5) this context is allowed to execute.
|
||||||
|
# After the timeout passes (no matter if any JS has been evaluated) and this
|
||||||
|
# context is still attempted to run code, a #Rhino::ScriptTimeoutError will
|
||||||
|
# be raised.
|
||||||
|
def timeout_limit=(limit)
|
||||||
|
if restrictable?
|
||||||
|
@native.timeout_limit = limit
|
||||||
|
else
|
||||||
|
warn "setting an timeout_limit has no effect on this context, use " +
|
||||||
|
"Context.open(:restricted => true) to gain a restrictable instance"
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def optimization_level
|
def optimization_level
|
||||||
@native.getOptimizationLevel
|
@native.getOptimizationLevel
|
||||||
end
|
end
|
||||||
|
@ -260,16 +278,13 @@ module Rhino
|
||||||
|
|
||||||
# protected void observeInstructionCount(Context context, int instructionCount)
|
# protected void observeInstructionCount(Context context, int instructionCount)
|
||||||
def observeInstructionCount(context, count)
|
def observeInstructionCount(context, count)
|
||||||
if context.is_a?(Context)
|
context.check!(count) if context.is_a?(Context)
|
||||||
context.instruction_count += count
|
|
||||||
context.check!
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# protected Object doTopCall(Callable callable, Context context,
|
# protected Object doTopCall(Callable callable, Context context,
|
||||||
# Scriptable scope, Scriptable thisObj, Object[] args)
|
# Scriptable scope, Scriptable thisObj, Object[] args)
|
||||||
def doTopCall(callable, context, scope, this, args)
|
def doTopCall(callable, context, scope, this, args)
|
||||||
context.instruction_count = 0 if context.is_a?(Context)
|
context.reset! if context.is_a?(Context)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -290,19 +305,58 @@ module Rhino
|
||||||
@instruction_limit = limit
|
@instruction_limit = limit
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :instruction_count
|
attr_reader :instruction_count
|
||||||
|
|
||||||
def check!
|
TIMEOUT_INSTRUCTION_TRESHOLD = 42
|
||||||
|
|
||||||
|
attr_reader :timeout_limit
|
||||||
|
|
||||||
|
def timeout_limit=(limit) # in seconds
|
||||||
|
treshold = getInstructionObserverThreshold
|
||||||
|
if limit && (treshold == 0 || treshold > TIMEOUT_INSTRUCTION_TRESHOLD)
|
||||||
|
setInstructionObserverThreshold(TIMEOUT_INSTRUCTION_TRESHOLD)
|
||||||
|
end
|
||||||
|
@timeout_limit = limit
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :start_time
|
||||||
|
|
||||||
|
def check!(count = nil)
|
||||||
|
@instruction_count += count if count
|
||||||
|
check_instruction_limit!
|
||||||
|
check_timeout_limit!(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_instruction_limit!
|
||||||
if instruction_limit && instruction_count > instruction_limit
|
if instruction_limit && instruction_count > instruction_limit
|
||||||
raise RunawayScriptError, "script exceeded allowable instruction count: #{instruction_limit}"
|
raise RunawayScriptError, "script exceeded allowable instruction count: #{instruction_limit}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def check_timeout_limit!(count = nil)
|
||||||
|
if timeout_limit
|
||||||
|
elapsed_time = Time.now.to_f - start_time.to_f
|
||||||
|
if elapsed_time > timeout_limit
|
||||||
|
raise ScriptTimeoutError, "script exceeded timeout: #{timeout_limit} seconds"
|
||||||
|
end
|
||||||
|
# adapt instruction treshold as needed :
|
||||||
|
if count
|
||||||
|
treshold = getInstructionObserverThreshold
|
||||||
|
if elapsed_time * 2 < timeout_limit
|
||||||
|
next_treshold_guess = treshold * 2
|
||||||
|
if instruction_limit && instruction_limit < next_treshold_guess
|
||||||
|
setInstructionObserverThreshold(instruction_limit)
|
||||||
|
else
|
||||||
|
setInstructionObserverThreshold(next_treshold_guess)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def reset!
|
def reset!
|
||||||
self.instruction_count = 0
|
@instruction_count = 0
|
||||||
self.instruction_limit = nil
|
@start_time = Time.now
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -316,4 +370,7 @@ module Rhino
|
||||||
class RunawayScriptError < ContextError # :nodoc:
|
class RunawayScriptError < ContextError # :nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class ScriptTimeoutError < ContextError # :nodoc:
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -90,4 +90,54 @@ describe Rhino::Context do
|
||||||
}.should_not raise_error(Rhino::RunawayScriptError)
|
}.should_not raise_error(Rhino::RunawayScriptError)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "allows a timeout limit per context" do
|
||||||
|
context1 = Rhino::Context.new :restrictable => true, :java => true
|
||||||
|
context1.timeout_limit = 0.3
|
||||||
|
|
||||||
|
context2 = Rhino::Context.new :restrictable => true, :java => true
|
||||||
|
context2.timeout_limit = 0.3
|
||||||
|
|
||||||
|
lambda {
|
||||||
|
context2.eval %Q{
|
||||||
|
var notDone = true;
|
||||||
|
(function foo() {
|
||||||
|
if (notDone) {
|
||||||
|
notDone = false;
|
||||||
|
java.lang.Thread.sleep(300);
|
||||||
|
foo();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}.should raise_error(Rhino::ScriptTimeoutError)
|
||||||
|
|
||||||
|
lambda {
|
||||||
|
context1.eval %Q{
|
||||||
|
var notDone = true;
|
||||||
|
(function foo {
|
||||||
|
if (notDone) {
|
||||||
|
notDone = false;
|
||||||
|
java.lang.Thread.sleep(100);
|
||||||
|
foo();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}.should_not raise_error(Rhino::RunawayScriptError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allows instruction and timeout limits at the same time" do
|
||||||
|
context = Rhino::Context.new :restrictable => true, :java => true
|
||||||
|
context.timeout_limit = 0.5
|
||||||
|
context.instruction_limit = 10000
|
||||||
|
lambda {
|
||||||
|
context.eval %Q{ for (var i = 0; i < 100; i++) { java.lang.Thread.sleep(100); } }
|
||||||
|
}.should raise_error(Rhino::ScriptTimeoutError)
|
||||||
|
|
||||||
|
context = Rhino::Context.new :restrictable => true, :java => true
|
||||||
|
context.timeout_limit = 0.5
|
||||||
|
context.instruction_limit = 1000
|
||||||
|
lambda {
|
||||||
|
context.eval %Q{ for (var i = 0; i < 100; i++) { java.lang.Thread.sleep(10); } }
|
||||||
|
}.should raise_error(Rhino::RunawayScriptError)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
Loading…
Reference in a new issue