sidekiq-limit_fetch 0.4 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -21,7 +21,7 @@ Specify limits which you want to place on queues inside sidekiq.yml:
21
21
 
22
22
  Or set it dynamically in your code:
23
23
  ```ruby
24
- Sidekiq::Queue.new('queue_name1').limit = 5
24
+ Sidekiq::Queue['queue_name1'].limit = 5
25
25
  Sidekiq::Queue['queue_name2'].limit = 10
26
26
  ```
27
27
 
@@ -32,7 +32,22 @@ workers simultaneously.
32
32
  Ability to set limits dynamically allows you to resize worker
33
33
  distribution among queues any time you want.
34
34
 
35
- Limits are applied strictly for current process.
35
+ You can also pause your queues temporarely. Upon continuing their limits
36
+ will be preserved.
37
+
38
+ ```ruby
39
+ Sidekiq::Queue['name'].pause # prevents workers from running tasks from this queue
40
+ ...
41
+ Sidekiq::Queue['name'].continue # allows workers to use the queue with the same limit
42
+ ```
43
+
44
+ Limits are applied per process. In case you have several worker
45
+ processes and want to have global locks between them, you'll need to
46
+ enable global mode by setting global option, eg:
47
+
48
+ ```yaml
49
+ :global: true
50
+ ```
36
51
 
37
52
  Sponsored by [Evil Martians].
38
53
  [Evil Martians]: http://evilmartians.com/
@@ -0,0 +1,19 @@
1
+ module Sidekiq
2
+ class Queue
3
+ extend LimitFetch::Singleton, Forwardable
4
+
5
+ def_delegators :lock,
6
+ :limit, :limit=,
7
+ :acquire, :release,
8
+ :pause, :continue,
9
+ :busy
10
+
11
+ def lock
12
+ @lock ||= mode::Semaphore.new name
13
+ end
14
+
15
+ def mode
16
+ Sidekiq.options[:global] ? LimitFetch::Global : LimitFetch::Local
17
+ end
18
+ end
19
+ end
@@ -2,10 +2,14 @@ require 'sidekiq'
2
2
  require 'sidekiq/fetch'
3
3
 
4
4
  class Sidekiq::LimitFetch
5
- require_relative 'limit_fetch/semaphore'
6
5
  require_relative 'limit_fetch/unit_of_work'
7
6
  require_relative 'limit_fetch/singleton'
8
- require_relative 'limit_fetch/queue'
7
+ require_relative 'limit_fetch/queues'
8
+ require_relative 'limit_fetch/local/semaphore'
9
+ require_relative 'limit_fetch/local/selector'
10
+ require_relative 'limit_fetch/global/semaphore'
11
+ require_relative 'limit_fetch/global/selector'
12
+ require_relative 'extensions/queue'
9
13
 
10
14
  Sidekiq.options[:fetch] = self
11
15
 
@@ -14,55 +18,23 @@ class Sidekiq::LimitFetch
14
18
  end
15
19
 
16
20
  def initialize(options)
17
- prepare_queues options
18
- options[:strict] ? define_strict_queues : define_weighted_queues
19
- end
20
-
21
- def available_queues
22
- fetch_queues.select(&:acquire)
21
+ @queues = Queues.new options
23
22
  end
24
23
 
25
24
  def retrieve_work
26
- queues = available_queues
27
-
28
- if queues.empty?
29
- sleep Sidekiq::Fetcher::TIMEOUT
30
- return
31
- end
32
-
33
- queue_name, message = Sidekiq.redis do |it|
34
- it.brpop *queues.map(&:full_name), Sidekiq::Fetcher::TIMEOUT
35
- end
36
-
37
- if message
38
- queue = queues.delete queues.find {|it| it.full_name == queue_name }
39
- UnitOfWork.new queue, message
40
- end
41
- ensure
42
- queues.each(&:release) if queues
25
+ queue, message = fetch_message
26
+ UnitOfWork.new queue, message if message
43
27
  end
44
28
 
45
29
  private
46
30
 
47
- def prepare_queues(options)
48
- limits = options[:limits] || {}
49
- @queues = options[:queues].map do |name|
50
- Sidekiq::Queue.new(name).tap do |it|
51
- it.limit = limits[name] if limits[name]
52
- end
53
- end
54
- end
55
-
56
- def define_strict_queues
57
- @queues.uniq!
58
- def fetch_queues
59
- @queues
60
- end
31
+ def fetch_message
32
+ queue, _ = redis_blpop *@queues.acquire, Sidekiq::Fetcher::TIMEOUT
33
+ ensure
34
+ @queues.release_except queue
61
35
  end
62
36
 
63
- def define_weighted_queues
64
- def fetch_queues
65
- @queues.shuffle.uniq
66
- end
37
+ def redis_blpop(*args)
38
+ Sidekiq.redis {|it| it.blpop *args }
67
39
  end
68
40
  end
@@ -0,0 +1,92 @@
1
+ module Sidekiq::LimitFetch::Global
2
+ module Selector
3
+ extend self
4
+
5
+ def acquire(queues)
6
+ redis_eval :acquire, [namespace, uuid, queues]
7
+ end
8
+
9
+ def release(queues)
10
+ redis_eval :release, [namespace, uuid, queues]
11
+ end
12
+
13
+ private
14
+
15
+ def namespace
16
+ @namespace ||= begin
17
+ namespace = Sidekiq.options[:namespace]
18
+ namespace + ':' if namespace
19
+ end
20
+ end
21
+
22
+ def uuid
23
+ @uuid ||= SecureRandom.uuid
24
+ end
25
+
26
+ def redis_eval(script_name, args)
27
+ Sidekiq.redis do |it|
28
+ begin
29
+ it.evalsha send("redis_#{script_name}_sha"), argv: args
30
+ rescue Redis::CommandError => error
31
+ raise unless error.message.include? 'NOSCRIPT'
32
+ it.eval send("redis_#{script_name}_script"), argv: args
33
+ end
34
+ end
35
+ end
36
+
37
+ def redis_acquire_sha
38
+ @acquire_sha ||= Digest::SHA1.hexdigest redis_acquire_script
39
+ end
40
+
41
+ def redis_release_sha
42
+ @release_sha ||= Digest::SHA1.hexdigest redis_release_script
43
+ end
44
+
45
+ def redis_acquire_script
46
+ <<-LUA
47
+ local namespace = table.remove(ARGV, 1)..'limit_fetch:'
48
+ local worker_name = table.remove(ARGV, 1)
49
+ local queues = ARGV
50
+ local available = {}
51
+
52
+ for _, queue in ipairs(queues) do
53
+ local busy_key = namespace..'busy:'..queue
54
+ local pause_key = namespace..'pause:'..queue
55
+ local paused = redis.call('get', pause_key)
56
+
57
+ if not paused then
58
+ local limit_key = namespace..'limit:'..queue
59
+ local queue_limit = tonumber(redis.call('get', limit_key))
60
+
61
+ if queue_limit then
62
+ local queue_locks = redis.call('llen', busy_key)
63
+
64
+ if queue_limit > queue_locks then
65
+ redis.call('rpush', busy_key, worker_name)
66
+ table.insert(available, queue)
67
+ end
68
+ else
69
+ redis.call('rpush', busy_key, worker_name)
70
+ table.insert(available, queue)
71
+ end
72
+ end
73
+ end
74
+
75
+ return available
76
+ LUA
77
+ end
78
+
79
+ def redis_release_script
80
+ <<-LUA
81
+ local namespace = table.remove(ARGV, 1)..'limit_fetch:'
82
+ local worker_name = table.remove(ARGV, 1)
83
+ local queues = ARGV
84
+
85
+ for _, queue in ipairs(queues) do
86
+ local busy_key = namespace..'busy:'..queue
87
+ redis.call('lrem', busy_key, 1, worker_name)
88
+ end
89
+ LUA
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,38 @@
1
+ module Sidekiq::LimitFetch::Global
2
+ class Semaphore
3
+ PREFIX = 'limit_fetch'
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+
9
+ def limit
10
+ value = Sidekiq.redis {|it| it.get "#{PREFIX}:limit:#@name" }
11
+ value.to_i if value
12
+ end
13
+
14
+ def limit=(value)
15
+ Sidekiq.redis {|it| it.set "#{PREFIX}:limit:#@name", value }
16
+ end
17
+
18
+ def acquire
19
+ Selector.acquire([@name]).size > 0
20
+ end
21
+
22
+ def release
23
+ Selector.release [@name]
24
+ end
25
+
26
+ def busy
27
+ Sidekiq.redis {|it| it.llen "#{PREFIX}:busy:#@name" }
28
+ end
29
+
30
+ def pause
31
+ Sidekiq.redis {|it| it.set "#{PREFIX}:pause:#@name", true }
32
+ end
33
+
34
+ def continue
35
+ Sidekiq.redis {|it| it.del "#{PREFIX}:pause:#@name" }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ module Sidekiq::LimitFetch::Local
2
+ module Selector
3
+ extend self
4
+
5
+ def acquire(names)
6
+ queues(names).select(&:acquire).map(&:name)
7
+ end
8
+
9
+ def release(names)
10
+ queues(names).each(&:release)
11
+ end
12
+
13
+ private
14
+
15
+ def queues(names)
16
+ names.map {|name| Sidekiq::Queue[name] }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ module Sidekiq::LimitFetch::Local
2
+ class Semaphore
3
+ attr_reader :limit, :busy
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ @lock = Mutex.new
8
+ @busy = 0
9
+ @paused = false
10
+ end
11
+
12
+ def limit=(value)
13
+ @lock.synchronize do
14
+ @limit = value
15
+ end
16
+ end
17
+
18
+ def acquire
19
+ return if @paused
20
+ @lock.synchronize do
21
+ @busy += 1 if not @limit or @limit > @busy
22
+ end
23
+ end
24
+
25
+ def release
26
+ @lock.synchronize do
27
+ @busy -= 1
28
+ end
29
+ end
30
+
31
+ def pause
32
+ @paused = true
33
+ end
34
+
35
+ def continue
36
+ @paused = false
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,57 @@
1
+ class Sidekiq::LimitFetch
2
+ class Queues
3
+ THREAD_KEY = :acquired_queues
4
+ attr_reader :selector
5
+
6
+ def initialize(options)
7
+ @queues = options[:queues]
8
+ options[:strict] ? strict_order! : weighted_order!
9
+
10
+ set_selector options[:global]
11
+ set_limits options[:limits]
12
+ end
13
+
14
+ def acquire
15
+ @selector.acquire(ordered_queues)
16
+ .tap {|it| save it }
17
+ .map {|it| "queue:#{it}" }
18
+ end
19
+
20
+ def release_except(full_name)
21
+ @selector.release restore.delete_if {|name| full_name.to_s.include? name }
22
+ end
23
+
24
+ private
25
+
26
+ def set_selector(global)
27
+ @selector = global ? Global::Selector : Local::Selector
28
+ end
29
+
30
+ def set_limits(limits)
31
+ ordered_queues.each do |name|
32
+ Sidekiq::Queue[name].tap do |it|
33
+ it.limit = (limits || {})[name]
34
+ end
35
+ end
36
+ end
37
+
38
+ def strict_order!
39
+ @queues.uniq!
40
+ def ordered_queues; @queues end
41
+ end
42
+
43
+ def weighted_order!
44
+ def ordered_queues; @queues.shuffle.uniq end
45
+ end
46
+
47
+ def save(queues)
48
+ Thread.current[THREAD_KEY] = queues
49
+ end
50
+
51
+ def restore
52
+ Thread.current[THREAD_KEY]
53
+ ensure
54
+ Thread.current[THREAD_KEY] = nil
55
+ end
56
+ end
57
+ end
@@ -1,16 +1,12 @@
1
- Sidekiq::LimitFetch::UnitOfWork = Struct.new :queue_wrapper, :message do
2
- extend Forwardable
1
+ module Sidekiq
2
+ class LimitFetch::UnitOfWork < BasicFetch::UnitOfWork
3
+ def acknowledge
4
+ Queue[queue_name].release
5
+ end
3
6
 
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 }
7
+ def requeue
8
+ super
9
+ acknowledge
10
+ end
15
11
  end
16
12
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'sidekiq-limit_fetch'
3
- gem.version = '0.4'
3
+ gem.version = '0.6'
4
4
  gem.authors = 'brainopia'
5
5
  gem.email = 'brainopia@evilmartians.com'
6
6
  gem.summary = 'Sidekiq strategy to support queue limits'
@@ -14,6 +14,6 @@ Gem::Specification.new do |gem|
14
14
  gem.test_files = gem.files.grep %r{^spec/}
15
15
  gem.require_paths = %w(lib)
16
16
 
17
- gem.add_dependency 'sidekiq', '>= 2.6.3'
17
+ gem.add_dependency 'sidekiq', '>= 2.6.5'
18
18
  gem.add_development_dependency 'rspec'
19
19
  end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sidekiq::Queue do
4
+ context 'singleton' do
5
+ shared_examples :constructor do
6
+ it 'with default name' do
7
+ new_object = -> { described_class.send constructor }
8
+ new_object.call.should == new_object.call
9
+ end
10
+
11
+ it 'with given name' do
12
+ new_object = ->(name) { described_class.send constructor, name }
13
+ new_object.call('name').should == new_object.call('name')
14
+ end
15
+ end
16
+
17
+ context '.new' do
18
+ let(:constructor) { :new }
19
+ it_behaves_like :constructor
20
+ end
21
+
22
+ context '.[]' do
23
+ let(:constructor) { :[] }
24
+ it_behaves_like :constructor
25
+ end
26
+
27
+ context '#lock' do
28
+ let(:name) { 'example' }
29
+ let(:queue) { Sidekiq::Queue[name] }
30
+
31
+ shared_examples_for :lock do
32
+ it 'should be available' do
33
+ queue.acquire.should be
34
+ end
35
+
36
+ it 'should be pausable' do
37
+ queue.pause
38
+ queue.acquire.should_not be
39
+ end
40
+
41
+ it 'should be continuable' do
42
+ queue.pause
43
+ queue.continue
44
+ queue.acquire.should be
45
+ end
46
+
47
+ it 'should be limitable' do
48
+ queue.limit = 1
49
+ queue.acquire.should be
50
+ queue.acquire.should_not be
51
+ end
52
+
53
+ it 'should be resizable' do
54
+ queue.limit = 0
55
+ queue.acquire.should_not be
56
+ queue.limit = nil
57
+ queue.acquire.should be
58
+ end
59
+
60
+ it 'should be countable' do
61
+ queue.limit = 3
62
+ 5.times { queue.acquire }
63
+ queue.busy.should == 3
64
+ end
65
+
66
+ it 'should be releasable' do
67
+ queue.acquire
68
+ queue.busy.should == 1
69
+ queue.release
70
+ queue.busy.should == 0
71
+ end
72
+ end
73
+
74
+ context 'global' do
75
+ before(:all) { Sidekiq.options[:global] = true }
76
+ it_behaves_like :lock
77
+ end
78
+
79
+ context 'local' do
80
+ before(:all) { Sidekiq.options[:global] = false }
81
+ it_behaves_like :lock
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sidekiq::LimitFetch::Queues do
4
+ subject { described_class.new options }
5
+
6
+ let(:queues) { %w[queue1 queue2] }
7
+ let(:limits) {{ 'queue1' => 3 }}
8
+ let(:strict) { true }
9
+ let(:global) { false }
10
+
11
+ let(:options) do
12
+ { queues: queues, limits: limits, strict: strict, global: global }
13
+ end
14
+
15
+ after(:each ) do
16
+ Thread.current[:available_queues] = nil
17
+ end
18
+
19
+ shared_examples_for :selector do
20
+ it 'should acquire queues' do
21
+ subject.acquire
22
+ Sidekiq::Queue['queue1'].busy.should == 1
23
+ Sidekiq::Queue['queue2'].busy.should == 1
24
+ end
25
+
26
+ it 'should release queues' do
27
+ subject.acquire
28
+ subject.release_except nil
29
+ Sidekiq::Queue['queue1'].busy.should == 0
30
+ Sidekiq::Queue['queue2'].busy.should == 0
31
+ end
32
+
33
+ it 'should release queues except selected' do
34
+ subject.acquire
35
+ subject.release_except 'queue:queue1'
36
+ Sidekiq::Queue['queue1'].busy.should == 1
37
+ Sidekiq::Queue['queue2'].busy.should == 0
38
+ end
39
+ end
40
+
41
+ context 'without global flag' do
42
+ it_should_behave_like :selector
43
+
44
+ it 'without global flag should be local' do
45
+ subject.selector.should == Sidekiq::LimitFetch::Local::Selector
46
+ end
47
+ end
48
+
49
+ context 'with global flag' do
50
+ let(:global) { true }
51
+ it_should_behave_like :selector
52
+
53
+ it 'should use global selector' do
54
+ subject.selector.should == Sidekiq::LimitFetch::Global::Selector
55
+ end
56
+ end
57
+
58
+ it 'should set limits' do
59
+ subject
60
+ Sidekiq::Queue['queue1'].limit.should == 3
61
+ Sidekiq::Queue['queue2'].limit.should_not be
62
+ end
63
+
64
+ context 'without strict flag' do
65
+ let(:strict) { false }
66
+
67
+ it 'should retrieve weighted queues' do
68
+ subject.ordered_queues.should =~ %w(queue1 queue2)
69
+ end
70
+ end
71
+
72
+ it 'with strict flag should retrieve strictly ordered queues' do
73
+ subject.ordered_queues.should == %w(queue1 queue2)
74
+ end
75
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'semaphore' do
4
+ shared_examples_for :semaphore do
5
+ it 'should have no limit by default' do
6
+ subject.limit.should_not be
7
+ end
8
+
9
+ it 'should set limit' do
10
+ subject.limit = 4
11
+ subject.limit.should == 4
12
+ end
13
+
14
+ it 'should acquire and count active tasks' do
15
+ 3.times { subject.acquire }
16
+ subject.busy.should == 3
17
+ end
18
+
19
+ it 'should acquire tasks with regard to limit' do
20
+ subject.limit = 4
21
+ 6.times { subject.acquire }
22
+ subject.busy.should == 4
23
+ end
24
+
25
+ it 'should release active tasks' do
26
+ 6.times { subject.acquire }
27
+ 3.times { subject.release }
28
+ subject.busy.should == 3
29
+ end
30
+
31
+ it 'should pause tasks' do
32
+ 3.times { subject.acquire }
33
+ subject.pause
34
+ 2.times { subject.acquire }
35
+ subject.busy.should == 3
36
+ 2.times { subject.release }
37
+ subject.busy.should == 1
38
+ end
39
+
40
+ it 'should unpause tasks' do
41
+ subject.pause
42
+ 3.times { subject.acquire }
43
+ subject.continue
44
+ 2.times { subject.acquire }
45
+ subject.busy.should == 2
46
+ end
47
+ end
48
+
49
+ let(:name) { 'default' }
50
+
51
+ context 'local' do
52
+ subject { Sidekiq::LimitFetch::Local::Semaphore.new name }
53
+ it_behaves_like :semaphore
54
+ end
55
+
56
+ context 'global' do
57
+ subject { Sidekiq::LimitFetch::Global::Semaphore.new name }
58
+ it_behaves_like :semaphore
59
+
60
+ after :each do
61
+ Sidekiq.redis do |it|
62
+ it.del "limit_fetch:limit:#{name}"
63
+ it.del "limit_fetch:busy:#{name}"
64
+ it.del "limit_fetch:pause:#{name}"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sidekiq::LimitFetch do
4
+ before :each do
5
+ Sidekiq.redis do |it|
6
+ it.del 'queue:queue1'
7
+ it.rpush 'queue:queue1', 'task1'
8
+ it.rpush 'queue:queue1', 'task2'
9
+ it.expire 'queue:queue1', 30
10
+ end
11
+ end
12
+
13
+ subject { described_class.new options }
14
+ let(:options) {{ queues: queues, limits: limits, global: global }}
15
+ let(:queues) { %w(queue1 queue1 queue2 queue2) }
16
+ let(:limits) {{ 'queue1' => 1, 'queue2' => 2 }}
17
+
18
+ shared_examples_for :strategy do
19
+ it 'should acquire lock on queue for execution' do
20
+ work = subject.retrieve_work
21
+ work.queue_name.should == 'queue1'
22
+ work.message.should == 'task1'
23
+
24
+ subject.retrieve_work.should_not be
25
+ work.requeue
26
+
27
+ work = subject.retrieve_work
28
+ work.message.should == 'task2'
29
+
30
+ subject.retrieve_work.should_not be
31
+ work.acknowledge
32
+
33
+ work = subject.retrieve_work
34
+ work.message.should == 'task1'
35
+ end
36
+ end
37
+
38
+ context 'global' do
39
+ let(:global) { true }
40
+ it_behaves_like :strategy
41
+ end
42
+
43
+ context 'local' do
44
+ let(:global) { false }
45
+ it_behaves_like :strategy
46
+ end
47
+ end
@@ -3,5 +3,17 @@ require 'sidekiq/limit_fetch'
3
3
  RSpec.configure do |config|
4
4
  config.before :each do
5
5
  Sidekiq::Queue.instance_variable_set :@instances, {}
6
+ Sidekiq.options[:global] = defined?(global) ? global : nil
7
+
8
+ Sidekiq.redis do |it|
9
+ clean_redis = ->(queue) do
10
+ it.del "limit_fetch:limit:#{queue}"
11
+ it.del "limit_fetch:busy:#{queue}"
12
+ it.del "limit_fetch:pause:#{queue}"
13
+ end
14
+
15
+ clean_redis.call(name) if defined?(name)
16
+ queues.each(&clean_redis) if defined?(queues) and queues.is_a? Array
17
+ end
6
18
  end
7
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-limit_fetch
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.4'
4
+ version: '0.6'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,40 +9,40 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-17 00:00:00.000000000 Z
12
+ date: 2013-01-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sidekiq
16
- prerelease: false
17
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
18
  requirements:
19
19
  - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: 2.6.3
22
- none: false
21
+ version: 2.6.5
23
22
  type: :runtime
23
+ prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
25
26
  requirements:
26
27
  - - ! '>='
27
28
  - !ruby/object:Gem::Version
28
- version: 2.6.3
29
- none: false
29
+ version: 2.6.5
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: rspec
32
- prerelease: false
33
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
34
  requirements:
35
35
  - - ! '>='
36
36
  - !ruby/object:Gem::Version
37
37
  version: '0'
38
- none: false
39
38
  type: :development
39
+ prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
41
42
  requirements:
42
43
  - - ! '>='
43
44
  - !ruby/object:Gem::Version
44
45
  version: '0'
45
- none: false
46
46
  description: ! " Sidekiq strategy to restrict number of workers\n which are
47
47
  able to run specified queues simultaneously.\n"
48
48
  email: brainopia@evilmartians.com
@@ -55,14 +55,20 @@ files:
55
55
  - LICENSE.txt
56
56
  - README.md
57
57
  - Rakefile
58
+ - lib/sidekiq/extensions/queue.rb
58
59
  - lib/sidekiq/limit_fetch.rb
59
- - lib/sidekiq/limit_fetch/queue.rb
60
- - lib/sidekiq/limit_fetch/semaphore.rb
60
+ - lib/sidekiq/limit_fetch/global/selector.rb
61
+ - lib/sidekiq/limit_fetch/global/semaphore.rb
62
+ - lib/sidekiq/limit_fetch/local/selector.rb
63
+ - lib/sidekiq/limit_fetch/local/semaphore.rb
64
+ - lib/sidekiq/limit_fetch/queues.rb
61
65
  - lib/sidekiq/limit_fetch/singleton.rb
