throttler 0.2.4 → 0.3.0

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/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .bundle
2
+ pkg
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,16 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ throttler (0.3.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ rake (0.9.2.2)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ rake (~> 0.9.2)
16
+ throttler!
data/LICENSE CHANGED
@@ -1,7 +1,7 @@
1
1
  (The MIT License)
2
-
3
- Copyright (c) 2010 Paper Cavalier
4
-
2
+
3
+ Copyright (c) 2012 Hakan Ensari
4
+
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
7
7
  'Software'), to deal in the Software without restriction, including
@@ -9,10 +9,10 @@ without limitation the rights to use, copy, modify, merge, publish,
9
9
  distribute, sublicense, and/or sell copies of the Software, and to
10
10
  permit persons to whom the Software is furnished to do so, subject to
11
11
  the following conditions:
12
-
12
+
13
13
  The above copyright notice and this permission notice shall be
14
14
  included in all copies or substantial portions of the Software.
15
-
15
+
16
16
  THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
17
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
18
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
data/README.md CHANGED
@@ -1,26 +1,40 @@
1
1
  Throttler
2
2
  =========
3
3
 
4
- Throttler is a tired, cranky module that helps you throttle stuff in parallel-running Ruby scripts on a single machine.
4
+ [![travis] [1]] [2]
5
5
 
