throttler 0.2.0 → 0.2.1
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/README.md +32 -20
- data/lib/throttler/timer.rb +2 -2
- data/lib/throttler.rb +8 -11
- data/spec/fixtures/foo.rb +8 -4
- data/spec/integration/blocks_with_long_execution_time_spec.rb +33 -0
- data/spec/integration/fibers_spec.rb +29 -0
- data/spec/integration/loop_spec.rb +27 -0
- data/spec/integration/processes_spec.rb +22 -0
- data/spec/integration/threads_spec.rb +51 -0
- data/spec/unit/throttler_spec.rb +10 -11
- metadata +14 -6
- data/spec/integration/throttler_spec.rb +0 -100
data/README.md
CHANGED
@@ -1,35 +1,47 @@
|
|
1
1
|
Throttler
|
2
2
|
=========
|
3
3
|
|
4
|
+
Put a throttle on concurrency.
|
5
|
+
|
4
6
|

|
5
7
|
|
6
|
-
|
8
|
+
Example
|
9
|
+
--------
|
7
10
|
|
8
|
-
|
9
|
-
include Throttler
|
11
|
+
Let's assume we have multiple workers hitting the Amazon API on three IP addresses.
|
10
12
|
|
13
|
+
class Worker
|
14
|
+
include Throttler
|
15
|
+
|
16
|
+
attr_accessor :interface
|
17
|
+
|
11
18
|
def request
|
12
|
-
|
13
|
-
|
14
|
-
|
19
|
+
|
20
|
+
# Scope the throttle by network interface:
|
21
|
+
name = "foo#{interface}"
|
22
|
+
|
23
|
+
# Amazon asks you to limit requests to one per second per IP, so:
|
24
|
+
interval = 0.9
|
25
|
+
|
26
|
+
# Say a request downloads in two seconds on average
|
27
|
+
throttle(name, interval) { sleep(2) }
|
15
28
|
end
|
16
|
-
|
17
|
-
def interface; "eth0"; end
|
18
29
|
end
|
19
30
|
|
20
|
-
|
31
|
+
worker, count = Worker.new, 0
|
21
32
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
33
|
+
(0..3).each do |port|
|
34
|
+
10.times do
|
35
|
+
Thread.new do
|
36
|
+
count += 1
|
37
|
+
worker.interface = "eth#{port}"
|
38
|
+
loop{ worker.request }
|
39
|
+
end
|
26
40
|
end
|
27
41
|
end
|
28
42
|
|
29
|
-
sleep
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
Imagine multiple workers hitting the Amazon API on the same IP address.
|
43
|
+
sleep 10.25
|
44
|
+
|
45
|
+
# We expect workers to have sent 11 requests within 10 seconds on
|
46
|
+
# each of the three available network interfaces.
|
47
|
+
count.should eql(33)
|
data/lib/throttler/timer.rb
CHANGED
data/lib/throttler.rb
CHANGED
@@ -7,20 +7,17 @@ module Throttler
|
|
7
7
|
|
8
8
|
# Throttles the frequency in which a block is run.
|
9
9
|
#
|
10
|
-
# Pass
|
11
|
-
#
|
10
|
+
# Pass throttler scope and the interval between each execution.
|
11
|
+
# Latter defaults to one second.
|
12
12
|
#
|
13
13
|
# throttle("foo") { some_code }
|
14
14
|
#
|
15
|
-
def throttle(
|
16
|
-
timer = Timer.new(
|
15
|
+
def throttle(scope, interval=1.0)
|
16
|
+
timer = Timer.new(scope)
|
17
17
|
timer.lock
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
ensure
|
23
|
-
timer.unlock
|
24
|
-
end
|
18
|
+
sleep [timer.timestamp + interval - Time.now.to_f, 0.0].max
|
19
|
+
timer.timestamp = Time.now.to_f
|
20
|
+
timer.unlock
|
21
|
+
yield if block_given?
|
25
22
|
end
|
26
23
|
end
|
data/spec/fixtures/foo.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe "Throttler" do
|
5
|
+
before do
|
6
|
+
class Foo
|
7
|
+
include Throttler
|
8
|
+
|
9
|
+
def bar
|
10
|
+
throttle("foo") do
|
11
|
+
yield # so we can count
|
12
|
+
sleep 10
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
FileUtils.rm "/tmp/.foo", :force => true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "throttles threads with long execution times" do
|
21
|
+
count = 0
|
22
|
+
|
23
|
+
threads = 10.times.collect do
|
24
|
+
Thread.new do
|
25
|
+
loop{ Foo.new.bar{ count += 1 } }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
sleep 3.25
|
29
|
+
threads.each { |t| Thread.kill(t) }
|
30
|
+
|
31
|
+
count.should eql 4
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
require "fiber"
|
3
|
+
require "fileutils"
|
4
|
+
require "spec_helper"
|
5
|
+
|
6
|
+
describe "Throttler" do
|
7
|
+
before do
|
8
|
+
class Foo
|
9
|
+
include Throttler
|
10
|
+
|
11
|
+
def bar
|
12
|
+
throttle("foo"){ }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
FileUtils.rm "/tmp/.foo", :force => true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "throttles fibers" do
|
20
|
+
time = Benchmark.realtime do
|
21
|
+
fib = Fiber.new do
|
22
|
+
loop{ Foo.new.bar; Fiber.yield }
|
23
|
+
end
|
24
|
+
3.times { fib.resume }
|
25
|
+
end
|
26
|
+
|
27
|
+
time.should be_close 2, 0.01
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
require "fileutils"
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe "Throttler" do
|
6
|
+
before do
|
7
|
+
class Foo
|
8
|
+
include Throttler
|
9
|
+
|
10
|
+
def bar
|
11
|
+
throttle("foo"){ }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
FileUtils.rm "/tmp/.foo", :force => true
|
16
|
+
end
|
17
|
+
|
18
|
+
it "throttles a loop" do
|
19
|
+
time = Benchmark.realtime do
|
20
|
+
3.times do
|
21
|
+
Foo.new.bar
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
time.should be_close 2, 0.1
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
require "fileutils"
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
|
6
|
+
describe "Throttler" do
|
7
|
+
before do
|
8
|
+
FileUtils.rm "/tmp/.foo", :force => true
|
9
|
+
end
|
10
|
+
|
11
|
+
it "throttles concurrent processes" do
|
12
|
+
startup_time = Benchmark.realtime do
|
13
|
+
`ruby #{File.dirname(__FILE__) + "/../fixtures/foo.rb"} control`
|
14
|
+
end
|
15
|
+
|
16
|
+
time = Benchmark.realtime do
|
17
|
+
4.times{ `ruby #{File.dirname(__FILE__) + "/../fixtures/foo.rb"}` }
|
18
|
+
end
|
19
|
+
|
20
|
+
(time - startup_time).should be_close(6.0, 0.5)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe "Throttler" do
|
5
|
+
before do
|
6
|
+
class Foo
|
7
|
+
include Throttler
|
8
|
+
|
9
|
+
def bar
|
10
|
+
throttle("foo"){ }
|
11
|
+
end
|
12
|
+
|
13
|
+
def baz
|
14
|
+
throttle("foo-prime"){ }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
%w{foo foo-prime}.each do |file|
|
19
|
+
FileUtils.rm "/tmp/.#{file}", :force => true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "throttles threads" do
|
24
|
+
count = 0
|
25
|
+
threads = 10.times.collect do
|
26
|
+
Thread.new do
|
27
|
+
loop { Foo.new.bar; count += 1 }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
sleep 3.25
|
31
|
+
threads.each { |t| Thread.kill(t) }
|
32
|
+
|
33
|
+
count.should eql 4
|
34
|
+
end
|
35
|
+
|
36
|
+
it "throttles by scope" do
|
37
|
+
count = 0
|
38
|
+
threads = 10.times.collect do
|
39
|
+
Thread.new do
|
40
|
+
loop do
|
41
|
+
Foo.new.send(count % 2 == 0 ? :bar : :baz)
|
42
|
+
count += 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
sleep 3.25
|
47
|
+
threads.each { |t| Thread.kill(t) }
|
48
|
+
|
49
|
+
count.should eql 8
|
50
|
+
end
|
51
|
+
end
|
data/spec/unit/throttler_spec.rb
CHANGED
@@ -2,24 +2,23 @@ require "benchmark"
|
|
2
2
|
require "fileutils"
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
|
-
class Foo
|
6
|
-
include Throttler
|
7
|
-
|
8
|
-
def bar
|
9
|
-
throttle("foo"){ raise }
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
5
|
describe Throttler do
|
14
6
|
before do
|
7
|
+
class Foo
|
8
|
+
include Throttler
|
9
|
+
|
10
|
+
def bar
|
11
|
+
throttle("foo"){ raise }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
15
|
FileUtils.rm "/tmp/.foo", :force => true
|
16
|
-
|
17
|
-
class Foo; include Throttler; end
|
16
|
+
|
18
17
|
@foo = Foo.new
|
19
18
|
end
|
20
19
|
|
21
20
|
context "#throttle" do
|
22
|
-
it "removes lock
|
21
|
+
it "removes lock even if block raises an exception" do
|
23
22
|
lambda{ @foo.throttle("foo"){ raise } }.should raise_error
|
24
23
|
|
25
24
|
File.open("/tmp/.foo"){ |f| f.flock(File::LOCK_EX | File::LOCK_NB).should_not be_false }
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 1
|
9
|
+
version: 0.2.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Hakan Ensari
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-07-
|
18
|
+
date: 2010-07-27 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -34,7 +34,11 @@ files:
|
|
34
34
|
- lib/throttler/timer.rb
|
35
35
|
- README.md
|
36
36
|
- spec/fixtures/foo.rb
|
37
|
-
- spec/integration/
|
37
|
+
- spec/integration/blocks_with_long_execution_time_spec.rb
|
38
|
+
- spec/integration/fibers_spec.rb
|
39
|
+
- spec/integration/loop_spec.rb
|
40
|
+
- spec/integration/processes_spec.rb
|
41
|
+
- spec/integration/threads_spec.rb
|
38
42
|
- spec/spec_helper.rb
|
39
43
|
- spec/unit/throttler/timer_spec.rb
|
40
44
|
- spec/unit/throttler_spec.rb
|
@@ -52,7 +56,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
52
56
|
requirements:
|
53
57
|
- - ">="
|
54
58
|
- !ruby/object:Gem::Version
|
55
|
-
hash:
|
59
|
+
hash: -3148749189245223368
|
56
60
|
segments:
|
57
61
|
- 0
|
58
62
|
version: "0"
|
@@ -73,7 +77,11 @@ specification_version: 3
|
|
73
77
|
summary: Throttles the frequency in which asynchronously-executed Ruby blocks are run on a single server or network interface.
|
74
78
|
test_files:
|
75
79
|
- spec/fixtures/foo.rb
|
76
|
-
- spec/integration/
|
80
|
+
- spec/integration/blocks_with_long_execution_time_spec.rb
|
81
|
+
- spec/integration/fibers_spec.rb
|
82
|
+
- spec/integration/loop_spec.rb
|
83
|
+
- spec/integration/processes_spec.rb
|
84
|
+
- spec/integration/threads_spec.rb
|
77
85
|
- spec/spec_helper.rb
|
78
86
|
- spec/unit/throttler/timer_spec.rb
|
79
87
|
- spec/unit/throttler_spec.rb
|
@@ -1,100 +0,0 @@
|
|
1
|
-
require "benchmark"
|
2
|
-
require "fileutils"
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
class Bar
|
6
|
-
include Throttler
|
7
|
-
|
8
|
-
def bar
|
9
|
-
throttle("bar"){ noop }
|
10
|
-
end
|
11
|
-
|
12
|
-
def baz
|
13
|
-
throttle("baz"){ noop }
|
14
|
-
end
|
15
|
-
|
16
|
-
def exceptional
|
17
|
-
throttle("bar"){ raise }
|
18
|
-
end
|
19
|
-
|
20
|
-
def noop; end
|
21
|
-
end
|
22
|
-
|
23
|
-
describe "Throttler" do
|
24
|
-
before do
|
25
|
-
FileUtils.rm "/tmp/.bar", :force => true
|
26
|
-
FileUtils.rm "/tmp/.baz", :force => true
|
27
|
-
end
|
28
|
-
|
29
|
-
it "throttles a loop" do
|
30
|
-
time = Benchmark.realtime do
|
31
|
-
3.times do
|
32
|
-
Bar.new.bar
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
time.should be_close 2, 0.1
|
37
|
-
end
|
38
|
-
|
39
|
-
it "throttles threads" do
|
40
|
-
count = 0
|
41
|
-
threads = 10.times.collect do
|
42
|
-
Thread.new do
|
43
|
-
bar = Bar.new
|
44
|
-
loop do
|
45
|
-
bar.bar
|
46
|
-
count += 1
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
sleep 2.2
|
51
|
-
threads.each { |t| Thread.kill(t) }
|
52
|
-
|
53
|
-
count.should eql 3
|
54
|
-
end
|
55
|
-
|
56
|
-
it "throttles fibers" do
|
57
|
-
require "fiber"
|
58
|
-
|
59
|
-
time = Benchmark.realtime do
|
60
|
-
fib = Fiber.new do
|
61
|
-
bar = Bar.new
|
62
|
-
loop do
|
63
|
-
bar.bar
|
64
|
-
Fiber.yield
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
3.times { fib.resume }
|
69
|
-
end
|
70
|
-
|
71
|
-
time.should be_close 2, 0.01
|
72
|
-
end
|
73
|
-
|
74
|
-
it "throttles concurrently-running scripts" do
|
75
|
-
time = Benchmark.realtime do
|
76
|
-
3.times do
|
77
|
-
`ruby #{File.dirname(__FILE__) + "/../fixtures/foo.rb"}`
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
time.should be > 4.0
|
82
|
-
end
|
83
|
-
|
84
|
-
it "supports multiple throttles" do
|
85
|
-
count = 0
|
86
|
-
threads = 10.times.collect do
|
87
|
-
Thread.new do
|
88
|
-
bar = Bar.new
|
89
|
-
loop do
|
90
|
-
count % 2 == 0 ? bar.bar : bar.baz
|
91
|
-
count += 1
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
sleep 2.2
|
96
|
-
threads.each { |t| Thread.kill(t) }
|
97
|
-
|
98
|
-
count.should eql 6
|
99
|
-
end
|
100
|
-
end
|