timers 0.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.
- data/.gitignore +17 -0
- data/.rspec +4 -0
- data/.travis.yml +12 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +73 -0
- data/Rakefile +7 -0
- data/lib/timers.rb +115 -0
- data/lib/timers/version.rb +3 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/timers_spec.rb +54 -0
- data/timers.gemspec +20 -0
- metadata +83 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Tony Arcieri
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
Timers
|
2
|
+
======
|
3
|
+
[](http://travis-ci.org/tarcieri/timers)
|
4
|
+
|
5
|
+
Pure Ruby timer collections. Schedule several procs to fire after configurable
|
6
|
+
delays or at periodic intervals.
|
7
|
+
|
8
|
+
This gem is especially useful when you are faced with an API that accepts a
|
9
|
+
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
|
11
|
+
Ruby library for using system calls like epoll and kqueue.
|
12
|
+
|
13
|
+
Usage
|
14
|
+
-----
|
15
|
+
|
16
|
+
Create a new timer group with `Timers.new`:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
require 'timers'
|
20
|
+
|
21
|
+
timers = Timers.new
|
22
|
+
```
|
23
|
+
|
24
|
+
Schedule a proc to run after 5 seconds with `Timers#after`:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
five_second_timer = timers.after(5) { puts "Take five" }
|
28
|
+
```
|
29
|
+
|
30
|
+
The `five_second_timer` variable is now bound to a Timers::Timer object. To
|
31
|
+
cancel a timer, use `Timers::Timer#cancel`
|
32
|
+
|
33
|
+
Once you've scheduled a timer, you can wait until the next timer fires with `Timers#wait`:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# Waits 5 seconds
|
37
|
+
timers.wait
|
38
|
+
|
39
|
+
# The script will now print "Take five"
|
40
|
+
```
|
41
|
+
|
42
|
+
You can schedule a block to run periodically with `Timers#every`:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
every_five_seconds = timers.every(5) { puts "Another 5 seconds" }
|
46
|
+
|
47
|
+
loop { timers.wait }
|
48
|
+
```
|
49
|
+
|
50
|
+
If you'd like another method to do the waiting for you, e.g. `Kernel.select`,
|
51
|
+
you can use `Timers#wait_interval` to obtain the amount of time to wait. When
|
52
|
+
a timeout is encountered, you can fire all pending timers with `Timers#fire`:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
loop do
|
56
|
+
interval = timers.wait_interval
|
57
|
+
ready_readers, ready_writers = select readers, writers, nil, interval
|
58
|
+
|
59
|
+
if ready_readers || ready_writers
|
60
|
+
# Handle IO
|
61
|
+
...
|
62
|
+
else
|
63
|
+
# Timeout!
|
64
|
+
timers.fire
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
License
|
70
|
+
-------
|
71
|
+
|
72
|
+
Copyright (c) 2012 Tony Arcieri. Distributed under the MIT License. See
|
73
|
+
LICENSE for further details.
|
data/Rakefile
ADDED
data/lib/timers.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require "timers/version"
|
2
|
+
|
3
|
+
# Low precision timers implemented in pure Ruby
|
4
|
+
class Timers
|
5
|
+
def initialize
|
6
|
+
@timers = []
|
7
|
+
end
|
8
|
+
|
9
|
+
# Call the given block after the given interval
|
10
|
+
def after(interval, &block)
|
11
|
+
Timer.new(self, interval, false, block)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Call the given block periodically at the given interval
|
15
|
+
def every(interval, &block)
|
16
|
+
Timer.new(self, interval, true, block)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Wait for the next timer and fire it
|
20
|
+
def wait
|
21
|
+
return if @timers.empty?
|
22
|
+
|
23
|
+
interval = wait_interval
|
24
|
+
sleep interval if interval >= Timer::QUANTUM
|
25
|
+
fire
|
26
|
+
end
|
27
|
+
|
28
|
+
# Interval to wait until when the next timer will fire
|
29
|
+
def wait_interval
|
30
|
+
@timers.first.time - Time.now unless empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Fire all timers that are ready
|
34
|
+
def fire
|
35
|
+
return if @timers.empty?
|
36
|
+
|
37
|
+
time = Time.now + Timer::QUANTUM
|
38
|
+
while not empty? and time > @timers.first.time
|
39
|
+
timer = @timers.shift
|
40
|
+
timer.call
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Insert a timer into the active timers
|
45
|
+
def insert(timer)
|
46
|
+
@timers.insert(index(timer), timer)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Remove a given timer from the set we're monitoring
|
50
|
+
def cancel(timer)
|
51
|
+
@timers.delete timer
|
52
|
+
end
|
53
|
+
|
54
|
+
# Are there any timers pending?
|
55
|
+
def empty?
|
56
|
+
@timers.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Index where a timer would be located in the sorted timers array
|
60
|
+
def index(timer)
|
61
|
+
l, r = 0, @timers.size - 1
|
62
|
+
|
63
|
+
while l <= r
|
64
|
+
m = (r + l) / 2
|
65
|
+
if timer < @timers.at(m)
|
66
|
+
r = m - 1
|
67
|
+
else
|
68
|
+
l = m + 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
l
|
72
|
+
end
|
73
|
+
|
74
|
+
# An individual timer set to fire a given proc at a given time
|
75
|
+
class Timer
|
76
|
+
include Comparable
|
77
|
+
|
78
|
+
# The timer system is guaranteed (at least by the specs) to be this precise
|
79
|
+
# during normal operation. Long blocking calls within actors will delay the
|
80
|
+
# firing of timers
|
81
|
+
QUANTUM = 0.02
|
82
|
+
|
83
|
+
attr_reader :interval, :time, :recurring
|
84
|
+
|
85
|
+
def initialize(timers, interval, recurring, block)
|
86
|
+
@timers, @interval, @recurring = timers, interval, recurring
|
87
|
+
@block = block
|
88
|
+
|
89
|
+
reset
|
90
|
+
end
|
91
|
+
|
92
|
+
def <=>(other)
|
93
|
+
@time <=> other.time
|
94
|
+
end
|
95
|
+
|
96
|
+
# Cancel this timer
|
97
|
+
def cancel
|
98
|
+
@timers.cancel self
|
99
|
+
end
|
100
|
+
|
101
|
+
# Reset this timer
|
102
|
+
def reset
|
103
|
+
@timers.cancel self if defined?(@time)
|
104
|
+
@time = Time.now + @interval
|
105
|
+
@timers.insert self
|
106
|
+
end
|
107
|
+
|
108
|
+
# Fire the block
|
109
|
+
def fire
|
110
|
+
reset if recurring
|
111
|
+
@block.call
|
112
|
+
end
|
113
|
+
alias_method :call, :fire
|
114
|
+
end
|
115
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/timers_spec.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Timers do
|
4
|
+
Q = Timers::Timer::QUANTUM
|
5
|
+
|
6
|
+
it "sleeps until the next timer" do
|
7
|
+
interval = 0.1
|
8
|
+
started_at = Time.now
|
9
|
+
|
10
|
+
fired = false
|
11
|
+
subject.after(interval) { fired = true }
|
12
|
+
subject.wait
|
13
|
+
|
14
|
+
fired.should be_true
|
15
|
+
(Time.now - started_at).should be_within(Q).of interval
|
16
|
+
end
|
17
|
+
|
18
|
+
it "it calculates the interval until the next timer should fire" do
|
19
|
+
interval = 0.1
|
20
|
+
|
21
|
+
subject.after(interval)
|
22
|
+
subject.wait_interval.should be_within(Q).of interval
|
23
|
+
end
|
24
|
+
|
25
|
+
it "fires timers in the correct order" do
|
26
|
+
result = []
|
27
|
+
|
28
|
+
subject.after(Q * 2) { result << :two }
|
29
|
+
subject.after(Q * 3) { result << :three }
|
30
|
+
subject.after(Q * 1) { result << :one }
|
31
|
+
|
32
|
+
sleep Q * 4
|
33
|
+
subject.fire
|
34
|
+
|
35
|
+
result.should == [:one, :two, :three]
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "recurring timers" do
|
39
|
+
it "should continue to fire the timers at each interval" do
|
40
|
+
result = []
|
41
|
+
|
42
|
+
subject.every(Q * 3) { result << :foo }
|
43
|
+
|
44
|
+
sleep Q * 3
|
45
|
+
subject.fire
|
46
|
+
result.should == [:foo]
|
47
|
+
|
48
|
+
sleep Q * 3
|
49
|
+
subject.fire
|
50
|
+
subject.fire
|
51
|
+
result.should == [:foo, :foo]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/timers.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/timers/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Tony Arcieri"]
|
6
|
+
gem.email = ["tony.arcieri@gmail.com"]
|
7
|
+
gem.description = "Pure Ruby one-shot and periodic timers"
|
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"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "timers"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Timers::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rake'
|
19
|
+
gem.add_development_dependency 'rspec'
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: timers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tony Arcieri
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70212434293540 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70212434293540
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70212434292840 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70212434292840
|
36
|
+
description: Pure Ruby one-shot and periodic timers
|
37
|
+
email:
|
38
|
+
- tony.arcieri@gmail.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- .rspec
|
45
|
+
- .travis.yml
|
46
|
+
- Gemfile
|
47
|
+
- LICENSE
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- lib/timers.rb
|
51
|
+
- lib/timers/version.rb
|
52
|
+
- spec/spec_helper.rb
|
53
|
+
- spec/timers_spec.rb
|
54
|
+
- timers.gemspec
|
55
|
+
homepage: https://github.com/tarcieri/timers
|
56
|
+
licenses: []
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.8.10
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Schedule procs to run after a certain time, or at periodic intervals, using
|
79
|
+
any API that accepts a timeout
|
80
|
+
test_files:
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
- spec/timers_spec.rb
|
83
|
+
has_rdoc:
|