6
- ![Mapplethorpe](https://github.com/papercavalier/throttler/raw/master/mapplethorpe_chains.jpg)
6
+ Throttler rate-limits code execution across threads or processes on a server.
7
7
 
8
- Example
9
- -------
8
+ ![Mapplethorpe][3]
10
9
 
11
- Our very own use case: We have multiple Resque workers hitting the Amazon IP on multiple addresses and want to make sure they don't fire more than once per locale per IP.
10
+ Installation
11
+ ------------
12
12
 
13
- Here's some pseudo code:
13
+ ```ruby
14
+ # in your Gemfile
15
+ gem 'throttler'
16
+ ```
14
17
 
15
- class Worker
16
- include Throttler
18
+ Throttler works only on platforms that support file locking.
17
19
 
18
- def perform(*args)
19
- scope = "#{locale}-#{interface}"
20
- freq = 1.0
20
+ Usage
21
+ -----
21
22
 
22
- throttle(scope, freq) do
23
- # perform a request
24
- end
25
- end
23
+ The following background job ensures workers will not scrape a site faster than
24
+ once every second per IP address.
25
+
26
+ ```ruby
27
+ class Scrape
28
+ def self.perform(site, ip_address, *ids)
29
+ # The block is syntactic sugar.
30
+ Throttler.limit 1.0, site, ip_address do
31
+ spider = Spider.new site, ip_address
32
+ spider.scrape *ids
26
33
  end
34
+ end
35
+ end
36
+ ```
37
+
38
+ [1]: https://secure.travis-ci.org/hakanensari/throttler.png
39
+ [2]: http://travis-ci.org/hakanensari/throttler
40
+ [3]: http://f.cl.ly/items/2S343U141D0N3b3h1K09/Mapplethorpe.png
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |test|
5
+ test.libs += %w{lib test}
6
+ test.test_files = FileList['test/**/*_test.rb']
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,48 @@
1
+ module Throttler
2
+ class Throttle
3
+ class << self
4
+ attr_accessor :tmp_dir
5
+ end
6
+
7
+ @tmp_dir = '/tmp'
8
+
9
+ def initialize(namespace)
10
+ @namespace = namespace
11
+ @file = File.open(path, File::RDWR|File::CREAT)
12
+ end
13
+
14
+ def lock
15
+ @file.flock(File::LOCK_EX)
16
+ end
17
+
18
+ def path
19
+ "#{self.class.tmp_dir}/.lock-#{@namespace}"
20
+ end
21
+
22
+ def hold(wait)
23
+ sleep [timestamp + wait - now, 0.0].max
24
+ timestamp!
25
+ end
26
+
27
+ def unlock
28
+ @file.flock(File::LOCK_UN)
29
+ @file.close
30
+ end
31
+
32
+ private
33
+
34
+ def now
35
+ Time.now.to_f
36
+ end
37
+
38
+ def timestamp
39
+ @file.rewind
40
+ @file.gets.to_f
41
+ end
42
+
43
+ def timestamp!
44
+ @file.rewind
45
+ @file.write now
46
+ end
47
+ end
48
+ end
@@ -1,4 +1,4 @@
1
- module Throttler #:nodoc:
2
- VERSION = "0.2.4"
1
+ module Throttler
2
+ VERSION = '0.3.0'
3
3
  end
4
4
 
data/lib/throttler.rb CHANGED
@@ -1,16 +1,23 @@
1
- require "throttler/timer"
1
+ require 'throttler/throttle'
2
2
 
3
- # = Throttler
4
- #
5
- # A tired, cranky module that helps you throttle stuff in parallel-running Ruby
6
- # scripts on a single machine. It gets the job done.
3
+ # Throttler rate-limits code execution.
7
4
  module Throttler
8
- def throttle(scope="throttler",freq=1.0)
9
- timer = Timer.new(scope)
10
- timer.lock
11
- sleep [timer.timestamp + freq - Time.now.to_f, 0.0].max
12
- timer.timestamp = Time.now.to_f
13
- timer.unlock
5
+ # Ensures subsequent code, including any given block, is executed at most per
6
+ # every `wait` seconds.
7
+ #
8
+ # Optionally takes a splat of words to namespace the throttle.
9
+ def self.limit(wait, *words)
10
+ words << 'default' if words.empty?
11
+ namespace = words.join '-'
12
+
13
+ begin
14
+ throttle = Throttle.new namespace
15
+ throttle.lock
16
+ throttle.hold wait
17
+ ensure
18
+ throttle.unlock
19
+ end
20
+
14
21
  yield if block_given?
15
22
  end
16
23
  end
@@ -0,0 +1,20 @@
1
+ require 'test/unit'
2
+ require 'benchmark'
3
+ require 'throttler'
4
+
5
+ class Work
6
+ def self.perform
7
+ Throttler.limit 0.1
8
+ end
9
+ end
10
+
11
+ class ThrottlerTest < Test::Unit::TestCase
12
+ def test_throttling
13
+ time = Benchmark.realtime do
14
+ 11.times { Process.fork { Work.perform } }
15
+ Process.waitall
16
+ end
17
+
18
+ assert_equal 1, time.to_i
19
+ end
20
+ end
data/throttler.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'throttler/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'throttler'
7
+ s.version = Throttler::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Hakan Ensari', 'Piotr Łaszewski']
10
+ s.email = ['hakan.ensari@papercavalier.com']
11
+ s.homepage = 'https://github.com/hakanensari/throttler'
12
+ s.summary = 'Rate-limits code execution'
13
+ s.description = 'Throttler rate-limits code execution across threads or processes.'
14
+
15
+ s.add_development_dependency 'rake', '~> 0.9.2'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+ end
22
+
metadata CHANGED
@@ -1,101 +1,70 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: throttler
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 2
8
- - 4
9
- version: 0.2.4
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Hakan Ensari
13
- - "Piotr \xC5\x81aszewski"
9
+ - Piotr Łaszewski
14
10
  autorequire:
15
11
  bindir: bin
16
12
  cert_chain: []
17
-
18
- date: 2010-11-29 00:00:00 +00:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: rspec
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
13
+ date: 2012-03-16 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ requirement: &70196529042720 !ruby/object:Gem::Requirement
25
18
  none: false
26
- requirements:
19
+ requirements:
27
20
  - - ~>
28
- - !ruby/object:Gem::Version
29
- segments:
30
- - 2
31
- - 1
32
- - 0
33
- version: 2.1.0
21
+ - !ruby/object:Gem::Version
22
+ version: 0.9.2
34
23
  type: :development
35
- version_requirements: *id001
36
- description: A tired, cranky module that helps you throttle stuff in parallel-running Ruby scripts on a single machine.
37
- email:
38
- - code@papercavalier.com
24
+ prerelease: false
25
+ version_requirements: *70196529042720
26
+ description: Throttler rate-limits code execution across threads or processes.
27
+ email:
28
+ - hakan.ensari@papercavalier.com
39
29
  executables: []
40
-
41
30
  extensions: []
42
-
43
31
  extra_rdoc_files: []
44
-
45
- files:
46
- - lib/throttler/timer.rb
47
- - lib/throttler/version.rb
48
- - lib/throttler.rb
32
+ files:
33
+ - .gitignore
34
+ - .travis.yml
35
+ - Gemfile
36
+ - Gemfile.lock
49
37
  - LICENSE
50
38
  - README.md
51
- - spec/fixtures/foo.rb
52
- - spec/integration/blocks_with_long_execution_time_spec.rb
53
- - spec/integration/fibers_spec.rb
54
- - spec/integration/loop_spec.rb
55
- - spec/integration/processes_spec.rb
56
- - spec/integration/threads_spec.rb
57
- - spec/spec_helper.rb
58
- - spec/unit/throttler/timer_spec.rb
59
- - spec/unit/throttler_spec.rb
60
- has_rdoc: true
61
- homepage: http://github.com/papercavalier/throttler
39
+ - Rakefile
40
+ - lib/throttler.rb
41
+ - lib/throttler/throttle.rb
42
+ - lib/throttler/version.rb
43
+ - test/throttler_test.rb
44
+ - throttler.gemspec
45
+ homepage: https://github.com/hakanensari/throttler
62
46
  licenses: []
63
-
64
47
  post_install_message:
65
48
  rdoc_options: []
66
-
67
- require_paths:
49
+ require_paths:
68
50
  - lib
69
- required_ruby_version: !ruby/object:Gem::Requirement
51
+ required_ruby_version: !ruby/object:Gem::Requirement
70
52
  none: false
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- segments:
75
- - 0
76
- version: "0"
77
- required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
58
  none: false
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- segments:
83
- - 0
84
- version: "0"
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
85
63
  requirements: []
86
-
87
- rubyforge_project: throttler
88
- rubygems_version: 1.3.7
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.11
89
66
  signing_key:
90
67
  specification_version: 3
91
- summary: A tired, cranky throttler
92
- test_files:
93
- - spec/fixtures/foo.rb
94
- - spec/integration/blocks_with_long_execution_time_spec.rb
95
- - spec/integration/fibers_spec.rb
96
- - spec/integration/loop_spec.rb
97
- - spec/integration/processes_spec.rb
98
- - spec/integration/threads_spec.rb
99
- - spec/spec_helper.rb
100
- - spec/unit/throttler/timer_spec.rb
101
- - spec/unit/throttler_spec.rb
68
+ summary: Rate-limits code execution
69
+ test_files:
70
+ - test/throttler_test.rb
@@ -1,27 +0,0 @@
1
- module Throttler #:nodoc:
2
- class Timer
3
- def initialize(scope)
4
- path = "/tmp/.#{scope}"
5
- @timer = File.open(path, File::RDWR|File::CREAT)
6
- end
7
-
8
- def lock
9
- @timer.flock(File::LOCK_EX)
10
- end
11
-
12
- def timestamp
13
- @timestamp ||= @timer.gets.to_f
14
- end
15
-
16
- def timestamp=(time)
17
- @timer.rewind
18
- @timer.write(time)
19
- @timestamp = time
20
- end
21
-
22
- def unlock
23
- @timer.flock(File::LOCK_UN)
24
- @timer.close
25
- end
26
- end
27
- end
data/spec/fixtures/foo.rb DELETED
@@ -1,18 +0,0 @@
1
- $:.push File.expand_path("../../../lib", __FILE__)
2
- require "throttler"
3
-
4
- class Foo
5
- include Throttler
6
-
7
- def bar
8
- throttle("foo", 2.0) { }
9
- end
10
-
11
- def control; end
12
- end
13
-
14
- if ARGV.size == 0
15
- Foo.new.bar
16
- else
17
- Foo.new.control
18
- end
@@ -1,33 +0,0 @@
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
@@ -1,29 +0,0 @@
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 { }
13
- end
14
- end
15
-
16
- FileUtils.rm "/tmp/.throttler", :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_within(0.1).of(2)
28
- end
29
- end
@@ -1,27 +0,0 @@
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 { }
12
- end
13
- end
14
-
15
- FileUtils.rm "/tmp/.throttler", :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_within(0.1).of(2)
26
- end
27
- end
@@ -1,22 +0,0 @@
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_within(0.5).of(6.0)
21
- end
22
- end
@@ -1,51 +0,0 @@
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 { }
11
- end
12
-
13
- def baz
14
- throttle("foo-prime"){ }
15
- end
16
- end
17
-
18
- %w{throttler 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/spec_helper.rb DELETED
@@ -1,2 +0,0 @@
1
- require "rspec"
2
- require File.expand_path("../../lib/throttler", __FILE__)
@@ -1,65 +0,0 @@
1
- require "spec_helper"
2
- require "benchmark"
3
- require "fileutils"
4
-
5
- module Throttler
6
- describe Timer do
7
- before do
8
- FileUtils.rm "/tmp/.foo", :force => true
9
- end
10
-
11
- let!(:timer) do
12
- Timer.new("foo")
13
- end
14
-
15
- it "creates a file" do
16
- File.exists?("/tmp/.foo").should be_true
17
- end
18
-
19
- context "#lock" do
20
- it "locks the file" do
21
- timer.lock
22
- File.open("/tmp/.foo") do |f|
23
- f.flock(File::LOCK_EX | File::LOCK_NB).should be_false
24
- end
25
- end
26
- end
27
-
28
- context "#timestamp" do
29
- it "gets the timestamp" do
30
- now = Time.now.to_f
31
- File.open("/tmp/.foo", "w") { |f| f.write(now) }
32
- timer.timestamp.should be_within(0.1).of(now)
33
- end
34
-
35
- it "returns 0.0 when file is first created" do
36
- timer.timestamp.should eql(0.0)
37
- end
38
- end
39
-
40
- context "#timestamp=" do
41
- it "sets the timestamp" do
42
- now = Time.now.to_f
43
- timer.timestamp= now
44
-
45
- # We need to close the file first
46
- timer.instance_variable_get(:@timer).close
47
-
48
- File.open("/tmp/.foo") do |f|
49
- f.gets.to_f.should be_within(0.1).of(now)
50
- end
51
- end
52
- end
53
-
54
- context "#unlock" do
55
- it "unlocks the file" do
56
- timer.lock
57
- timer.unlock
58
-
59
- File.open("/tmp/.foo") do |f|
60
- f.flock(File::LOCK_EX | File::LOCK_NB).should_not be_false
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,44 +0,0 @@
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
- end
10
-
11
- FileUtils.rm "/tmp/.throttler", :force => true
12
- end
13
-
14
- let!(:foo) do
15
- Foo.new
16
- end
17
-
18
- describe "#throttle" do
19
- it "removes a lock when an exception is raised" do
20
- expect do
21
- foo.throttle { raise }
22
- end.to raise_error
23
-
24
- File.open("/tmp/.throttler") do |f|
25
- f.flock(File::LOCK_EX | File::LOCK_NB).should_not be_false
26
- end
27
- end
28
-
29
- context "by default" do
30
- it "names scope as `throttler`" do
31
- foo.throttle
32
- FileTest.exists?("/tmp/.throttler").should be_true
33
- end
34
-
35
- it "throttles for one second" do
36
- time = Benchmark.realtime do
37
- 2.times{ foo.throttle }
38
- end
39
-
40
- time.should be_within(0.1).of(1)
41
- end
42
- end
43
- end
44
- end