diff options
Diffstat (limited to 'lib/webrick/utils.rb')
-rw-r--r-- | lib/webrick/utils.rb | 75 |
1 files changed, 75 insertions, 0 deletions
diff --git a/lib/webrick/utils.rb b/lib/webrick/utils.rb index cf9da6f2ce..4a447aaf85 100644 --- a/lib/webrick/utils.rb +++ b/lib/webrick/utils.rb @@ -96,5 +96,80 @@ module WEBrick end module_function :random_string + ########### + + require "thread" + require "timeout" + require "singleton" + + class TimeoutHandler + include Singleton + TimeoutMutex = Mutex.new + + def TimeoutHandler.register(seconds, exception) + TimeoutMutex.synchronize{ + instance.register(Thread.current, Time.now + seconds, exception) + } + end + + def TimeoutHandler.cancel(id) + TimeoutMutex.synchronize{ + instance.cancel(Thread.current, id) + } + end + + def initialize + @timeout_info = Hash.new + Thread.start{ + while true + now = Time.now + @timeout_info.each{|thread, ary| + ary.each{|info| + time, exception = *info + interrupt(thread, info.object_id, exception) if time < now + } + } + sleep 0.5 + end + } + end + + def interrupt(thread, id, exception) + TimeoutMutex.synchronize{ + if cancel(thread, id) && thread.alive? + thread.raise(exception, "execution timeout") + end + } + end + + def register(thread, time, exception) + @timeout_info[thread] ||= Array.new + @timeout_info[thread] << [time, exception] + return @timeout_info[thread].last.object_id + end + + def cancel(thread, id) + if ary = @timeout_info[thread] + ary.delete_if{|info| info.object_id == id } + if ary.empty? + @timeout_info.delete(thread) + end + return true + end + return false + end + end + + def timeout(seconds, exception=Timeout::Error) + return yield if seconds.nil? or seconds.zero? + raise ThreadError, "timeout within critical session" if Thread.critical + id = TimeoutHandler.register(seconds, exception) + begin + yield(seconds) + ensure + TimeoutHandler.cancel(id) + end + end + module_function :timeout end end |