1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
# HTTPClient - HTTP client library.
# Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
require 'timeout'
require 'thread'
class HTTPClient
# Replaces timeout.rb to avoid Thread creation and scheduling overhead.
#
# You should check another timeout replace in WEBrick.
# See lib/webrick/utils.rb in ruby/1.9.
#
# About this implementation:
# * Do not create Thread for each timeout() call. Just create 1 Thread for
# timeout scheduler.
# * Do not wakeup the scheduler thread so often. Let scheduler thread sleep
# until the nearest period.
if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
class TimeoutScheduler
# Represents timeout period.
class Period
attr_reader :thread, :time
# Creates new Period.
def initialize(thread, time, ex)
@thread, @time, @ex = thread, time, ex
@lock = Mutex.new
end
# Raises if thread exists and alive.
def raise(message)
@lock.synchronize do
if @thread and @thread.alive?
@thread.raise(@ex, message)
end
end
end
# Cancel this Period. Mutex is needed to avoid too-late exception.
def cancel
@lock.synchronize do
@thread = nil
end
end
end
# Creates new TimeoutScheduler.
def initialize
@pool = {}
@next = nil
@thread = start_timer_thread
end
# Registers new timeout period.
def register(thread, sec, ex)
period = Period.new(thread, Time.now + sec, ex || ::Timeout::Error)
@pool[period] = true
if @next.nil? or period.time < @next
begin
@thread.wakeup
rescue ThreadError
# Thread may be dead by fork.
@thread = start_timer_thread
end
end
period
end
# Cancels the given period.
def cancel(period)
@pool.delete(period)
period.cancel
end
private
def start_timer_thread
thread = Thread.new {
while true
if @pool.empty?
@next = nil
sleep
else
min, = @pool.min { |a, b| a[0].time <=> b[0].time }
@next = min.time
sec = @next - Time.now
if sec > 0
sleep(sec)
end
end
now = Time.now
@pool.keys.each do |period|
if period.time < now
period.raise('execution expired')
cancel(period)
end
end
end
}
Thread.pass while thread.status != 'sleep'
thread
end
end
class << self
# CAUTION: caller must aware of race condition.
def timeout_scheduler
@timeout_scheduler ||= TimeoutScheduler.new
end
end
timeout_scheduler # initialize at first time.
end
module Timeout
if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
def timeout(sec, ex = nil, &block)
return yield if sec == nil or sec.zero?
scheduler = nil
begin
scheduler = HTTPClient.timeout_scheduler
period = scheduler.register(Thread.current, sec, ex)
yield(sec)
ensure
scheduler.cancel(period) if scheduler and period
end
end
end
end
end
|