timers 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1bb812d219dd39a2f06dbead6028d0a472d43358
4
+ data.tar.gz: 069ca7cb58645d3a4bc419166d67842eb482a375
5
+ SHA512:
6
+ metadata.gz: 49534f3d20dab0cff520e02592ed0f4850230454f6fae5165990df3ad71b408b54ffe1c3e362954dc4f0fa9271f5a436f31c6bdb50b40b83d44d3aaac180c1b1
7
+ data.tar.gz: b26cf3a87ac426cc12b336ef16f3081b302da9ebd8d5e631a7ddfc5c7aed64544c4d8e1aec13dd36f1e36771506a5e077b1b0a6dca91763d2a8eb40288047dc5
@@ -0,0 +1 @@
1
+ service-name: travis-pro
@@ -1,10 +1,16 @@
1
1
  rvm:
2
- - 1.8.7
3
2
  - 1.9.3
4
- - ree
3
+ - 2.0.0
5
4
  - ruby-head
6
- - jruby-18mode
7
- - jruby-19mode
8
- # - jruby-head segving o_O
9
- # - rbx-18mode sporadic failures
10
- # - rbx-19mode
5
+ - jruby
6
+ - jruby-head
7
+ - rbx
8
+
9
+ matrix:
10
+ allow_failures:
11
+ - rvm: ruby-head
12
+ - rvm: jruby-head
13
+ - rvm: rbx
14
+
15
+ notifications:
16
+ irc: "irc.freenode.org#celluloid"
data/CHANGES.md CHANGED
@@ -1,3 +1,9 @@
1
+ 2.0.0 (2013-12-30)
2
+ ------------------
3
+ * Switch to Hitimes for high precision monotonic counters
4
+ * Removed Timers#time. Replaced with Timers#current_offset which provides a
5
+ monotonic time counter.
6
+
1
7
  1.1.0
2
8
  -----
3
9
  * Timers#after_milliseconds and #after_ms for waiting in milliseconds
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'coveralls', :require => false
4
+
3
5
  # Specify your gem's dependencies in timers.gemspec
4
6
  gemspec
data/README.md CHANGED
@@ -1,13 +1,16 @@
1
1
  Timers
2
2
  ======
