File: timeout.rb

package info (click to toggle)
ruby-httpclient 2.8.3%2Bgit20211122.4658227-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 1,908 kB
  • sloc: ruby: 9,963; makefile: 10; sh: 2
file content (140 lines) | stat: -rw-r--r-- 3,493 bytes parent folder | download | duplicates (4)
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