sidekiq-limit_fetch 0.4 → 0.6

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
@@ -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