throttler 0.1.3 → 0.1.4
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 +18 -9
- data/lib/throttler/throttle.rb +31 -0
- data/lib/throttler.rb +17 -17
- data/spec/fixtures/foo.rb +3 -3
- data/spec/{throttler_spec.rb → integration/scenarios_spec.rb} +28 -15
- data/spec/unit/throttler/throttle_spec.rb +66 -0
- data/spec/unit/throttler_spec.rb +36 -0
- metadata +11 -8
data/README.md
CHANGED
@@ -5,22 +5,31 @@ Throttler
|
|
5
5
|
|
6
6
|
Throttle concurrency.
|
7
7
|
|
8
|
-
|
8
|
+
class Foo
|
9
|
+
include Throttler
|
10
|
+
|
11
|
+
def request
|
12
|
+
throttle("foo#{interface}", 1.0) do
|
13
|
+
#noop
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def interface; "eth0"; end
|
18
|
+
end
|
19
|
+
|
20
|
+
foo, count = Foo.new, 0
|
9
21
|
|
10
22
|
100.times do
|
11
23
|
Thread.new do
|
12
|
-
|
13
|
-
|
14
|
-
count += 1
|
15
|
-
end
|
16
|
-
end
|
24
|
+
foo.request
|
25
|
+
count += 1
|
17
26
|
end
|
18
27
|
end
|
19
|
-
sleep 2.2
|
20
28
|
|
21
|
-
|
29
|
+
sleep 1.2
|
30
|
+
count.should eql 2 # 98 threads pending
|
22
31
|
|
23
32
|
Use case
|
24
33
|
--------
|
25
34
|
|
26
|
-
Imagine multiple workers hitting the Amazon API on the same IP address.
|
35
|
+
Imagine multiple workers hitting the Amazon API on the same IP address.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Throttler
|
2
|
+
|
3
|
+
# The Throttle file
|
4
|
+
class Throttle
|
5
|
+
def initialize(name)
|
6
|
+
path = "/tmp/.#{name}"
|
7
|
+
@file = File.open(path, File::RDWR|File::CREAT)
|
8
|
+
end
|
9
|
+
|
10
|
+
def delay(interval)
|
11
|
+
last = @file.gets.to_f
|
12
|
+
last = (Time.now.to_f - interval) if last == 0.0
|
13
|
+
|
14
|
+
sleep [last + interval - Time.now.to_f, 0.0].max
|
15
|
+
end
|
16
|
+
|
17
|
+
def lock
|
18
|
+
@file.flock(File::LOCK_EX)
|
19
|
+
end
|
20
|
+
|
21
|
+
def timestamp
|
22
|
+
@file.rewind
|
23
|
+
@file.write(Time.now.to_f)
|
24
|
+
end
|
25
|
+
|
26
|
+
def unlock
|
27
|
+
@file.flock(File::LOCK_UN)
|
28
|
+
@file.close
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/throttler.rb
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
|
1
|
+
require File.dirname(__FILE__) + "/throttler/throttle"
|
2
|
+
|
3
|
+
# The Throttler module.
|
4
|
+
#
|
5
|
+
# Simply include this in the class where you wish to use the throttler.
|
2
6
|
module Throttler
|
3
|
-
require "fileutils"
|
4
7
|
|
8
|
+
# Throttles execution of a block of code.
|
9
|
+
#
|
10
|
+
# Pass name of throttler and optionally, the interval between each
|
11
|
+
# moment of execution. Latte defaults to one second.
|
12
|
+
#
|
13
|
+
# throttle("foo") { some_code }
|
14
|
+
#
|
5
15
|
def throttle(name, interval=1.0)
|
6
16
|
begin
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
file = File.open(path, "r+")
|
12
|
-
file.flock(File::LOCK_EX)
|
13
|
-
|
14
|
-
last = file.gets.to_f || Time.now.to_f - interval
|
15
|
-
sleep [last + interval - Time.now.to_f, 0.0].max
|
16
|
-
|
17
|
+
file = Throttle.new(name)
|
18
|
+
file.lock
|
19
|
+
file.delay interval
|
17
20
|
yield if block_given?
|
18
|
-
|
19
|
-
file.rewind
|
20
|
-
file.write(Time.now.to_f)
|
21
|
+
file.timestamp
|
21
22
|
ensure
|
22
|
-
file.
|
23
|
-
file.close
|
23
|
+
file.unlock
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
data/spec/fixtures/foo.rb
CHANGED
@@ -2,7 +2,7 @@ require "benchmark"
|
|
2
2
|
require "fileutils"
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
|
-
class
|
5
|
+
class Bar
|
6
6
|
include Throttler
|
7
7
|
|
8
8
|
def bar
|
@@ -20,7 +20,7 @@ class Foo
|
|
20
20
|
def noop; end
|
21
21
|
end
|
22
22
|
|
23
|
-
describe
|
23
|
+
describe "Simple integration tests" do
|
24
24
|
before do
|
25
25
|
FileUtils.rm "/tmp/.bar", :force => true
|
26
26
|
FileUtils.rm "/tmp/.baz", :force => true
|
@@ -29,7 +29,7 @@ describe Throttler do
|
|
29
29
|
it "throttles a loop" do
|
30
30
|
time = Benchmark.realtime do
|
31
31
|
3.times do
|
32
|
-
|
32
|
+
Bar.new.bar
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -40,9 +40,9 @@ describe Throttler do
|
|
40
40
|
count = 0
|
41
41
|
threads = 10.times.collect do
|
42
42
|
Thread.new do
|
43
|
-
|
43
|
+
bar = Bar.new
|
44
44
|
loop do
|
45
|
-
|
45
|
+
bar.bar
|
46
46
|
count += 1
|
47
47
|
end
|
48
48
|
end
|
@@ -53,23 +53,41 @@ describe Throttler do
|
|
53
53
|
count.should eql 3
|
54
54
|
end
|
55
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
|
+
|
56
74
|
it "throttles concurrently-running scripts" do
|
57
75
|
time = Benchmark.realtime do
|
58
76
|
3.times do
|
59
|
-
`ruby #{File.dirname(__FILE__) + "
|
77
|
+
`ruby #{File.dirname(__FILE__) + "/../fixtures/foo.rb"}`
|
60
78
|
end
|
61
79
|
end
|
62
80
|
|
63
|
-
time.should be >
|
81
|
+
time.should be > 4.0
|
64
82
|
end
|
65
83
|
|
66
|
-
it "
|
84
|
+
it "supports multiple throttles" do
|
67
85
|
count = 0
|
68
86
|
threads = 10.times.collect do
|
69
87
|
Thread.new do
|
70
|
-
|
88
|
+
bar = Bar.new
|
71
89
|
loop do
|
72
|
-
count % 2 == 0 ?
|
90
|
+
count % 2 == 0 ? bar.bar : bar.baz
|
73
91
|
count += 1
|
74
92
|
end
|
75
93
|
end
|
@@ -79,9 +97,4 @@ describe Throttler do
|
|
79
97
|
|
80
98
|
count.should eql 6
|
81
99
|
end
|
82
|
-
|
83
|
-
it "removes lock if block raises exception" do
|
84
|
-
lambda{ Foo.new.exceptional }.should raise_error
|
85
|
-
File.open("/tmp/.bar") { |f| f.flock(File::LOCK_EX | File::LOCK_NB).should_not be_false }
|
86
|
-
end
|
87
100
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "benchmark"
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Throttler
|
6
|
+
describe Throttle do
|
7
|
+
before do
|
8
|
+
FileUtils.rm "/tmp/.foo", :force => true
|
9
|
+
@foo = Throttle.new("foo")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "creates a file" do
|
13
|
+
File.exists?("/tmp/.foo").should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
context "#lock" do
|
17
|
+
it "locks the file" do
|
18
|
+
@foo.lock
|
19
|
+
File.open("/tmp/.foo") { |f| f.flock(File::LOCK_EX | File::LOCK_NB).should be_false }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "#delay" do
|
24
|
+
it "throttles if file contains a timestamp" do
|
25
|
+
file = @foo.instance_variable_get(:@file)
|
26
|
+
file.stub!(:gets).and_return(Time.now.to_f)
|
27
|
+
|
28
|
+
time = Benchmark.realtime do
|
29
|
+
@foo.delay(1.0)
|
30
|
+
end
|
31
|
+
|
32
|
+
time.should be_close 1, 0.1
|
33
|
+
end
|
34
|
+
|
35
|
+
it "does not throttle if file is blank" do
|
36
|
+
file = @foo.instance_variable_get(:@file)
|
37
|
+
file.stub!(:gets).and_return("")
|
38
|
+
|
39
|
+
time = Benchmark.realtime do
|
40
|
+
@foo.delay(1.0)
|
41
|
+
end
|
42
|
+
|
43
|
+
time.should be_close 0, 0.1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "#timestamp" do
|
48
|
+
it "timestamps file" do
|
49
|
+
@foo.timestamp
|
50
|
+
file = @foo.instance_variable_get(:@file)
|
51
|
+
file.close # We need to close the file first
|
52
|
+
|
53
|
+
File.open("/tmp/.foo") { |f| f.gets.to_f.should be_close Time.now.to_f, 0.1 }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "#unlock" do
|
58
|
+
it "unlocks the file" do
|
59
|
+
@foo.lock
|
60
|
+
@foo.unlock
|
61
|
+
|
62
|
+
File.open("/tmp/.foo") { |f| f.flock(File::LOCK_EX | File::LOCK_NB).should_not be_false }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
require "fileutils"
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
class Foo
|
6
|
+
include Throttler
|
7
|
+
|
8
|
+
def bar
|
9
|
+
throttle("foo"){ raise }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Throttler do
|
14
|
+
before do
|
15
|
+
FileUtils.rm "/tmp/.foo", :force => true
|
16
|
+
|
17
|
+
class Foo; include Throttler; end
|
18
|
+
@foo = Foo.new
|
19
|
+
end
|
20
|
+
|
21
|
+
context "#throttle" do
|
22
|
+
it "removes lock after block raises an exception" do
|
23
|
+
lambda{ @foo.throttle("foo"){ raise } }.should raise_error
|
24
|
+
|
25
|
+
File.open("/tmp/.foo"){ |f| f.flock(File::LOCK_EX | File::LOCK_NB).should_not be_false }
|
26
|
+
end
|
27
|
+
|
28
|
+
it "throttles for one second by default" do
|
29
|
+
time = Benchmark.realtime do
|
30
|
+
2.times{ @foo.throttle("foo"){ } }
|
31
|
+
end
|
32
|
+
|
33
|
+
time.should be_close 1, 0.1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: throttler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 29
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
7
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
8
|
+
- 4
|
9
|
+
version: 0.1.4
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Hakan Ensari
|
@@ -16,7 +15,7 @@ autorequire:
|
|
16
15
|
bindir: bin
|
17
16
|
cert_chain: []
|
18
17
|
|
19
|
-
date: 2010-07-
|
18
|
+
date: 2010-07-24 00:00:00 +01:00
|
20
19
|
default_executable:
|
21
20
|
dependencies: []
|
22
21
|
|
@@ -32,10 +31,13 @@ extra_rdoc_files:
|
|
32
31
|
files:
|
33
32
|
- LICENSE
|
34
33
|
- lib/throttler.rb
|
34
|
+
- lib/throttler/throttle.rb
|
35
35
|
- README.md
|
36
36
|
- spec/fixtures/foo.rb
|
37
|
+
- spec/integration/scenarios_spec.rb
|
37
38
|
- spec/spec_helper.rb
|
38
|
-
- spec/
|
39
|
+
- spec/unit/throttler/throttle_spec.rb
|
40
|
+
- spec/unit/throttler_spec.rb
|
39
41
|
has_rdoc: true
|
40
42
|
homepage: http://github.com/papercavalier/throttler
|
41
43
|
licenses: []
|
@@ -50,7 +52,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
52
|
requirements:
|
51
53
|
- - ">="
|
52
54
|
- !ruby/object:Gem::Version
|
53
|
-
hash:
|
55
|
+
hash: -2072448878210444692
|
54
56
|
segments:
|
55
57
|
- 0
|
56
58
|
version: "0"
|
@@ -59,7 +61,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
61
|
requirements:
|
60
62
|
- - ">="
|
61
63
|
- !ruby/object:Gem::Version
|
62
|
-
hash: 3
|
63
64
|
segments:
|
64
65
|
- 0
|
65
66
|
version: "0"
|
@@ -72,5 +73,7 @@ specification_version: 3
|
|
72
73
|
summary: Throttles the frequency at which concurrently-running Ruby blocks are executed.
|
73
74
|
test_files:
|
74
75
|
- spec/fixtures/foo.rb
|
76
|
+
- spec/integration/scenarios_spec.rb
|
75
77
|
- spec/spec_helper.rb
|
76
|
-
- spec/
|
78
|
+
- spec/unit/throttler/throttle_spec.rb
|
79
|
+
- spec/unit/throttler_spec.rb
|