62
66
  - lib/sidekiq/limit_fetch/unit_of_work.rb
63
67
  - sidekiq-limit_fetch.gemspec
64
- - spec/limit_fetch_spec.rb
65
- - spec/sidekiq/limit_fetch/queue_spec.rb
68
+ - spec/sidekiq/extensions/queue_spec.rb
69
+ - spec/sidekiq/limit_fetch/queues_spec.rb
70
+ - spec/sidekiq/limit_fetch/semaphore_spec.rb
71
+ - spec/sidekiq/limit_fetch_spec.rb
66
72
  - spec/spec_helper.rb
67
73
  homepage: https://github.com/brainopia/sidekiq-limit_fetch
68
74
  licenses: []
@@ -71,17 +77,17 @@ rdoc_options: []
71
77
  require_paths:
72
78
  - lib
73
79
  required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
74
81
  requirements:
75
82
  - - ! '>='
76
83
  - !ruby/object:Gem::Version
77
84
  version: '0'
78
- none: false
79
85
  required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
80
87
  requirements:
81
88
  - - ! '>='
82
89
  - !ruby/object:Gem::Version
83
90
  version: '0'
84
- none: false
85
91
  requirements: []
86
92
  rubyforge_project:
87
93
  rubygems_version: 1.8.24
@@ -89,6 +95,8 @@ signing_key:
89
95
  specification_version: 3
