1
0
Fork 0
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:
kares 2012-02-15 11:08:48 +01:00
parent b4ac2b3339
commit 036e38cd49
2 changed files with 122 additions and 15 deletions

View file

@ -153,6 +153,24 @@ module Rhino
nil nil
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
@ -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,22 +305,61 @@ module Rhino
@instruction_limit = limit @instruction_limit = limit
end end
attr_accessor :instruction_count attr_reader :instruction_count
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! 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
def reset! elapsed_time = Time.now.to_f - start_time.to_f
self.instruction_count = 0 if elapsed_time > timeout_limit
self.instruction_limit = nil raise ScriptTimeoutError, "script exceeded timeout: #{timeout_limit} seconds"
self 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
end
def reset!
@instruction_count = 0
@start_time = Time.now
self
end
end end
end end
@ -315,5 +369,8 @@ module Rhino
class RunawayScriptError < ContextError # :nodoc: class RunawayScriptError < ContextError # :nodoc:
end end
class ScriptTimeoutError < ContextError # :nodoc:
end
end end

View file

@ -89,5 +89,55 @@ describe Rhino::Context do
context.eval %Q{ for (var i = 0; i < 100; i++) Number(i).toString(); } context.eval %Q{ for (var i = 0; i < 100; i++) Number(i).toString(); }
}.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