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 CHANGED
@@ -1,35 +1,47 @@
1
1
  Throttler
2
2
  =========
3
3
 
4
+ Put a throttle on concurrency.
5
+
4
6
  ![Mapplethorpe](http://github.com/papercavalier/throttler/raw/master/mapplethorpe_chains.jpg)
5
7
 
6
- Throttle concurrency.
8
+ Example
9
+ --------
7
10
 
8
- class Foo
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
- throttle("foo#{interface}", 1.0) do
13
- # do something
14
- end
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
- foo, count = Foo.new, 0
31
+ worker, count = Worker.new, 0
21
32
 
22
- 100.times do
23
- Thread.new do
24
- foo.request
25
- count += 1
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 1.2
30
- count.should eql(2)
31
-
32
- Use case
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)
@@ -1,7 +1,7 @@
1
1
  module Throttler
2
2
  class Timer
3
- def initialize(name)
4
- path = "/tmp/.#{name}"
3
+ def initialize(scope)
4
+ path = "/tmp/.#{scope}"
5
5
  @file = File.open(path, File::RDWR|File::CREAT)
6
6
  end
7
7
 
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 name of throttler and optionally, the interval between each
11
- # moment of execution. Latter defaults to one second.
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(name, interval=1.0)
16
- timer = Timer.new(name)
15
+ def throttle(scope, interval=1.0)
16
+ timer = Timer.new(scope)
17
17
  timer.lock
18
- begin
19
- sleep [timer.timestamp + interval - Time.now.to_f, 0.0].max
20
- yield if block_given?
21
- timer.timestamp = Time.now.to_f
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
@@ -4,10 +4,14 @@ class Foo
4
4
  include Throttler
5
5
 
6
6
  def bar
7
- throttle("bar", 2.0) do
8
- #noop
9
- end
7
+ throttle("foo", 2.0) { }
10
8
  end
9
+
10
+ def control; end
11
11
  end
12
12
 
13
- Foo.new.bar
13
+ if ARGV.size == 0
14
+ Foo.new.bar
15
+ else
16
+ Foo.new.control
17
+ end
@@ -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
@@ -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 after block raises an exception" do
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
- - 0
9
- version: 0.2.0
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-26 00:00:00 +01:00
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/throttler_spec.rb
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: 4404238439013896602
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/throttler_spec.rb
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