timers 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: