File: timers.rb

package info (click to toggle)
ruby-httpx 1.7.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,816 kB
  • sloc: ruby: 12,209; makefile: 4
file content (127 lines) | stat: -rw-r--r-- 2,743 bytes parent folder | download
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
# frozen_string_literal: true

module HTTPX
  class Timers
    def initialize
      @intervals = []
    end

    def after(interval_in_secs, cb = nil, &blk)
      callback = cb || blk

      raise Error, "timer must have a callback" unless callback

      # I'm assuming here that most requests will have the same
      # request timeout, as in most cases they share common set of
      # options. A user setting different request timeouts for 100s of
      # requests will already have a hard time dealing with that.
      unless (interval = @intervals.bsearch { |t| t.interval == interval_in_secs })
        interval = Interval.new(interval_in_secs)
        @intervals << interval
        @intervals.sort!
      end

      interval << callback

      @next_interval_at = nil

      Timer.new(interval, callback)
    end

    def wait_interval
      return if @intervals.empty?

      first_interval = @intervals.first

      drop_elapsed!(0) if first_interval.elapsed?(0)

      @next_interval_at = Utils.now

      first_interval.interval
    end

    def fire(error = nil)
      raise error if error && error.timeout != @intervals.first
      return if @intervals.empty? || !@next_interval_at

      elapsed_time = Utils.elapsed_time(@next_interval_at)

      drop_elapsed!(elapsed_time)

      @next_interval_at = nil if @intervals.empty?
    end

    private

    def drop_elapsed!(elapsed_time)
      @intervals = @intervals.drop_while { |interval| interval.elapse(elapsed_time) <= 0 }
    end

    class Timer
      def initialize(interval, callback)
        @interval = interval
        @callback = callback
      end

      def cancel
        @interval.delete(@callback)
      end
    end

    class Interval
      include Comparable

      attr_reader :interval

      def initialize(interval)
        @interval = interval
        @callbacks = []
      end

      def <=>(other)
        @interval <=> other.interval
      end

      def ==(other)
        return @interval == other if other.is_a?(Numeric)

        @interval == other.to_f # rubocop:disable Lint/FloatComparison
      end

      def to_f
        Float(@interval)
      end

      def <<(callback)
        @callbacks << callback
      end

      def delete(callback)
        @callbacks.delete(callback)
      end

      def no_callbacks?
        @callbacks.empty?
      end

      def elapsed?(elapsed = 0)
        (@interval - elapsed) <= 0 || @callbacks.empty?
      end

      def elapse(elapsed)
        # same as elapsing
        return 0 if @callbacks.empty?

        @interval -= elapsed

        if @interval <= 0
          cb = @callbacks.dup
          cb.each(&:call)
        end

        @interval
      end
    end
    private_constant :Interval
  end
end