em-throttled_queue 1.0.2
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/.yardopts +2 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +22 -0
- data/LICENSE +19 -0
- data/README.markdown +42 -0
- data/Rakefile +30 -0
- data/em-throttled_queue.gemspec +65 -0
- data/lib/em/throttled_queue.rb +76 -0
- data/lib/em/throttled_queue/version.rb +7 -0
- data/lib/eventmachine/throttled_queue.rb +1 -0
- data/spec/throttled_queue.rb +75 -0
- metadata +121 -0
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
eventmachine (0.12.10)
|
5
|
+
git (1.2.5)
|
6
|
+
jeweler (1.5.2)
|
7
|
+
bundler (~> 1.0.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
rake (0.8.7)
|
11
|
+
rdiscount (1.6.8)
|
12
|
+
yard (0.6.4)
|
13
|
+
|
14
|
+
PLATFORMS
|
15
|
+
ruby
|
16
|
+
|
17
|
+
DEPENDENCIES
|
18
|
+
eventmachine
|
19
|
+
jeweler (~> 1.5)
|
20
|
+
rake
|
21
|
+
rdiscount
|
22
|
+
yard
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Kim Burgestrand
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# EM-Throttled_Queue is a throttled queue (surprise, surprise!)
|
2
|
+
[ThrottledQueue](http://rdoc.info/github/Burgestrand/em-throttled_queue/master/EventMachine/ThrottledQueue) is just like an [EM::Queue](http://rdoc.info/github/eventmachine/eventmachine/master/EventMachine/Queue), but will pop items off itself at a pace specified by you!
|
3
|
+
|
4
|
+
Version v1.0.0 and v1.0.1 has an unintentional flaw (result of coding while tired) and should not be used. v1.0.2 is coming as soon as issue #2 is resolved. They have been yanked from rubygems.
|
5
|
+
|
6
|
+
Example
|
7
|
+
-------
|
8
|
+
|
9
|
+
# Example code that will pop off 2 items in total within a period of
|
10
|
+
# one second. The other items are not popped because of throttle.
|
11
|
+
EM::run do
|
12
|
+
# Create a queue that will pop off maximum of 2 items per second,
|
13
|
+
# with a refill-limit of 1 second.
|
14
|
+
queue = EM::ThrottledQueue.new(2)
|
15
|
+
|
16
|
+
queue.push 1 # you can push one item at a time
|
17
|
+
queue.push 2, 3, 4, 5, 6 # or several at once
|
18
|
+
|
19
|
+
5.times { queue.pop(&EM::Callback(Object, :puts)) }
|
20
|
+
|
21
|
+
EM::add_timer(1) { EM::stop }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Output:
|
25
|
+
# 1
|
26
|
+
# 2
|
27
|
+
|
28
|
+
What problem does EM-Throttled_Queue solve?
|
29
|
+
-------------------------------------------
|
30
|
+
At [radiofy](http://radiofy.se/) we consume a lot of external services. As we are rebuilding our internal structures we will be using EventMachine for the consumption of said services. Some of them have an applied rate-limit that will punish you if you don’t restrain how many queries you execute.
|
31
|
+
|
32
|
+
As a result, we need to call our blocks of code within the allowed rate-limit. For example, the [Spotify Metadata API](http://developer.spotify.com/en/metadata-api/overview/) allow a maximum of 10 requests per second, and if exceeded will force you to wait 10 seconds (essentially losing 90 reqs/10 seconds). EM::ThrottledQueue will allow us to easily limit how many calls we make per second to their API.
|
33
|
+
|
34
|
+
How do I contribute?
|
35
|
+
--------------------
|
36
|
+
Fork, add tests (important!), add your code and send a pull request. If you wish to report an issue, please use the GitHub issue tracker. I can also be contacted by mail (visible on [my GitHub user page](http://github.com/Burgestrand)).
|
37
|
+
|
38
|
+
As far as development dependencies goes they are all specified in the Gemfile. Once you have checked out the code, a mere `bundle install` should fetch them all for you! \o/
|
39
|
+
|
40
|
+
Why don’t you support 1.8.7?
|
41
|
+
----------------------------
|
42
|
+
There currently is no reason for me to do so. If you believe there is, file an issue.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
require 'jeweler'
|
4
|
+
require './lib/em/throttled_queue/version'
|
5
|
+
Jeweler::Tasks.new do |gem|
|
6
|
+
gem.name = "em-throttled_queue"
|
7
|
+
gem.homepage = "http://github.com/Burgestrand/em-throttled_queue"
|
8
|
+
|
9
|
+
gem.license = "X11 License"
|
10
|
+
|
11
|
+
gem.summary = "A rate-limited Queue for EventMachine"
|
12
|
+
gem.authors = ["Kim Burgestrand"]
|
13
|
+
gem.email = "kim@burgestrand.se"
|
14
|
+
|
15
|
+
gem.version = EventMachine::ThrottledQueue::VERSION
|
16
|
+
gem.required_ruby_version = '~> 1.9'
|
17
|
+
end
|
18
|
+
Jeweler::RubygemsDotOrgTasks.new
|
19
|
+
|
20
|
+
require 'yard'
|
21
|
+
require 'yard/rake/yardoc_task'
|
22
|
+
YARD::Rake::YardocTask.new
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
Rake::TestTask.new(:spec) do |t|
|
26
|
+
t.pattern = 'spec/*.rb'
|
27
|
+
end
|
28
|
+
|
29
|
+
task :release => ['gemspec:release', 'git:release', 'gemcutter:release']
|
30
|
+
task :default => :spec
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{em-throttled_queue}
|
8
|
+
s.version = "1.0.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Kim Burgestrand"]
|
12
|
+
s.date = %q{2011-02-13}
|
13
|
+
s.email = %q{kim@burgestrand.se}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.markdown"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".yardopts",
|
20
|
+
"Gemfile",
|
21
|
+
"Gemfile.lock",
|
22
|
+
"LICENSE",
|
23
|
+
"README.markdown",
|
24
|
+
"Rakefile",
|
25
|
+
"em-throttled_queue.gemspec",
|
26
|
+
"lib/em/throttled_queue.rb",
|
27
|
+
"lib/em/throttled_queue/version.rb",
|
28
|
+
"lib/eventmachine/throttled_queue.rb",
|
29
|
+
"spec/throttled_queue.rb"
|
30
|
+
]
|
31
|
+
s.homepage = %q{http://github.com/Burgestrand/em-throttled_queue}
|
32
|
+
s.licenses = ["X11 License"]
|
33
|
+
s.require_paths = ["lib"]
|
34
|
+
s.required_ruby_version = Gem::Requirement.new("~> 1.9")
|
35
|
+
s.rubygems_version = %q{1.5.0}
|
36
|
+
s.summary = %q{A rate-limited Queue for EventMachine}
|
37
|
+
s.test_files = [
|
38
|
+
"spec/throttled_queue.rb"
|
39
|
+
]
|
40
|
+
|
41
|
+
if s.respond_to? :specification_version then
|
42
|
+
s.specification_version = 3
|
43
|
+
|
44
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
45
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
|
46
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5"])
|
47
|
+
s.add_development_dependency(%q<rake>, [">= 0"])
|
48
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
49
|
+
s.add_development_dependency(%q<rdiscount>, [">= 0"])
|
50
|
+
else
|
51
|
+
s.add_dependency(%q<eventmachine>, [">= 0"])
|
52
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5"])
|
53
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
54
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
55
|
+
s.add_dependency(%q<rdiscount>, [">= 0"])
|
56
|
+
end
|
57
|
+
else
|
58
|
+
s.add_dependency(%q<eventmachine>, [">= 0"])
|
59
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5"])
|
60
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
61
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
62
|
+
s.add_dependency(%q<rdiscount>, [">= 0"])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'em/throttled_queue/version'
|
4
|
+
|
5
|
+
# @see https://github.com/eventmachine/eventmachine
|
6
|
+
module EventMachine
|
7
|
+
# An EM::Queue with a rate-limit applied to it. This is useful if you
|
8
|
+
# wish to consume items at a limited pace.
|
9
|
+
#
|
10
|
+
# @todo Add a maximum size-limit on amount of items in queue.
|
11
|
+
class ThrottledQueue < Queue
|
12
|
+
# Create a new rate-limited queue.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
#
|
16
|
+
# EM::ThrottledQueue.new(10)
|
17
|
+
# # => allows maximum 10 deqs every second
|
18
|
+
#
|
19
|
+
# @param [Fixnum] maximum of dequeues every second
|
20
|
+
def initialize(limit)
|
21
|
+
@credits = @limit = limit.to_i
|
22
|
+
@ticker = EM::add_periodic_timer(1) do
|
23
|
+
@credits = @limit
|
24
|
+
scheduled_dequeue
|
25
|
+
end
|
26
|
+
|
27
|
+
super()
|
28
|
+
end
|
29
|
+
|
30
|
+
# Pop an item off the queue as soon as conditions allow, passing it
|
31
|
+
# to the given block.
|
32
|
+
#
|
33
|
+
# @note The given block is executed within the reactor thread.
|
34
|
+
# @yield [item] callback to execute when item is popped off
|
35
|
+
# @yieldparam item an item from the queue
|
36
|
+
# @return (see #interact)
|
37
|
+
# @see http://rdoc.info/github/eventmachine/eventmachine/master/EventMachine.Callback
|
38
|
+
def pop(&block)
|
39
|
+
interact { @popq.push(block) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Push items onto the queue (from within the reactor thread) asap.
|
43
|
+
#
|
44
|
+
# @note This call is thread-safe.
|
45
|
+
# @param [item, …] items items to be pushed onto the queue
|
46
|
+
# @return (see #interact)
|
47
|
+
def push(*items)
|
48
|
+
interact { @items.push(*items) }
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
# Helper method to schedule queue interaction and then _dequeue.
|
53
|
+
#
|
54
|
+
# @yield block to call within EM::schedule
|
55
|
+
# @return [ThrottledQueue]
|
56
|
+
def interact(&block)
|
57
|
+
EM::schedule do
|
58
|
+
block.call
|
59
|
+
# next_tick to avoid locking up caller in a dequeuing loop
|
60
|
+
EM::next_tick method(:scheduled_dequeue)
|
61
|
+
end
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Dequeue as many items as ossible.
|
66
|
+
#
|
67
|
+
# @return [ThrottledQueue]
|
68
|
+
def scheduled_dequeue
|
69
|
+
until @credits < 1 || @items.empty? || @popq.empty?
|
70
|
+
@credits -= 1
|
71
|
+
@popq.shift.call @items.shift
|
72
|
+
end
|
73
|
+
self
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'em/throttled_queue'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'em/throttled_queue'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'minitest/spec'
|
4
|
+
|
5
|
+
class << MiniTest::Spec
|
6
|
+
def it_enhanced(*args, &block)
|
7
|
+
it_original(*args) do
|
8
|
+
EM::run(&block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :it_original, :it
|
13
|
+
alias_method :it, :it_enhanced
|
14
|
+
end
|
15
|
+
|
16
|
+
describe EM::ThrottledQueue do
|
17
|
+
it "should pop items in FIFO order" do
|
18
|
+
queue = EM::ThrottledQueue.new(1)
|
19
|
+
pushed_items = [1, 2, 3, 4]
|
20
|
+
popped_items = []
|
21
|
+
queue.push(*pushed_items)
|
22
|
+
|
23
|
+
1.upto(4) do |i|
|
24
|
+
queue.pop do |j|
|
25
|
+
popped_items << j
|
26
|
+
|
27
|
+
if i == 4
|
28
|
+
popped_items.must_equal pushed_items
|
29
|
+
EM::stop
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should pop items within the rate limit" do
|
36
|
+
ticks = 0
|
37
|
+
deqs = 0
|
38
|
+
|
39
|
+
queue = EM::ThrottledQueue.new(10) # 10 deqs/s
|
40
|
+
queue.push(*(1..1000).to_a)
|
41
|
+
queue.size.must_equal 1000
|
42
|
+
|
43
|
+
EM::add_timer(0.5) do
|
44
|
+
deqs.must_equal 10
|
45
|
+
(ticks > 100).must_equal true # need good margin
|
46
|
+
EM::stop
|
47
|
+
end
|
48
|
+
|
49
|
+
ticker = proc do |me|
|
50
|
+
ticks += 1
|
51
|
+
queue.pop { deqs += 1 }
|
52
|
+
EM::next_tick { me.call(me) }
|
53
|
+
end
|
54
|
+
|
55
|
+
ticker.call(ticker)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should not build up credits over time" do
|
59
|
+
deqs = 0
|
60
|
+
|
61
|
+
queue = EM::ThrottledQueue.new(10) # 10 deqs/s
|
62
|
+
queue.push(*(1..1000).to_a)
|
63
|
+
|
64
|
+
EM::add_timer(3) do
|
65
|
+
# Oh this is bad… test tied to implementation details!
|
66
|
+
queue.instance_variable_get("@credits").must_equal 10
|
67
|
+
EM::stop
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should load the version" do
|
72
|
+
defined?(EM::ThrottledQueue::VERSION).wont_be_nil
|
73
|
+
EM::stop
|
74
|
+
end
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-throttled_queue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.2
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kim Burgestrand
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-02-13 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: eventmachine
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jeweler
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "1.5"
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rake
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: yard
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: rdiscount
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: *id005
|
71
|
+
description:
|
72
|
+
email: kim@burgestrand.se
|
73
|
+
executables: []
|
74
|
+
|
75
|
+
extensions: []
|
76
|
+
|
77
|
+
extra_rdoc_files:
|
78
|
+
- LICENSE
|
79
|
+
- README.markdown
|
80
|
+
files:
|
81
|
+
- .yardopts
|
82
|
+
- Gemfile
|
83
|
+
- Gemfile.lock
|
84
|
+
- LICENSE
|
85
|
+
- README.markdown
|
86
|
+
- Rakefile
|
87
|
+
- em-throttled_queue.gemspec
|
88
|
+
- lib/em/throttled_queue.rb
|
89
|
+
- lib/em/throttled_queue/version.rb
|
90
|
+
- lib/eventmachine/throttled_queue.rb
|
91
|
+
- spec/throttled_queue.rb
|
92
|
+
has_rdoc: true
|
93
|
+
homepage: http://github.com/Burgestrand/em-throttled_queue
|
94
|
+
licenses:
|
95
|
+
- X11 License
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ~>
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: "1.9"
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: "0"
|
113
|
+
requirements: []
|
114
|
+
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 1.5.0
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: A rate-limited Queue for EventMachine
|
120
|
+
test_files:
|
121
|
+
- spec/throttled_queue.rb
|