sidekiq-limit_fetch 0.2
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 +2 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +27 -0
- data/Rakefile +1 -0
- data/lib/sidekiq/limit_fetch/queue.rb +14 -0
- data/lib/sidekiq/limit_fetch/semaphore.rb +28 -0
- data/lib/sidekiq/limit_fetch/unit_of_work.rb +16 -0
- data/lib/sidekiq/limit_fetch.rb +59 -0
- data/sidekiq-limit_fetch.gemspec +19 -0
- data/spec/integration_spec.rb +67 -0
- metadata +89 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 brainopia
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
## Description
|
2
|
+
|
3
|
+
Sidekig strategy to restrict number of workers
|
4
|
+
which are able to run specified queues simultaneously.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'sidekiq-limit_fetch'
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
Specify limits which you want to place on queues inside sidekiq.yml:
|
15
|
+
|
16
|
+
```yaml
|
17
|
+
:limits:
|
18
|
+
queue_name1: 5
|
19
|
+
queue_name2: 10
|
20
|
+
```
|
21
|
+
|
22
|
+
In this example, tasks for the first restricted queue will be run by at most 5
|
23
|
+
workers at the same time and the second queue will have no more than 10
|
24
|
+
workers simultaneously.
|
25
|
+
|
26
|
+
Sponsored by [Evil Martians].
|
27
|
+
[Evil Martians]: http://evilmartians.com/
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Sidekiq::LimitFetch
|
2
|
+
class Queue
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
attr_reader :name, :full_name
|
6
|
+
def_delegators :@lock, :acquire, :release
|
7
|
+
|
8
|
+
def initialize(name, limit)
|
9
|
+
@name = name
|
10
|
+
@full_name = "queue:#{name}"
|
11
|
+
@lock = Semaphore.for limit
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Sidekiq::LimitFetch::Semaphore
|
2
|
+
Stub = Struct.new(:acquire, :release)
|
3
|
+
|
4
|
+
def self.for(limit)
|
5
|
+
limit ? new(limit) : stub
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.stub
|
9
|
+
@stub ||= Stub.new(true, true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(limit)
|
13
|
+
@lock = Mutex.new
|
14
|
+
@limit = limit
|
15
|
+
end
|
16
|
+
|
17
|
+
def acquire
|
18
|
+
@lock.synchronize do
|
19
|
+
@limit -= 1 if @limit > 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def release
|
24
|
+
@lock.synchronize do
|
25
|
+
@limit += 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Sidekiq::LimitFetch::UnitOfWork = Struct.new :queue_wrapper, :message do
|
2
|
+
extend Forwardable
|
3
|
+
|
4
|
+
def_delegator :queue_wrapper, :full_name, :queue
|
5
|
+
def_delegator :queue_wrapper, :name, :queue_name
|
6
|
+
def_delegator :queue_wrapper, :release
|
7
|
+
|
8
|
+
def acknowledge
|
9
|
+
release
|
10
|
+
end
|
11
|
+
|
12
|
+
def requeue
|
13
|
+
release
|
14
|
+
Sidekiq.redis {|it| it.rpush queue, message }
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
require 'sidekiq/fetch'
|
3
|
+
|
4
|
+
class Sidekiq::LimitFetch
|
5
|
+
require_relative 'limit_fetch/semaphore'
|
6
|
+
require_relative 'limit_fetch/queue'
|
7
|
+
require_relative 'limit_fetch/unit_of_work'
|
8
|
+
|
9
|
+
Sidekiq.options[:fetch] = self
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
prepare_queues options
|
13
|
+
options[:strict] ? define_strict_queues : define_weighted_queues
|
14
|
+
end
|
15
|
+
|
16
|
+
def available_queues
|
17
|
+
fetch_queues.select(&:acquire)
|
18
|
+
end
|
19
|
+
|
20
|
+
def retrieve_work
|
21
|
+
queues = available_queues
|
22
|
+
queue_name, message = Sidekiq.redis do |it|
|
23
|
+
it.brpop *queues.map(&:full_name), Sidekiq::Fetcher::TIMEOUT
|
24
|
+
end
|
25
|
+
|
26
|
+
if message
|
27
|
+
queue = queues.find {|it| it.full_name == queue_name }
|
28
|
+
queues.delete queue
|
29
|
+
|
30
|
+
UnitOfWork.new queue, message
|
31
|
+
end
|
32
|
+
ensure
|
33
|
+
queues.each(&:release) if queues
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def prepare_queues(options)
|
39
|
+
cache = {}
|
40
|
+
limits = options[:limits] || {}
|
41
|
+
|
42
|
+
@queues = options[:queues].map do |name|
|
43
|
+
cache[name] ||= Queue.new name, limits[name]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def define_strict_queues
|
48
|
+
@queues.uniq!
|
49
|
+
def fetch_queues
|
50
|
+
@queues
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_weighted_queues
|
55
|
+
def fetch_queues
|
56
|
+
@queues.shuffle.uniq
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = 'sidekiq-limit_fetch'
|
3
|
+
gem.version = '0.2'
|
4
|
+
gem.authors = 'brainopia'
|
5
|
+
gem.email = 'brainopia@evilmartians.com'
|
6
|
+
gem.summary = 'Sidekig strategy to support queue limits'
|
7
|
+
gem.homepage = 'https://github.com/brainopia/sidekiq-limit_fetch'
|
8
|
+
gem.description = <<-DESCRIPTION
|
9
|
+
Sidekig strategy to restrict number of workers
|
10
|
+
which are able to run specified queues simultaneously.
|
11
|
+
DESCRIPTION
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($/)
|
14
|
+
gem.test_files = gem.files.grep %r{^spec/}
|
15
|
+
gem.require_paths = %w(lib)
|
16
|
+
|
17
|
+
gem.add_dependency 'sidekiq', '>= 2.6.3'
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'sidekiq/limit_fetch'
|
2
|
+
|
3
|
+
describe Sidekiq::LimitFetch do
|
4
|
+
before :each do
|
5
|
+
Sidekiq.redis do |it|
|
6
|
+
it.del 'queue:example'
|
7
|
+
it.rpush 'queue:example', 'task'
|
8
|
+
it.expire 'queue:example', 30
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def queues(fetcher)
|
13
|
+
fetcher.available_queues.map(&:full_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def new_fetcher(options={})
|
17
|
+
described_class.new options.merge queues: %w(example example example2 example2)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should retrieve weighted queues' do
|
21
|
+
fetcher = new_fetcher
|
22
|
+
queues(fetcher).should =~ %w(queue:example queue:example2)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should retrieve strictly ordered queues' do
|
26
|
+
fetcher = new_fetcher strict: true
|
27
|
+
queues(fetcher).should == %w(queue:example queue:example2)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should retrieve limited queues' do
|
31
|
+
fetcher = new_fetcher strict: true, limits: { 'example' => 2 }
|
32
|
+
queues = -> { fetcher.available_queues }
|
33
|
+
|
34
|
+
queues1 = queues.call
|
35
|
+
queues2 = queues.call
|
36
|
+
queues1.should have(2).items
|
37
|
+
queues2.should have(2).items
|
38
|
+
queues.call.should have(1).items
|
39
|
+
|
40
|
+
queues1.each(&:release)
|
41
|
+
queues.call.should have(2).items
|
42
|
+
queues.call.should have(1).items
|
43
|
+
|
44
|
+
queues2.each(&:release)
|
45
|
+
queues.call.should have(2).items
|
46
|
+
queues.call.should have(1).items
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should acquire lock on queue for excecution' do
|
50
|
+
fetcher = new_fetcher limits: { 'example' => 1, 'example2' => 1 }
|
51
|
+
work = fetcher.retrieve_work
|
52
|
+
work.message.should == 'task'
|
53
|
+
work.queue.should == 'queue:example'
|
54
|
+
work.queue_name.should == 'example'
|
55
|
+
|
56
|
+
queues = fetcher.available_queues
|
57
|
+
queues.should have(1).item
|
58
|
+
queues.each(&:release)
|
59
|
+
|
60
|
+
work.requeue
|
61
|
+
work = fetcher.retrieve_work
|
62
|
+
work.message.should == 'task'
|
63
|
+
work.acknowledge
|
64
|
+
|
65
|
+
fetcher.available_queues.should have(2).items
|
66
|
+
end
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sidekiq-limit_fetch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- brainopia
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sidekiq
|
16
|
+
prerelease: false
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.6.3
|
22
|
+
none: false
|
23
|
+
type: :runtime
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ! '>='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 2.6.3
|
29
|
+
none: false
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
prerelease: false
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
none: false
|
39
|
+
type: :development
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
none: false
|
46
|
+
description: ! " Sidekig strategy to restrict number of workers\n which are
|
47
|
+
able to run specified queues simultaneously.\n"
|
48
|
+
email: brainopia@evilmartians.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/sidekiq/limit_fetch.rb
|
59
|
+
- lib/sidekiq/limit_fetch/queue.rb
|
60
|
+
- lib/sidekiq/limit_fetch/semaphore.rb
|
61
|
+
- lib/sidekiq/limit_fetch/unit_of_work.rb
|
62
|
+
- sidekiq-limit_fetch.gemspec
|
63
|
+
- spec/integration_spec.rb
|
64
|
+
homepage: https://github.com/brainopia/sidekiq-limit_fetch
|
65
|
+
licenses: []
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
none: false
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
none: false
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.8.24
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Sidekig strategy to support queue limits
|
88
|
+
test_files:
|
89
|
+
- spec/integration_spec.rb
|