90
96
  summary: Sidekiq strategy to support queue limits
91
97
  test_files:
92
- - spec/limit_fetch_spec.rb
93
- - spec/sidekiq/limit_fetch/queue_spec.rb
98
+ - spec/sidekiq/extensions/queue_spec.rb
99
+ - spec/sidekiq/limit_fetch/queues_spec.rb
100
+ - spec/sidekiq/limit_fetch/semaphore_spec.rb
101
+ - spec/sidekiq/limit_fetch_spec.rb
94
102
  - spec/spec_helper.rb
@@ -1,14 +0,0 @@
1
- module Sidekiq
2
- class Queue
3
- extend LimitFetch::Singleton, Forwardable
4
- def_delegators :lock, :limit, :limit=, :acquire, :release, :busy
5
-
6
- def full_name
7
- @rname
8
- end
9
-
10
- def lock
11
- @lock ||= LimitFetch::Semaphore.new
12
- end
13
- end
14
- end
@@ -1,26 +0,0 @@
1
- class Sidekiq::LimitFetch::Semaphore
2
- attr_reader :limit, :busy
3
-
4
- def initialize
5
- @lock = Mutex.new
6
- @busy = 0
7
- end
8
-
9
- def limit=(value)
10
- @lock.synchronize do
11
- @limit = value
12
- end
13
- end
14
-
15
- def acquire
16
- @lock.synchronize do
17
- @busy += 1 if not @limit or @limit > @busy
18
- end
19
- end
20
-
21
- def release
22
- @lock.synchronize do
23
- @busy -= 1
24
- end
25
- end
26
- end
@@ -1,81 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Sidekiq::LimitFetch do
4
- before :each do
5
- Sidekiq.redis do |it|
6
- it.del 'queue:example1'
7
- it.rpush 'queue:example1', 'task'
8
- it.expire 'queue:example1', 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(example1 example1 example2 example2)
18
- end
19
-
20
- it 'should retrieve weighted queues' do
21
- fetcher = new_fetcher
22
- queues(fetcher).should =~ %w(queue:example1 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:example1 queue:example2)
28
- end
29
-
30
- it 'should retrieve only available queues' do
31
- fetcher = new_fetcher strict: true, limits: { 'example1' => 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: { 'example1' => 1, 'example2' => 1 }
51
- work = fetcher.retrieve_work
52
- work.message.should == 'task'
53
- work.queue.should == 'queue:example1'
54
- work.queue_name.should == 'example1'
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
-
68
- it 'should set queue limits on the fly' do
69
- Sidekiq::Queue['example1'].limit = 1
70
- Sidekiq::Queue['example2'].limit = 2
71
-
72
- fetcher = new_fetcher
73
-
74
- fetcher.available_queues.should have(2).item
75
- fetcher.available_queues.should have(1).item
76
- fetcher.available_queues.should have(0).item
77
-
78
- Sidekiq::Queue['example1'].limit = 2
79
- fetcher.available_queues.should have(1).item
80
- end
81
- end
@@ -1,27 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Sidekiq::Queue do
4
- context 'singleton' do
5
- shared_examples :constructor do
6
- it 'with default name' do
7
- new_object = -> { described_class.send constructor }
8
- new_object.call.should == new_object.call
9
- end
10
-
11
- it 'with given name' do
12
- new_object = ->(name) { described_class.send constructor, name }
13
- new_object.call('name').should == new_object.call('name')
14
- end
15
- end
16
-
17
- context '.new' do
18
- let(:constructor) { :new }
19
- it_behaves_like :constructor
20
- end
21
-
22
- context '.[]' do
23
- let(:constructor) { :[] }
24
- it_behaves_like :constructor
25
- end
26
- end
27
- end