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 CHANGED
@@ -5,22 +5,31 @@ Throttler
5
5
 
6
6
  Throttle concurrency.
7
7
 
8
- include Throttler
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
- loop do
13
- throttle("foo") do
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
- count.should eql 3
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. You gotta do something about it.
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
- # Throttler
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
- path = "/tmp/.#{name}"
8
-
9
- FileUtils.touch path unless File.exist? path
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.flock(File::LOCK_UN)
23
- file.close
23
+ file.unlock
24
24
  end
25
25
  end
26
26
  end
data/spec/fixtures/foo.rb CHANGED
@@ -4,10 +4,10 @@ class Foo
4
4
  include Throttler
5
5
 
6
6
  def bar
7
- throttle("bar") { noop }
7
+ throttle("bar", 2.0) do
8
+ #noop
9
+ end
8
10
  end
9
-
10
- def noop; end
11
11
  end
12
12
 
13
13
  Foo.new.bar
@@ -2,7 +2,7 @@ require "benchmark"
2
2
  require "fileutils"
3
3
  require "spec_helper"
4
4
 
5
- class Foo
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 Throttler do
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
- Foo.new.bar
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
- foo = Foo.new
43
+ bar = Bar.new
44
44
  loop do
45
- foo.bar
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__) + "/fixtures/foo.rb"}`
77
+ `ruby #{File.dirname(__FILE__) + "/../fixtures/foo.rb"}`
60
78
  end
61
79
  end
62
80
 
63
- time.should be > 2.0
81
+ time.should be > 4.0
64
82
  end
65
83
 
66
- it "throttles by name" do
84
+ it "supports multiple throttles" do
67
85
  count = 0
68
86
  threads = 10.times.collect do
69
87
  Thread.new do
70
- foo = Foo.new
88
+ bar = Bar.new
71
89
  loop do
72
- count % 2 == 0 ? foo.bar : foo.baz
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
- - 3
10
- version: 0.1.3
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-23 00:00:00 +01:00
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/throttler_spec.rb
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: 3
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/throttler_spec.rb
78
+ - spec/unit/throttler/throttle_spec.rb
79
+ - spec/unit/throttler_spec.rb