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 +4 -4
- data/CHANGES.md +10 -2
- data/Gemfile +4 -0
- data/README.md +11 -11
- data/lib/timers.rb +1 -6
- data/lib/timers/events.rb +115 -0
- data/lib/timers/group.rb +73 -61
- data/lib/timers/timer.rb +62 -31
- data/lib/timers/version.rb +1 -1
- data/lib/timers/{timeout.rb → wait.rb} +13 -1
- data/spec/events_spec.rb +57 -0
- data/spec/every_spec.rb +19 -0
- data/spec/group_spec.rb +48 -16
- data/spec/performance_spec.rb +83 -0
- data/spec/strict_spec.rb +37 -0
- data/spec/timeout_spec.rb +4 -4
- metadata +12 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa56588795590f6b0772c42830b2969d4c18c31b
|
4
|
+
data.tar.gz: 203a266076597db814652c1b1e8d25fdca6808b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf717b2718394941dc03bd9bd4184a790116fff7e936b07227fe64cec0d7801ff84903ba23efd5332751919d3a9acbbff95c3a1b53f8c150cd67a94c78531763
|
7
|
+
data.tar.gz: 26d41eddf5f924ac26860a09daba9af79203f32f0f7cefc93cafc5a46ffd95ad959f0281c6dbe619ca3ad8087c63e16c87ef1e860d297f6c30e98da3562c5be0
|
data/CHANGES.md
CHANGED
@@ -1,8 +1,16 @@
|
|
1
|
-
|
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 (
|
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
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
|
-
|
9
|
-
|
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.
|
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.
|
86
|
+
timers.resume
|
87
87
|
10.times { timers.wait } # will fire all timers
|
88
88
|
```
|
89
89
|
|
data/lib/timers.rb
CHANGED
@@ -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/
|
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
|
data/lib/timers/group.rb
CHANGED
@@ -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
|
-
|
12
|
-
|
12
|
+
|
13
|
+
extend Forwardable
|
14
|
+
def_delegators :@timers, :each, :empty?
|
13
15
|
|
14
16
|
def initialize
|
15
|
-
@
|
16
|
-
|
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
|
-
#
|
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
|
27
|
-
#
|
28
|
-
|
29
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
70
|
-
|
71
|
-
@
|
78
|
+
# Fire all timers that are ready.
|
79
|
+
def fire(offset = self.current_offset)
|
80
|
+
@events.fire(offset)
|
72
81
|
end
|
73
82
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
83
|
+
# Pause all timers.
|
84
|
+
def pause
|
85
|
+
@timers.dup.each do |timer|
|
86
|
+
timer.pause
|
87
|
+
end
|
79
88
|
end
|
80
89
|
|
81
|
-
|
82
|
-
|
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
|
-
|
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
|
101
|
+
@timers.each do |timer|
|
102
|
+
timer.delay(seconds)
|
103
|
+
end
|
98
104
|
end
|
99
105
|
|
100
|
-
|
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
|
data/lib/timers/timer.rb
CHANGED
@@ -5,68 +5,99 @@ module Timers
|
|
5
5
|
include Comparable
|
6
6
|
attr_reader :interval, :offset, :recurring
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
|
11
|
-
@
|
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
|
-
|
24
|
+
def paused?
|
25
|
+
@group.paused_timers.include? self
|
14
26
|
end
|
15
27
|
|
16
|
-
def
|
17
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
@
|
51
|
+
@handle.cancel! if @handle
|
52
|
+
|
28
53
|
@offset += seconds
|
29
|
-
|
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 = @
|
34
|
-
@
|
69
|
+
def reset(offset = @group.current_offset)
|
70
|
+
@handle.cancel! if @handle
|
71
|
+
|
35
72
|
@offset = Float(offset) + @interval
|
36
|
-
|
73
|
+
|
74
|
+
@handle = @group.events.schedule(@offset, self)
|
37
75
|
end
|
38
76
|
|
39
77
|
# Fire the block
|
40
|
-
def fire(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
|
-
|
52
|
-
def pause
|
53
|
-
@timers.pause self
|
88
|
+
@block.call(offset)
|
54
89
|
end
|
55
90
|
|
56
|
-
|
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 - @
|
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
|
data/lib/timers/version.rb
CHANGED
@@ -3,7 +3,19 @@ require 'hitimes'
|
|
3
3
|
|
4
4
|
module Timers
|
5
5
|
# An exclusive, monotonic timeout class.
|
6
|
-
class
|
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
|
data/spec/events_spec.rb
ADDED
@@ -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
|
data/spec/every_spec.rb
ADDED
@@ -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
|
data/spec/group_spec.rb
CHANGED
@@ -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
|
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
|
-
|
72
|
-
|
73
|
-
interval_ms = 25
|
100
|
+
it "calculates the proper interval to wait until firing" do
|
101
|
+
interval_ms = 25
|
74
102
|
|
75
|
-
|
76
|
-
expected_elapse = subject.wait_interval
|
103
|
+
subject.after(interval_ms / 1000.0)
|
77
104
|
|
78
|
-
|
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.
|
113
|
+
@timer = subject.after(@interval) { @fired = true }
|
89
114
|
@fired2 = false
|
90
|
-
@timer2 = subject.
|
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.
|
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.
|
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.
|
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
|
-
|
160
|
-
|
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
|
data/spec/strict_spec.rb
ADDED
@@ -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
|
data/spec/timeout_spec.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
|
2
2
|
require 'spec_helper'
|
3
|
-
require 'timers/
|
3
|
+
require 'timers/wait'
|
4
4
|
|
5
|
-
RSpec.describe Timers::
|
5
|
+
RSpec.describe Timers::Wait do
|
6
6
|
it "repeats until timeout expired" do
|
7
|
-
timeout = Timers::
|
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::
|
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:
|
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-
|
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
|