3
- [![Build Status](https://secure.travis-ci.org/tarcieri/timers.png?branch=master)](http://travis-ci.org/tarcieri/timers)
3
+ [![Gem Version](https://badge.fury.io/rb/timers.png)](http://rubygems.org/gems/timers)
4
+ [![Build Status](https://secure.travis-ci.org/celluloid/timers.png?branch=master)](http://travis-ci.org/celluloid/timers)
5
+ [![Code Climate](https://codeclimate.com/github/celluloid/timers.png)](https://codeclimate.com/github/celluloid/timers)
6
+ [![Coverage Status](https://coveralls.io/repos/celluloid/timers/badge.png?branch=master)](https://coveralls.io/r/celluloid/timers)
4
7
 
5
8
  Pure Ruby timer collections. Schedule several procs to fire after configurable
6
9
  delays or at periodic intervals.
7
10
 
8
11
  This gem is especially useful when you are faced with an API that accepts a
9
12
  single timeout but you want to run multiple timers on top of it. An example of
10
- such a library is [nio4r](https://github.com/tarcieri/nio4r), a cross-platform
13
+ such a library is [nio4r](https://github.com/celluloid/nio4r), a cross-platform
11
14
  Ruby library for using system calls like epoll and kqueue.
12
15
 
13
16
  Usage
@@ -66,6 +69,24 @@ loop do
66
69
  end
67
70
  ```
68
71
 
72
+ You can also pause and continue individual timers, or all timers:
73
+
74
+ ```ruby
75
+ paused_timer = timers.every(5) { puts "I was paused" }
76
+
77
+ paused_timer.pause
78
+ 10.times { timers.wait } # will not fire paused timer
79
+
80
+ paused_timer.continue
81
+ 10.times { timers.wait } # will fire timer
82
+
83
+ timers.pause
84
+ 10.times { timers.wait } # will not fire any timers
85
+
86
+ timers.continue
87
+ 10.times { timers.wait } # will fire all timers
88
+ ```
89
+
69
90
  License
70
91
  -------
71
92
 
@@ -1,8 +1,12 @@
1
1
  require 'set'
2
2
  require 'forwardable'
3
3
  require 'timers/version'
4
+ require 'hitimes'
5
+
6
+ # Workaround for thread safety issues in SortedSet initialization
7
+ # See: https://github.com/celluloid/timers/issues/20
8
+ SortedSet.new
4
9
 
5
- # Low precision timers implemented in pure Ruby
6
10
  class Timers
7
11
  include Enumerable
8
12
  extend Forwardable
@@ -10,13 +14,16 @@ class Timers
10
14
 
11
15
  def initialize
12
16
  @timers = SortedSet.new
17
+ @paused_timers = SortedSet.new
18
+ @interval = Hitimes::Interval.new
19
+ @interval.start
13
20
  end
14
21
 
15
22
  # Call the given block after the given interval
16
23
  def after(interval, &block)
17
24
  Timer.new(self, interval, false, &block)
18
25
  end
19
-
26
+
20
27
  # Call the given block after the given interval has expired. +interval+
21
28
  # is measured in milliseconds.
22
29
  #
@@ -40,19 +47,19 @@ class Timers
40
47
  end
41
48
 
42
49
  # Interval to wait until when the next timer will fire
43
- def wait_interval(now = Time.now)
50
+ def wait_interval(offset = self.current_offset)
44
51
  timer = @timers.first
45
52
  return unless timer
46
- interval = timer.time - now
53
+ interval = timer.offset - Float(offset)
47
54
  interval > 0 ? interval : 0
48
55
  end
49
56
 
50
57
  # Fire all timers that are ready
51
- def fire(now = Time.now)
52
- time = now + 0.001 # Fudge 1ms in case of clock imprecision
53
- while (timer = @timers.first) && (time >= timer.time)
58
+ def fire(offset = self.current_offset)
59
+ time = Float(offset) + 0.001 # Fudge 1ms in case of clock imprecision
60
+ while (timer = @timers.first) && (time >= timer.offset)
54
61
  @timers.delete timer
55
- timer.fire(now)
62
+ timer.fire(offset)
56
63
  end
57
64
  end
58
65
 
@@ -61,23 +68,53 @@ class Timers
61
68
  @timers.add(timer)
62
69
  end
63
70
 
71
+ def pause(timer = nil)
72
+ return pause_all if timer.nil?
73
+ raise TypeError, "not a Timers::Timer" unless timer.is_a? Timers::Timer
74
+ @timers.delete timer
75
+ @paused_timers.add timer
76
+ end
77
+
78
+ def pause_all
79
+ @timers.each {|timer| timer.pause}
80
+ end
81
+
82
+ def continue(timer = nil)
83
+ return continue_all if timer.nil?
84
+ raise TypeError, "not a Timers::Timer" unless timer.is_a? Timers::Timer
85
+ @paused_timers.delete timer
86
+ @timers.add timer
87
+ end
88
+
89
+ def continue_all
90
+ @paused_timers.each {|timer| timer.continue}
91
+ end
92
+
93
+ def delay(seconds)
94
+ @timers.each {|timer| timer.delay(seconds)}
95
+ end
96
+
64
97
  alias_method :cancel, :delete
65
98
 
99
+ def current_offset
100
+ @interval.to_f
101
+ end
102
+
66
103
  # An individual timer set to fire a given proc at a given time
67
104
  class Timer
68
105
  include Comparable
69
- attr_reader :interval, :time, :recurring
106
+ attr_reader :interval, :offset, :recurring
70
107
 
71
108
  def initialize(timers, interval, recurring = false, &block)
72
109
  @timers, @interval, @recurring = timers, interval, recurring
73
- @block = block
74
- @time = nil
110
+ @block = block
111
+ @offset = nil
75
112
 
76
113
  reset
77
114
  end
78
115
 
79
116
  def <=>(other)
80
- @time <=> other.time
117
+ @offset <=> other.offset
81
118
  end
82
119
 
83
120
  # Cancel this timer
@@ -85,30 +122,47 @@ class Timers
85
122
  @timers.cancel self
86
123
  end
87
124
 
125
+ # Extend this timer
126
+ def delay(seconds)
127
+ @timers.delete self
128
+ @offset += seconds
129
+ @timers.add self
130
+ end
131
+
88
132
  # Reset this timer
89
- def reset(now = Time.now)
133
+ def reset(offset = @timers.current_offset)
90
134
  @timers.cancel self if @time
91
- @time = now + @interval
135
+ @offset = Float(offset) + @interval
92
136
  @timers.add self
93
137
  end
94
138
 
95
139
  # Fire the block
96
- def fire(now = Time.now)
97
- reset(now) if recurring
140
+ def fire(offset = @timers.current_offset)
141
+ reset(offset) if recurring
98
142
  @block.call
99
143
  end
100
144
  alias_method :call, :fire
101
145
 
146
+ # Pause this timer
147
+ def pause
148
+ @timers.pause self
149
+ end
150
+
151
+ # Continue this timer
152
+ def continue
153
+ @timers.continue self
154
+ end
155
+
102
156
  # Inspect a timer
103
157
  def inspect
104
158
  str = "#<Timers::Timer:#{object_id.to_s(16)} "
105
- now = Time.now
159
+ offset = @timers.current_offset
106
160
 
107
- if @time
108
- if @time >= now
109
- str << "fires in #{@time - now} seconds"
161
+ if @offset
162
+ if @offset >= offset
163
+ str << "fires in #{@offset - offset} seconds"
110
164
  else
111
- str << "fired #{now - @time} seconds ago"
165
+ str << "fired #{offset - @offset} seconds ago"
112
166
  end
113
167
 
114
168
  str << ", recurs every #{interval}" if recurring
@@ -1,3 +1,3 @@
1
1
  class Timers
2
- VERSION = "1.1.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -1,2 +1,4 @@
1
1
  require 'bundler/setup'
2
- require 'timers'
2
+ require 'timers'
3
+ require 'coveralls'
4
+ Coveralls.wear!
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Timers do
4
- # Level of accuracy enforced by most tests (50ms)
4
+ # Level of accuracy enforced by tests (50ms)
5
5
  Q = 0.05
6
6
 
7
7
  it "sleeps until the next timer" do
@@ -12,8 +12,8 @@ describe Timers do
12
12
  subject.after(interval) { fired = true }
13
13
  subject.wait
14
14
 
15
- fired.should be_true
16
- (Time.now - started_at).should be_within(Q).of interval
15
+ expect(fired).to be_true
16
+ expect(Time.now - started_at).to be_within(Q).of interval
17
17
  end
18
18
 
19
19
  it "fires instantly when next timer is in the past" do
@@ -22,17 +22,17 @@ describe Timers do
22
22
  sleep(Q * 2)
23
23
  subject.wait
24
24
 
25
- fired.should be_true
25
+ expect(fired).to be_true
26
26
  end
27
27
 
28
28
  it "calculates the interval until the next timer should fire" do
29
29
  interval = 0.1
30
30
 
31
31
  subject.after(interval)
32
- subject.wait_interval.should be_within(Q).of interval
32
+ expect(subject.wait_interval).to be_within(Q).of interval
33
33
 
34
34
  sleep(interval)
35
- subject.wait_interval.should be(0)
35
+ expect(subject.wait_interval).to be(0)
36
36
  end
37
37
 
38
38
  it "fires timers in the correct order" do
@@ -45,7 +45,13 @@ describe Timers do
45
45
  sleep Q * 4
46
46
  subject.fire
47
47
 
48
- result.should == [:one, :two, :three]
48
+ expect(result).to eq [:one, :two, :three]
49
+ end
50
+
51
+ it "raises TypeError if given an invalid time" do
52
+ expect do
53
+ subject.after(nil) { nil }
54
+ end.to raise_exception(TypeError)
49
55
  end
50
56
 
51
57
  describe "recurring timers" do
@@ -56,14 +62,14 @@ describe Timers do
56
62
 
57
63
  sleep Q * 3
58
64
  subject.fire
59
- result.should == [:foo]
65
+ expect(result).to eq [:foo]
60
66
 
61
67
  sleep Q * 5
62
68
  subject.fire
63
- result.should == [:foo, :foo]
69
+ expect(result).to eq [:foo, :foo]
64
70
  end
65
71
  end
66
-
72
+
67
73
  describe "millisecond timers" do
68
74
  it "calculates the proper interval to wait until firing" do
69
75
  interval_ms = 25
@@ -71,7 +77,125 @@ describe Timers do
71
77
  subject.after_milliseconds(interval_ms)
72
78
  expected_elapse = subject.wait_interval
73
79
 
74
- subject.wait_interval.should be_within(Q).of (interval_ms / 1000.0)
80
+ expect(subject.wait_interval).to be_within(Q).of(interval_ms / 1000.0)
81
+ end
82
+ end
83
+
84
+ describe "pause and continue timers" do
85
+ before(:each) do
86
+ @interval = Q * 2
87
+ started_at = Time.now
88
+
89
+ @fired = false
90
+ @timer = subject.every(@interval) { @fired = true }
91
+ @fired2 = false
92
+ @timer2 = subject.every(@interval) { @fired2 = true }
93
+ end
94
+
95
+ it "does not fire when paused" do
96
+ @timer.pause
97
+ subject.wait
98
+ expect(@fired).to be_false
99
+ end
100
+
101
+ it "fires when continued after pause" do
102
+ @timer.pause
103
+ subject.wait
104
+ @timer.continue
105
+ subject.wait
106
+ expect(@fired).to be_true
107
+ end
108
+
109
+ it "can pause all timers at once" do
110
+ subject.pause
111
+ subject.wait
112
+ expect(@fired).to be_false
113
+ expect(@fired2).to be_false
114
+ end
115
+
116
+ it "can continue all timers at once" do
117
+ subject.pause
118
+ subject.wait
119
+ subject.continue
120
+ subject.wait
121
+ expect(@fired).to be_true
122
+ expect(@fired2).to be_true
123
+ end
124
+
125
+ it "can fire the timer directly" do
126
+ fired = false
127
+ timer = subject.after( Q * 1 ) { fired = true }
128
+ timer.pause
129
+ subject.wait
130
+ expect(fired).not_to be_true
131
+ timer.continue
132
+ expect(fired).not_to be_true
133
+ timer.fire
134
+ expect(fired).to be_true
135
+ end
136
+
137
+ end
138
+
139
+ describe "delay timer" do
140
+ it "adds appropriate amount of time to timer" do
141
+ timer = subject.after(10)
142
+ timer.delay(5)
143
+ expect(timer.offset - subject.current_offset).to be_within(Q).of(15)
144
+ end
145
+ end
146
+
147
+ describe "delay timer collection" do
148
+ it "delay on set adds appropriate amount of time to all timers" do
149
+ timer = subject.after(10)
150
+ timer2 = subject.after(20)
151
+ subject.delay(5)
152
+ expect(timer.offset - subject.current_offset).to be_within(Q).of(15)
153
+ expect(timer2.offset - subject.current_offset).to be_within(Q).of(25)
154
+ end
155
+ end
156
+
157
+ describe "on delaying a timer" do
158
+ it "fires timers in the correct order" do
159
+ result = []
160
+
161
+ second = subject.after(Q * 2) { result << :two }
162
+ third = subject.after(Q * 3) { result << :three }
163
+ first = subject.after(Q * 1) { result << :one }
164
+ first.delay(Q * 3)
165
+
166
+ sleep Q * 5
167
+ subject.fire
168
+
169
+ expect(result).to eq [:two, :three, :one]
170
+ end
171
+ end
172
+
173
+ describe "Timer inspection" do
174
+ it "before firing" do
175
+ fired = false
176
+ timer = subject.after(Q * 5) { fired = true }
177
+ timer.pause
178
+ expect(fired).not_to be_true
179
+ expect(timer.inspect).to match(/\A#<Timers::Timer:[\da-f]+ fires in [-\.\de]+ seconds>\Z/)
180
+ end
181
+
182
+ it "after firing" do
183
+ fired = false
184
+ timer = subject.after(Q) { fired = true }
185
+
186
+ subject.wait
187
+
188
+ expect(fired).to be_true
189
+ expect(timer.inspect).to match(/\A#<Timers::Timer:[\da-f]+ fired [-\.\de]+ seconds ago>\Z/)
190
+ end
191
+
192
+ it "recurring firing" do
193
+ result = []
194
+ timer = subject.every(Q) { result << :foo }
195
+
196
+ subject.wait
197
+ expect(result).not_to be_empty
198
+ expect(timer.inspect).to match(/\A#<Timers::Timer:[\da-f]+ fires in [-\.\de]+ seconds, recurs every #{sprintf("%0.2f", Q)}>\Z/)
75
199
  end
76
200
  end
77
201
  end
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
6
6
  gem.email = ["tony.arcieri@gmail.com"]
7
7
  gem.description = "Pure Ruby one-shot and periodic timers"
8
8
  gem.summary = "Schedule procs to run after a certain time, or at periodic intervals, using any API that accepts a timeout"
9
- gem.homepage = "https://github.com/tarcieri/timers"
9
+ gem.homepage = "https://github.com/celluloid/timers"
10
10
 
11
11
  gem.files = `git ls-files`.split($\)
12
12
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -14,6 +14,9 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "timers"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Timers::VERSION
17
+ gem.licenses = ['MIT']
18
+
19
+ gem.add_runtime_dependency 'hitimes'
17
20
 
18
21
  gem.add_development_dependency 'rake'
19
22
  gem.add_development_dependency 'rspec'
metadata CHANGED
@@ -1,46 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timers
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
5
- prerelease:
4
+ version: 2.0.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Tony Arcieri
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-01-19 00:00:00.000000000 Z
11
+ date: 2013-12-30 00:00:00.000000000 Z
13
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hitimes
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
14
27
  - !ruby/object:Gem::Dependency
15
28
  name: rake
16
29
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
30
  requirements:
19
- - - ! '>='
31
+ - - ">="
20
32
  - !ruby/object:Gem::Version
21
33
  version: '0'
22
34
  type: :development
23
35
  prerelease: false
24
36
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
37
  requirements:
27
- - - ! '>='
38
+ - - ">="
28
39
  - !ruby/object:Gem::Version
29
40
  version: '0'
30
41
  - !ruby/object:Gem::Dependency
31
42
  name: rspec
32
43
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
44
  requirements:
35
- - - ! '>='
45
+ - - ">="
36
46
  - !ruby/object:Gem::Version
37
47
  version: '0'
38
48
  type: :development
39
49
  prerelease: false
40
50
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
51
  requirements:
43
- - - ! '>='
52
+ - - ">="
44
53
  - !ruby/object:Gem::Version
45
54
  version: '0'
46
55
  description: Pure Ruby one-shot and periodic timers
@@ -50,9 +59,10 @@ executables: []
50
59
  extensions: []
51
60
  extra_rdoc_files: []
52
61
  files:
53
- - .gitignore
54
- - .rspec
55
- - .travis.yml
62
+ - ".coveralls.yml"
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".travis.yml"
56
66
  - CHANGES.md
57
67
  - Gemfile
58
68
  - LICENSE
@@ -63,35 +73,29 @@ files:
63
73
  - spec/spec_helper.rb
64
74
  - spec/timers_spec.rb
65
75
  - timers.gemspec
66
- homepage: https://github.com/tarcieri/timers
67
- licenses: []
76
+ homepage: https://github.com/celluloid/timers
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
68
80
  post_install_message:
69
81
  rdoc_options: []
70
82
  require_paths:
71
83
  - lib
72
84
  required_ruby_version: !ruby/object:Gem::Requirement
73
- none: false
74
85
  requirements:
75
- - - ! '>='
86
+ - - ">="
76
87
  - !ruby/object:Gem::Version
77
88
  version: '0'
78
- segments:
79
- - 0
80
- hash: 1264960547307952819
81
89
  required_rubygems_version: !ruby/object:Gem::Requirement
82
- none: false
83
90
  requirements:
84
- - - ! '>='
91
+ - - ">="
85
92
  - !ruby/object:Gem::Version
86
93
  version: '0'
87
- segments:
88
- - 0
89
- hash: 1264960547307952819
90
94
  requirements: []
91
95
  rubyforge_project:
92
- rubygems_version: 1.8.23
96
+ rubygems_version: 2.2.0
93
97
  signing_key:
94
- specification_version: 3
98
+ specification_version: 4
95
99
  summary: Schedule procs to run after a certain time, or at periodic intervals, using
96
100
  any API that accepts a timeout
97
101
  test_files: