timers 3.0.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6b1ffcb7b42d35c2139deec0e9c80e51de965726
4
- data.tar.gz: 16f73387c90dcc417750ddd350e99dd99488d6cc
3
+ metadata.gz: aa56588795590f6b0772c42830b2969d4c18c31b
4
+ data.tar.gz: 203a266076597db814652c1b1e8d25fdca6808b2
5
5
  SHA512:
6
- metadata.gz: 8a6fe65d356cdf4ff49e68bc6a566c73f8b0028cbd7ffe9f9acafc677326d5d671e018baaf2b457c44add0cb4fb68ff1b2233df77f6e87ff1d6e036dab7637da
7
- data.tar.gz: 87196f70fcfbca7cac3b97d73daaab1cad153b2a8485ecd234bf0654fc1284466bacbaef0a8fe991a8b7fd050ab7fad763d7df9dabec388c019a1c283000e61d
6
+ metadata.gz: cf717b2718394941dc03bd9bd4184a790116fff7e936b07227fe64cec0d7801ff84903ba23efd5332751919d3a9acbbff95c3a1b53f8c150cd67a94c78531763
7
+ data.tar.gz: 26d41eddf5f924ac26860a09daba9af79203f32f0f7cefc93cafc5a46ffd95ad959f0281c6dbe619ca3ad8087c63e16c87ef1e860d297f6c30e98da3562c5be0
data/CHANGES.md CHANGED
@@ -1,8 +1,16 @@
1
- 3.0.1 (2013-06-27)
1
+ 4.0.0 (2014-07-27)
2
+ ------------------
3
+ * Replace Timers::Timeout with Timers::Wait
4
+ * Timers::Group#wait_interval now returns nil when no timers, a postive or
5
+ negative interval which if positive is the amount of time required to wait
6
+ and if negative, how far in the past the latest timer should have fired
7
+ * Performance improvements
8
+
9
+ 3.0.1 (2014-06-27)
2
10
  ------------------
3
11
  * Require 'set' automatically
4
12
 
5
- 3.0.0 (2013-06-14)
13
+ 3.0.0 (2014-06-14)
6
14
  ------------------
7
15
  * Refactor `Timers` class into `Timers::Group`
8
16
  * Add `Timers::Timeout` class for implementing timeouts
data/Gemfile CHANGED
@@ -4,3 +4,7 @@ gem 'coveralls', :require => false
4
4
 
5
5
  # Specify your gem's dependencies in timers.gemspec
6
6
  gemspec
7
+
8
+ unless RUBY_PLATFORM =~ /java/
9
+ gem "ruby-prof", :group => :development
10
+ end
data/README.md CHANGED
@@ -5,8 +5,8 @@ Timers
5
5
  [![Code Climate](https://codeclimate.com/github/celluloid/timers.png)](https://codeclimate.com/github/celluloid/timers)
6
6
  [![Coverage Status](https://coveralls.io/repos/celluloid/timers/badge.png?branch=master)](https://coveralls.io/r/celluloid/timers)
7
7
 
8
- Pure Ruby timer collections. Schedule several procs to fire after configurable
9
- delays or at periodic intervals.
8
+ Ruby timer collections. Schedule several procs to fire after configurable delays
9
+ or at periodic intervals.
10
10
 
11
11
  This gem is especially useful when you are faced with an API that accepts a
12
12
  single timeout but you want to run multiple timers on top of it. An example of
@@ -16,15 +16,15 @@ Ruby library for using system calls like epoll and kqueue.
16
16
  Usage
17
17
  -----
18
18
 
19
- Create a new timer group with `Timers.new`:
19
+ Create a new timer group with `Timers::Group.new`:
20
20
 
21
21
  ```ruby
22
22
  require 'timers'
23
23
 
24
- timers = Timers.new
24
+ timers = Timers::Group.new
25
25
  ```
26
26
 
27
- Schedule a proc to run after 5 seconds with `Timers#after`:
27
+ Schedule a proc to run after 5 seconds with `Timers::Group#after`:
28
28
 
29
29
  ```ruby
30
30
  five_second_timer = timers.after(5) { puts "Take five" }
@@ -33,7 +33,7 @@ five_second_timer = timers.after(5) { puts "Take five" }
33
33
  The `five_second_timer` variable is now bound to a Timers::Timer object. To
34
34
  cancel a timer, use `Timers::Timer#cancel`
35
35
 
36
- Once you've scheduled a timer, you can wait until the next timer fires with `Timers#wait`:
36
+ Once you've scheduled a timer, you can wait until the next timer fires with `Timers::Group#wait`:
37
37
 
38
38
  ```ruby
39
39
  # Waits 5 seconds
@@ -42,7 +42,7 @@ timers.wait
42
42
  # The script will now print "Take five"
43
43
  ```
44
44
 
45
- You can schedule a block to run periodically with `Timers#every`:
45
+ You can schedule a block to run periodically with `Timers::Group#every`:
46
46
 
47
47
  ```ruby
48
48
  every_five_seconds = timers.every(5) { puts "Another 5 seconds" }
@@ -51,8 +51,8 @@ loop { timers.wait }
51
51
  ```
52
52
 
53
53
  If you'd like another method to do the waiting for you, e.g. `Kernel.select`,
54
- you can use `Timers#wait_interval` to obtain the amount of time to wait. When
55
- a timeout is encountered, you can fire all pending timers with `Timers#fire`:
54
+ you can use `Timers::Group#wait_interval` to obtain the amount of time to wait. When
55
+ a timeout is encountered, you can fire all pending timers with `Timers::Group#fire`:
56
56
 
57
57
  ```ruby
58
58
  loop do
@@ -77,13 +77,13 @@ paused_timer = timers.every(5) { puts "I was paused" }
77
77
  paused_timer.pause
78
78
  10.times { timers.wait } # will not fire paused timer
79
79
 
80
- paused_timer.continue
80
+ paused_timer.resume
81
81
  10.times { timers.wait } # will fire timer
82
82
 
83
83
  timers.pause
84
84
  10.times { timers.wait } # will not fire any timers
85
85
 
86
- timers.continue
86
+ timers.resume
87
87
  10.times { timers.wait } # will fire all timers
88
88
  ```
89
89
 
@@ -1,10 +1,5 @@
1
1
 
2
- # Workaround for thread safety issues in SortedSet initialization
3
- # See: https://github.com/celluloid/timers/issues/20
4
- require 'set'
5
- SortedSet.new
6
-
7
2
  require 'timers/version'
8
3
 
9
4
  require 'timers/group'
10
- require 'timers/timeout'
5
+ require 'timers/wait'
@@ -0,0 +1,115 @@
1
+
2
+ require 'forwardable'
3
+ require 'hitimes'
4
+
5
+ require 'timers/timer'
6
+
7
+ module Timers
8
+ # Maintains an ordered list of events, which can be cancelled.
9
+ class Events
10
+ # Represents a cancellable handle for a specific timer event.
11
+ class Handle
12
+ def initialize(time, callback)
13
+ @time = time
14
+ @callback = callback
15
+ end
16
+
17
+ # The absolute time that the handle should be fired at.
18
+ attr :time
19
+
20
+ # Cancel this timer, O(1).
21
+ def cancel!
22
+ # The simplest way to keep track of cancelled status is to nullify the
23
+ # callback. This should also be optimal for garbage collection.
24
+ @callback = nil
25
+ end
26
+
27
+ # Has this timer been cancelled? Cancelled timer's don't fire.
28
+ def cancelled?
29
+ @callback.nil?
30
+ end
31
+
32
+ def > other
33
+ @time > other.to_f
34
+ end
35
+
36
+ def to_f
37
+ @time
38
+ end
39
+
40
+ # Fire the callback if not cancelled with the given time parameter.
41
+ def fire(time)
42
+ if @callback
43
+ @callback.call(time)
44
+ end
45
+ end
46
+ end
47
+
48
+ def initialize
49
+ # A sequence of handles, maintained in sorted order, future to present.
50
+ # @sequence.last is the next event to be fired.
51
+ @sequence = []
52
+ end
53
+
54
+ # Add an event at the given time.
55
+ def schedule(time, callback)
56
+ handle = Handle.new(time.to_f, callback)
57
+
58
+ index = bisect_left(@sequence, handle)
59
+
60
+ # Maintain sorted order, O(logN) insertion time.
61
+ @sequence.insert(index, handle)
62
+
63
+ return handle
64
+ end
65
+
66
+ # Returns the first non-cancelled handle.
67
+ def first
68
+ while handle = @sequence.last
69
+ if handle.cancelled?
70
+ @sequence.pop
71
+ else
72
+ return handle
73
+ end
74
+ end
75
+ end
76
+
77
+ # Returns the number of pending (possibly cancelled) events.
78
+ def size
79
+ @sequence.size
80
+ end
81
+
82
+ # Fire all handles for which Handle#time is less than the given time.
83
+ def fire(time)
84
+ pop(time).reverse_each do |handle|
85
+ handle.fire(time)
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ # Efficiently take k handles for which Handle#time is less than the given
92
+ # time.
93
+ def pop(time)
94
+ index = bisect_left(@sequence, time)
95
+
96
+ return @sequence.pop(@sequence.size - index)
97
+ end
98
+
99
+ # Return the left-most index where to insert item e, in a list a, assuming
100
+ # a is sorted in descending order.
101
+ def bisect_left(a, e, l = 0, u = a.length)
102
+ while l < u
103
+ m = l + (u-l)/2
104
+
105
+ if a[m] > e
106
+ l = m+1
107
+ else
108
+ u = m
109
+ end
110
+ end
111
+
112
+ return l
113
+ end
114
+ end
115
+ end
@@ -4,103 +4,115 @@ require 'forwardable'
4
4
  require 'hitimes'
5
5
 
6
6
  require 'timers/timer'
7
+ require 'timers/events'
7
8
 
8
9
  module Timers
9
10
  class Group
10
11
  include Enumerable
11
- extend Forwardable
12
- def_delegators :@timers, :delete, :each, :empty?
12
+
13
+ extend Forwardable
14
+ def_delegators :@timers, :each, :empty?
13
15
 
14
16
  def initialize
15
- @timers = SortedSet.new
16
- @paused_timers = SortedSet.new
17
+ @events = Events.new
18
+
19
+ @timers = Set.new
20
+ @paused_timers = Set.new
21
+
17
22
  @interval = Hitimes::Interval.new
18
23
  @interval.start
19
24
  end
20
25
 
21
- # Call the given block after the given interval
26
+ # Scheduled events:
27
+ attr :events
28
+
29
+ # Active timers:
30
+ attr :timers
31
+
32
+ # Paused timers:
33
+ attr :paused_timers
34
+
35
+ # Call the given block after the given interval. The first argument will be
36
+ # the time at which the group was asked to fire timers for.
22
37
  def after(interval, &block)
23
38
  Timer.new(self, interval, false, &block)
24
39
  end
25
40
 
26
- # Call the given block after the given interval has expired. +interval+
27
- # is measured in milliseconds.
28
- #
29
- # Timer.new.after_milliseconds(25) { puts "fired!" }
30
- #
31
- def after_milliseconds(interval, &block)
32
- after(interval / 1000.0, &block)
33
- end
34
- alias_method :after_ms, :after_milliseconds
35
-
36
- # Call the given block periodically at the given interval
37
- def every(interval, &block)
38
- Timer.new(self, interval, true, &block)
41
+ # Call the given block periodically at the given interval. The first
42
+ # argument will be the time at which the group was asked to fire timers for.
43
+ def every(interval, recur = true, &block)
44
+ Timer.new(self, interval, recur, &block)
39
45
  end
40
46
 
41
- # Wait for the next timer and fire it
42
- def wait
43
- # Repeatedly call sleep until there is no longer any wait_interval:
44
- while i = wait_interval
45
- # We cannot assume that sleep will wait for the specified time, it might be +/- a bit.
46
- sleep i
47
+ # Wait for the next timer and fire it. Can take a block, which should behave
48
+ # like sleep(n), except that n may be nil (sleep forever) or a negative
49
+ # number (fire immediately after return).
50
+ def wait(&block)
51
+ if block_given?
52
+ yield wait_interval
53
+
54
+ while interval = wait_interval and interval > 0
55
+ yield interval
56
+ end
57
+ else
58
+ while interval = wait_interval and interval > 0
59
+ # We cannot assume that sleep will wait for the specified time, it might be +/- a bit.
60
+ sleep interval
61
+ end
47
62
  end
48
-
63
+
49
64
  fire
50
65
  end
51
66
 
52
- # Interval to wait until when the next timer will fire
67
+ # Interval to wait until when the next timer will fire.
68
+ # - nil: no timers
69
+ # - -ve: timers expired already
70
+ # - 0: timers ready to fire
71
+ # - +ve: timers waiting to fire
53
72
  def wait_interval(offset = self.current_offset)
54
- timer = @timers.first
55
- return unless timer
56
- interval = timer.offset - Float(offset)
57
- interval > 0 ? interval : nil
58
- end
59
-
60
- # Fire all timers that are ready
61
- def fire(offset = self.current_offset)
62
- time = Float(offset)
63
- while (timer = @timers.first) && (time >= timer.offset)
64
- @timers.delete timer
65
- timer.fire(offset)
73
+ if handle = @events.first
74
+ return handle.time - Float(offset)
66
75
  end
67
76
  end
68
77
 
69
- def add(timer)
70
- raise TypeError, "not a Timers::Timer" unless timer.is_a? Timers::Timer
71
- @timers.add(timer)
78
+ # Fire all timers that are ready.
79
+ def fire(offset = self.current_offset)
80
+ @events.fire(offset)
72
81
  end
73
82
 
74
- def pause(timer = nil)
75
- return pause_all if timer.nil?
76
- raise TypeError, "not a Timers::Timer" unless timer.is_a? Timers::Timer
77
- @timers.delete timer
78
- @paused_timers.add timer
83
+ # Pause all timers.
84
+ def pause
85
+ @timers.dup.each do |timer|
86
+ timer.pause
87
+ end
79
88
  end
80
89
 
81
- def pause_all
82
- @timers.each {|timer| timer.pause}
90
+ # Resume all timers.
91
+ def resume
92
+ @paused_timers.dup.each do |timer|
93
+ timer.resume
94
+ end
83
95
  end
84
96
 
85
- def continue(timer = nil)
86
- return continue_all if timer.nil?
87
- raise TypeError, "not a Timers::Timer" unless timer.is_a? Timers::Timer
88
- @paused_timers.delete timer
89
- @timers.add timer
90
- end
91
-
92
- def continue_all
93
- @paused_timers.each {|timer| timer.continue}
94
- end
97
+ alias_method :continue, :resume
95
98
 
99
+ # Delay all timers.
96
100
  def delay(seconds)
97
- @timers.each {|timer| timer.delay(seconds)}
101
+ @timers.each do |timer|
102
+ timer.delay(seconds)
103
+ end
98
104
  end
99
105
 
100
- alias_method :cancel, :delete
106
+ # Cancel all timers.
107
+ def cancel
108
+ @timers.dup.each do |timer|
109
+ timer.cancel
110
+ end
111
+ end
101
112
 
113
+ # The group's current time.
102
114
  def current_offset
103
115
  @interval.to_f
104
116
  end
105
117
  end
106
- end
118
+ end
@@ -5,68 +5,99 @@ module Timers
5
5
  include Comparable
6
6
  attr_reader :interval, :offset, :recurring
7
7
 
8
- def initialize(timers, interval, recurring = false, &block)
9
- @timers, @interval, @recurring = timers, interval, recurring
10
- @block = block
11
- @offset = nil
8
+ def initialize(group, interval, recurring = false, offset = nil, &block)
9
+ @group = group
10
+
11
+ @group.timers << self
12
+
13
+ @interval = interval
14
+ @recurring = recurring
15
+ @block = block
16
+ @offset = offset
17
+
18
+ @handle = nil
19
+
20
+ # If a start offset was supplied, use that, otherwise use the current timers offset.
21
+ reset(@offset || @group.current_offset)
22
+ end
12
23
 
13
- reset
24
+ def paused?
25
+ @group.paused_timers.include? self
14
26
  end
15
27
 
16
- def <=>(other)
17
- @offset <=> other.offset
28
+ def pause
29
+ return if paused?
30
+
31
+ @group.timers.delete self
32
+ @group.paused_timers.add self
33
+
34
+ @handle.cancel! if @handle
35
+ @handle = nil
18
36
  end
19
37
 
20
- # Cancel this timer
21
- def cancel
22
- @timers.cancel self
38
+ def resume
39
+ return unless paused?
40
+
41
+ @group.timers.add self
42
+ @group.paused_timers.delete self
43
+
44
+ reset
23
45
  end
24
46
 
47
+ alias_method :continue, :resume
48
+
25
49
  # Extend this timer
26
50
  def delay(seconds)
27
- @timers.delete self
51
+ @handle.cancel! if @handle
52
+
28
53
  @offset += seconds
29
- @timers.add self
54
+
55
+ @handle = @group.events.schedule(@offset, self)
56
+ end
57
+
58
+ # Cancel this timer
59
+ def cancel
60
+ @handle.cancel! if @handle
61
+ @handle = nil
62
+
63
+ # This timer is no longer valid:
64
+ @group.timers.delete self
65
+ @group = nil
30
66
  end
31
67
 
32
68
  # Reset this timer
33
- def reset(offset = @timers.current_offset)
34
- @timers.cancel self if @offset
69
+ def reset(offset = @group.current_offset)
70
+ @handle.cancel! if @handle
71
+
35
72
  @offset = Float(offset) + @interval
36
- @timers.add self
73
+
74
+ @handle = @group.events.schedule(@offset, self)
37
75
  end
38
76
 
39
77
  # Fire the block
40
- def fire(offset = @timers.current_offset)
41
- if recurring
78
+ def fire(offset = @group.current_offset)
79
+ if recurring == :strict
80
+ # ... make the next interval strictly the last offset + the interval:
81
+ reset(@offset)
82
+ elsif recurring
42
83
  reset(offset)
43
84
  else
44
85
  @offset = offset
45
86
  end
46
-
47
- @block.call
48
- end
49
- alias_method :call, :fire
50
87
 
51
- # Pause this timer
52
- def pause
53
- @timers.pause self
88
+ @block.call(offset)
54
89
  end
55
90
 
56
- # Continue this timer
57
- def continue
58
- @timers.continue self
59
- end
91
+ alias_method :call, :fire
60
92
 
61
93
  # Number of seconds until next fire / since last fire
62
94
  def fires_in
63
- @offset - @timers.current_offset if @offset
95
+ @offset - @group.current_offset if @offset
64
96
  end
65
97
 
66
98
  # Inspect a timer
67
99
  def inspect
68
100
  str = "#<Timers::Timer:#{object_id.to_s(16)} "
69
- offset = @timers.current_offset
70
101
 
71
102
  if @offset
72
103
  if fires_in >= 0
@@ -83,4 +114,4 @@ module Timers
83
114
  str << ">"
84
115
  end
85
116
  end
86
- end
117
+ end
@@ -1,3 +1,3 @@
1
1
  module Timers
2
- VERSION = "3.0.1"
2
+ VERSION = "4.0.0"
3
3
  end
@@ -3,7 +3,19 @@ require 'hitimes'
3
3
 
4
4
  module Timers
5
5
  # An exclusive, monotonic timeout class.
6
- class Timeout
6
+ class Wait
7
+ def self.for(duration, &block)
8
+ if duration
9
+ timeout = self.new(duration)
10
+
11
+ timeout.while_time_remaining(&block)
12
+ else
13
+ while true
14
+ yield(nil)
15
+ end
16
+ end
17
+ end
18
+
7
19
  def initialize(duration)
8
20
  @duration = duration
9
21
  @remaining = true
@@ -0,0 +1,57 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ RSpec.describe Timers::Events do
5
+ it "should register an event" do
6
+ fired = false
7
+
8
+ callback = proc do |time|
9
+ fired = true
10
+ end
11
+
12
+ handle = subject.schedule(0.1, callback)
13
+
14
+ expect(subject.size).to be == 1
15
+
16
+ subject.fire(0.15)
17
+
18
+ expect(subject.size).to be == 0
19
+
20
+ expect(fired).to be true
21
+ end
22
+
23
+ it "should register events in order" do
24
+ fired = []
25
+
26
+ times = [0.95, 0.1, 0.3, 0.5, 0.4, 0.2, 0.01, 0.9]
27
+
28
+ times.each do |requested_time|
29
+ callback = proc do |time|
30
+ fired << requested_time
31
+ end
32
+
33
+ subject.schedule(requested_time, callback)
34
+ end
35
+
36
+ subject.fire(0.5)
37
+ expect(fired).to be == times.sort.first(6)
38
+
39
+ subject.fire(1.0)
40
+ expect(fired).to be == times.sort
41
+ end
42
+
43
+ it "should fire events with the time they were fired at" do
44
+ fired_at = :not_fired
45
+
46
+ callback = proc do |time|
47
+ # The time we actually were fired at:
48
+ fired_at = time
49
+ end
50
+
51
+ subject.schedule(0.5, callback)
52
+
53
+ subject.fire(1.0)
54
+
55
+ expect(fired_at).to be == 1.0
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ RSpec.describe Timers::Group do
5
+ it "should fire several times" do
6
+ result = []
7
+
8
+ subject.every(0.7) { result << :a }
9
+ subject.every(2.3) { result << :b }
10
+ subject.every(1.3) { result << :c }
11
+ subject.every(2.4) { result << :d }
12
+
13
+ Timers::Wait.for(2.5) do |remaining|
14
+ subject.wait if subject.wait_interval < remaining
15
+ end
16
+
17
+ expect(result).to be == [:a, :c, :a, :a, :b, :d]
18
+ end
19
+ end
@@ -2,6 +2,35 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  RSpec.describe Timers::Group do
5
+ describe "#wait" do
6
+ it "calls the wait block with nil" do
7
+ called = false
8
+
9
+ subject.wait do |interval|
10
+ expect(interval).to be == nil
11
+ called = true
12
+ end
13
+
14
+ expect(called).to be true
15
+ end
16
+
17
+ it "calls the wait block with an interval" do
18
+ called = false
19
+ fired = false
20
+
21
+ subject.after(0.1) { fired = true }
22
+
23
+ subject.wait do |interval|
24
+ expect(interval).to be_within(TIMER_QUANTUM).of(0.1)
25
+ called = true
26
+ sleep 0.2
27
+ end
28
+
29
+ expect(called).to be true
30
+ expect(fired).to be true
31
+ end
32
+ end
33
+
5
34
  it "sleeps until the next timer" do
6
35
  interval = TIMER_QUANTUM * 2
7
36
  started_at = Time.now
@@ -30,7 +59,7 @@ RSpec.describe Timers::Group do
30
59
  expect(subject.wait_interval).to be_within(TIMER_QUANTUM).of interval
31
60
 
32
61
  sleep(interval)
33
- expect(subject.wait_interval).to be(nil)
62
+ expect(subject.wait_interval).to be <= 0
34
63
  end
35
64
 
36
65
  it "fires timers in the correct order" do
@@ -68,26 +97,22 @@ RSpec.describe Timers::Group do
68
97
  end
69
98
  end
70
99
 
71
- describe "millisecond timers" do
72
- it "calculates the proper interval to wait until firing" do
73
- interval_ms = 25
100
+ it "calculates the proper interval to wait until firing" do
101
+ interval_ms = 25
74
102
 
75
- subject.after_milliseconds(interval_ms)
76
- expected_elapse = subject.wait_interval
103
+ subject.after(interval_ms / 1000.0)
77
104
 
78
- expect(subject.wait_interval).to be_within(TIMER_QUANTUM).of(interval_ms / 1000.0)
79
- end
105
+ expect(subject.wait_interval).to be_within(TIMER_QUANTUM).of(interval_ms / 1000.0)
80
106
  end
81
107
 
82
108
  describe "pause and continue timers" do
83
109
  before(:each) do
84
110
  @interval = TIMER_QUANTUM * 2
85
- started_at = Time.now
86
111
 
87
112
  @fired = false
88
- @timer = subject.every(@interval) { @fired = true }
113
+ @timer = subject.after(@interval) { @fired = true }
89
114
  @fired2 = false
90
- @timer2 = subject.every(@interval) { @fired2 = true }
115
+ @timer2 = subject.after(@interval) { @fired2 = true }
91
116
  end
92
117
 
93
118
  it "does not fire when paused" do
@@ -99,8 +124,11 @@ RSpec.describe Timers::Group do
99
124
  it "fires when continued after pause" do
100
125
  @timer.pause
101
126
  subject.wait
102
- @timer.continue
127
+ @timer.resume
128
+
129
+ sleep @timer.interval
103
130
  subject.wait
131
+
104
132
  expect(@fired).to be true
105
133
  end
106
134
 
@@ -114,8 +142,12 @@ RSpec.describe Timers::Group do
114
142
  it "can continue all timers at once" do
115
143
  subject.pause
116
144
  subject.wait
117
- subject.continue
145
+ subject.resume
146
+
147
+ # We need to wait until we are sure both timers will fire, otherwise highly accurate clocks (e.g. JVM) may only fire the first timer, but not the second, because they are actually schedueled at different times.
148
+ sleep TIMER_QUANTUM * 2
118
149
  subject.wait
150
+
119
151
  expect(@fired).to be true
120
152
  expect(@fired2).to be true
121
153
  end
@@ -126,7 +158,7 @@ RSpec.describe Timers::Group do
126
158
  timer.pause
127
159
  subject.wait
128
160
  expect(fired).not_to be true
129
- timer.continue
161
+ timer.resume
130
162
  expect(fired).not_to be true
131
163
  timer.fire
132
164
  expect(fired).to be true
@@ -156,8 +188,8 @@ RSpec.describe Timers::Group do
156
188
  it "fires timers in the correct order" do
157
189
  result = []
158
190
 
159
- second = subject.after(TIMER_QUANTUM * 2) { result << :two }
160
- third = subject.after(TIMER_QUANTUM * 3) { result << :three }
191
+ subject.after(TIMER_QUANTUM * 2) { result << :two }
192
+ subject.after(TIMER_QUANTUM * 3) { result << :three }
161
193
  first = subject.after(TIMER_QUANTUM * 1) { result << :one }
162
194
  first.delay(TIMER_QUANTUM * 3)
163
195
 
@@ -0,0 +1,83 @@
1
+
2
+ require 'spec_helper'
3
+ require 'ruby-prof' unless RUBY_PLATFORM =~ /java/
4
+
5
+ # Event based timers:
6
+
7
+ # Serviced 31812 events in 2.39075272 seconds, 13306.320832794887 e/s.
8
+ # Thread ID: 7336700
9
+ # Fiber ID: 30106340
10
+ # Total: 2.384043
11
+ # Sort by: self_time
12
+
13
+ # %self total self wait child calls name
14
+ # 13.48 0.510 0.321 0.000 0.189 369133 Timers::Events::Handle#<=>
15
+ # 8.12 0.194 0.194 0.000 0.000 427278 Timers::Events::Handle#to_f
16
+ # 4.55 0.109 0.109 0.000 0.000 427278 Float#<=>
17
+ # 4.40 1.857 0.105 0.000 1.752 466376 *Timers::Events#bsearch
18
+ # 4.30 0.103 0.103 0.000 0.000 402945 Float#to_f
19
+ # 2.65 0.063 0.063 0.000 0.000 33812 Array#insert
20
+ # 2.64 1.850 0.063 0.000 1.787 33812 Timers::Events#schedule
21
+ # 2.40 1.930 0.057 0.000 1.873 33812 Timers::Timer#reset
22
+ # 1.89 1.894 0.045 0.000 1.849 31812 Timers::Timer#fire
23
+ # 1.69 1.966 0.040 0.000 1.926 31812 Timers::Events::Handle#fire
24
+ # 1.35 0.040 0.032 0.000 0.008 33812 Timers::Events::Handle#initialize
25
+ # 1.29 0.044 0.031 0.000 0.013 44451 Timers::Group#current_offset
26
+
27
+ # SortedSet based timers:
28
+
29
+ # Serviced 32516 events in 66.753277275 seconds, 487.1072288781219 e/s.
30
+ # Thread ID: 15995640
31
+ # Fiber ID: 38731780
32
+ # Total: 66.716394
33
+ # Sort by: self_time
34
+
35
+ # %self total self wait child calls name
36
+ # 54.73 49.718 36.513 0.000 13.205 57084873 Timers::Timer#<=>
37
+ # 23.74 65.559 15.841 0.000 49.718 32534 Array#sort!
38
+ # 19.79 13.205 13.205 0.000 0.000 57084873 Float#<=>
39
+
40
+ # Max out events performance (on my computer):
41
+ # Serviced 1142649 events in 11.194903921 seconds, 102068.70405115146 e/s.
42
+
43
+ RSpec.describe Timers::Group do
44
+ if defined? RubyProf
45
+ before(:each) do
46
+ # Running RubyProf makes the code slightly slower.
47
+ RubyProf.start
48
+ puts "*** Running with RubyProf reduces performance ***"
49
+ end
50
+
51
+ after(:each) do |arg|
52
+ if RubyProf.running?
53
+ # file = arg.metadata[:description].gsub(/\s+/, '-')
54
+
55
+ result = RubyProf.stop
56
+
57
+ printer = RubyProf::FlatPrinter.new(result)
58
+ printer.print($stderr, min_percent: 1.0)
59
+ end
60
+ end
61
+ end
62
+
63
+ it "run efficiently" do
64
+ result = []
65
+ range = (1..500)
66
+ duration = 2.0
67
+
68
+ total = 0
69
+ range.each do |index|
70
+ offset = index.to_f / range.max
71
+ total += (duration / offset).floor
72
+
73
+ subject.every(index.to_f / range.max, :strict) { result << index }
74
+ end
75
+
76
+ subject.wait while result.size < total
77
+
78
+ rate = result.size.to_f / subject.current_offset
79
+ puts "Serviced #{result.size} events in #{subject.current_offset} seconds, #{rate} e/s."
80
+
81
+ expect(subject.current_offset).to be_within(TIMER_QUANTUM).of(duration)
82
+ end
83
+ end
@@ -0,0 +1,37 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ RSpec.describe Timers::Group do
5
+ it "should not diverge too much" do
6
+ fired = :not_fired_yet
7
+ count = 0
8
+ quantum = 0.01
9
+
10
+ start_offset = subject.current_offset
11
+ Timers::Timer.new(subject, quantum, :strict, start_offset) do |offset|
12
+ fired = offset
13
+ count += 1
14
+ end
15
+
16
+ iterations = 1000
17
+ subject.wait while count < iterations
18
+
19
+ # In my testing on the JVM, without the :strict recurring, I noticed 60ms of error here.
20
+ expect(fired - start_offset).to be_within(quantum).of(iterations * quantum)
21
+ end
22
+
23
+ it "should only fire once" do
24
+ fired = :not_fired_yet
25
+ count = 0
26
+
27
+ start_offset = subject.current_offset
28
+ Timers::Timer.new(subject, 0, :strict, start_offset) do |offset|
29
+ fired = offset
30
+ count += 1
31
+ end
32
+
33
+ subject.wait
34
+
35
+ expect(count).to be == 1
36
+ end
37
+ end
@@ -1,10 +1,10 @@
1
1
 
2
2
  require 'spec_helper'
3
- require 'timers/timeout'
3
+ require 'timers/wait'
4
4
 
5
- RSpec.describe Timers::Timeout do
5
+ RSpec.describe Timers::Wait do
6
6
  it "repeats until timeout expired" do
7
- timeout = Timers::Timeout.new(5)
7
+ timeout = Timers::Wait.new(5)
8
8
  count = 0
9
9
 
10
10
  timeout.while_time_remaining do |remaining|
@@ -18,7 +18,7 @@ RSpec.describe Timers::Timeout do
18
18
  end
19
19
 
20
20
  it "yields results as soon as possible" do
21
- timeout = Timers::Timeout.new(5)
21
+ timeout = Timers::Wait.new(5)
22
22
 
23
23
  result = timeout.while_time_remaining do |remaining|
24
24
  break :done
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timers
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Arcieri
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-27 00:00:00.000000000 Z
11
+ date: 2014-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hitimes
@@ -70,12 +70,17 @@ files:
70
70
  - README.md
71
71
  - Rakefile
72
72
  - lib/timers.rb
73
+ - lib/timers/events.rb
73
74
  - lib/timers/group.rb
74
- - lib/timers/timeout.rb
75
75
  - lib/timers/timer.rb
76
76
  - lib/timers/version.rb
77
+ - lib/timers/wait.rb
78
+ - spec/events_spec.rb
79
+ - spec/every_spec.rb
77
80
  - spec/group_spec.rb
81
+ - spec/performance_spec.rb
78
82
  - spec/spec_helper.rb
83
+ - spec/strict_spec.rb
79
84
  - spec/timeout_spec.rb
80
85
  - timers.gemspec
81
86
  homepage: https://github.com/celluloid/timers
@@ -104,6 +109,10 @@ specification_version: 4
104
109
  summary: Schedule procs to run after a certain time, or at periodic intervals, using
105
110
  any API that accepts a timeout
106
111
  test_files:
112
+ - spec/events_spec.rb
113
+ - spec/every_spec.rb
107
114
  - spec/group_spec.rb
115
+ - spec/performance_spec.rb
108
116
  - spec/spec_helper.rb
117
+ - spec/strict_spec.rb
109
118
  - spec/timeout_spec.rb