rcgt-sidekiq-limit_fetch 3.4.1

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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +161 -0
  8. data/Rakefile +12 -0
  9. data/bench/compare.rb +52 -0
  10. data/demo/Gemfile +7 -0
  11. data/demo/README.md +37 -0
  12. data/demo/Rakefile +99 -0
  13. data/demo/app/workers/a_worker.rb +8 -0
  14. data/demo/app/workers/b_worker.rb +8 -0
  15. data/demo/app/workers/c_worker.rb +9 -0
  16. data/demo/app/workers/fast_worker.rb +8 -0
  17. data/demo/app/workers/slow_worker.rb +8 -0
  18. data/demo/config/application.rb +11 -0
  19. data/demo/config/boot.rb +2 -0
  20. data/demo/config/environment.rb +2 -0
  21. data/demo/config/environments/development.rb +9 -0
  22. data/lib/sidekiq/extensions/manager.rb +16 -0
  23. data/lib/sidekiq/extensions/queue.rb +23 -0
  24. data/lib/sidekiq/limit_fetch/global/monitor.rb +72 -0
  25. data/lib/sidekiq/limit_fetch/global/selector.rb +125 -0
  26. data/lib/sidekiq/limit_fetch/global/semaphore.rb +175 -0
  27. data/lib/sidekiq/limit_fetch/instances.rb +19 -0
  28. data/lib/sidekiq/limit_fetch/queues.rb +124 -0
  29. data/lib/sidekiq/limit_fetch/unit_of_work.rb +24 -0
  30. data/lib/sidekiq/limit_fetch.rb +58 -0
  31. data/lib/sidekiq-limit_fetch.rb +1 -0
  32. data/rcgt-sidekiq-limit_fetch.gemspec +24 -0
  33. data/spec/sidekiq/extensions/queue_spec.rb +94 -0
  34. data/spec/sidekiq/limit_fetch/global/monitor_spec.rb +33 -0
  35. data/spec/sidekiq/limit_fetch/queues_spec.rb +100 -0
  36. data/spec/sidekiq/limit_fetch/semaphore_spec.rb +63 -0
  37. data/spec/sidekiq/limit_fetch_spec.rb +48 -0
  38. data/spec/spec_helper.rb +28 -0
  39. metadata +149 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e9ffb7cc431bd4e8626c7691d2203893ded60945489fab10c6ac4d18faf35a30
4
+ data.tar.gz: eec03e77491392d8e749f71202b2cada20f93accf29e98486cf9f3e31eeab3db
5
+ SHA512:
6
+ metadata.gz: d7c752b55bb5aaaf9e73b1e6cc02c738ffddd0f61a10303f040756d914b109b5ac4f382d0b2d7098ab0121fb0947a341eaf6271026cd417d9d0178c5e04d950a
7
+ data.tar.gz: 7e8d5cb9932ba6539b6fabd66c9f6e1842818aa99e3af395fef991eec5cb704f10efd3490fb06ae310cb50d10acb8ac9e5baefe23d855b7345af944d544931e7
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ pkg/
3
+ .bundle/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper --color
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.1
4
+ - 2.2.5
5
+ - jruby-9.1.5.0
6
+ services:
7
+ - redis-server
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'sidekiq'
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,161 @@
1
+ ## Description
2
+
3
+ A fork of https://github.com/brainopia/sidekiq-limit_fetch
4
+
5
+ Sidekiq strategy to support a granular queue control –
6
+ limiting, pausing, blocking, querying.
7
+
8
+ [![Build Status](https://secure.travis-ci.org/brainopia/sidekiq-limit_fetch.svg)](http://travis-ci.org/brainopia/sidekiq-limit_fetch)
9
+ [![Gem Version](https://badge.fury.io/rb/sidekiq-limit_fetch.svg)](http://badge.fury.io/rb/sidekiq-limit_fetch)
10
+ [![Dependency Status](https://gemnasium.com/brainopia/sidekiq-limit_fetch.svg)](https://gemnasium.com/brainopia/sidekiq-limit_fetch)
11
+ [![Code Climate](https://codeclimate.com/github/brainopia/sidekiq-limit_fetch.svg)](https://codeclimate.com/github/brainopia/sidekiq-limit_fetch)
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'rcgt-sidekiq-limit_fetch'
18
+
19
+ ### Requirements
20
+
21
+ **Important note:** At this moment, `sidekiq-limit_fetch` is incompatible with
22
+ - sidekiq pro's `reliable_fetch`
23
+ - `sidekiq-rate-limiter`
24
+ - any other plugin that rewrites fetch strategy of sidekiq.
25
+
26
+ ## Usage
27
+
28
+ ### Require
29
+ You must `require 'sidekiq-limit_fetch'` if it isn't already. It will not work until then.
30
+
31
+ ### Limits
32
+
33
+ Specify limits which you want to place on queues inside sidekiq.yml:
34
+
35
+ ```yaml
36
+ :limits:
37
+ queue_name1: 5
38
+ queue_name2: 10
39
+ ```
40
+
41
+ Or set it dynamically in your code:
42
+ ```ruby
43
+ Sidekiq::Queue['queue_name1'].limit = 5
44
+ Sidekiq::Queue['queue_name2'].limit = 10
45
+ ```
46
+
47
+ In these examples, tasks for the ```queue_name1``` will be run by at most 5
48
+ workers at the same time and the ```queue_name2``` will have no more than 10
49
+ workers simultaneously.
50
+
51
+ Ability to set limits dynamically allows you to resize worker
52
+ distribution among queues any time you want.
53
+
54
+ ### Limits per process
55
+
56
+ If you use multiple sidekiq processes then you can specify limits per process:
57
+
58
+ ```yaml
59
+ :process_limits:
60
+ queue_name: 2
61
+ ```
62
+
63
+ Or set it in your code:
64
+
65
+ ```ruby
66
+ Sidekiq::Queue['queue_name'].process_limit = 2
67
+ ```
68
+
69
+ ### Busy workers by queue
70
+
71
+ You can see how many workers currently handling a queue:
72
+
73
+ ```ruby
74
+ Sidekiq::Queue['name'].busy # number of busy workers
75
+ ```
76
+
77
+ ### Pauses
78
+
79
+ You can also pause your queues temporarily. Upon continuing their limits
80
+ will be preserved.
81
+
82
+ ```ruby
83
+ Sidekiq::Queue['name'].pause # prevents workers from running tasks from this queue
84
+ Sidekiq::Queue['name'].paused? # => true
85
+ Sidekiq::Queue['name'].unpause # allows workers to use the queue
86
+ Sidekiq::Queue['name'].pause_for_ms(1000) # will pause for a second
87
+ ```
88
+
89
+ ### Blocking queue mode
90
+
91
+ If you use strict queue ordering (it will be used if you don't specify queue weights)
92
+ then you can set blocking status for queues. It means if a blocking
93
+ queue task is executing then no new task from lesser priority queues will
94
+ be ran. Eg,
95
+
96
+ ```yaml
97
+ :queues:
98
+ - a
99
+ - b
100
+ - c
101
+ :blocking:
102
+ - b
103
+ ```
104
+
105
+ In this case when a task for `b` queue is ran no new task from `c` queue
106
+ will be started.
107
+
108
+ You can also enable and disable blocking mode for queues on the fly:
109
+
110
+ ```ruby
111
+ Sidekiq::Queue['name'].block
112
+ Sidekiq::Queue['name'].blocking? # => true
113
+ Sidekiq::Queue['name'].unblock
114
+ ```
115
+
116
+ ### Advanced blocking queues
117
+
118
+ You can also block on array of queues. It means when any of them is
119
+ running only queues higher and queues from their blocking group can
120
+ run. It will be easier to understand with an example:
121
+
122
+ ```yaml
123
+ :queues:
124
+ - a
125
+ - b
126
+ - c
127
+ - d
128
+ :blocking:
129
+ - [b, c]
130
+ ```
131
+
132
+ In this case tasks from `d` will be blocked when a task from queue `b` or `c` is executed.
133
+
134
+ You can dynamically set exceptions for queue blocking:
135
+
136
+ ```ruby
137
+ Sidekiq::Queue['queue1'].block_except 'queue2'
138
+ ```
139
+
140
+ ### Dynamic queues
141
+
142
+ You can support dynamic queues (that are not listed in sidekiq.yml but
143
+ that have tasks pushed to them (usually with `Sidekiq::Client.push`)).
144
+
145
+ To use this mode you need to specify a following line in sidekiq.yml:
146
+
147
+ ```yaml
148
+ :dynamic: true
149
+ ```
150
+
151
+ Dynamic queues will be ran at the lowest priority.
152
+
153
+ ### Maintenance
154
+
155
+ If you use ```flushdb```, restart the sidekiq process to re-populate the dynamic configuration.
156
+
157
+ ### Thanks
158
+
159
+ <a href="https://evilmartians.com/?utm_source=sidekiq-limit_fetch">
160
+ <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
161
+
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default do
7
+ rspec = Rake::Task[:spec]
8
+ rspec.invoke
9
+ ENV['namespace'] = 'namespace'
10
+ rspec.reenable
11
+ rspec.invoke
12
+ end
data/bench/compare.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'benchmark'
2
+ require 'sidekiq/cli'
3
+ require 'sidekiq/api'
4
+
5
+ total = (ARGV.shift || 50).to_i
6
+ concurrency = ARGV.shift || 1
7
+ limit = ARGV.shift
8
+
9
+ if limit
10
+ limit = nil if limit == 'nil'
11
+
12
+ $:.unshift File.expand_path '../lib'
13
+ require 'sidekiq-limit_fetch'
14
+ Sidekiq::Queue['inline'].limit = limit
15
+ Sidekiq.redis {|it| it.del 'limit_fetch:probed:inline' }
16
+ Sidekiq::LimitFetch::Queues.send(:define_method, :set) {|*| }
17
+ end
18
+
19
+ Sidekiq::Queue.new('inline').clear
20
+
21
+ class FastJob
22
+ include Sidekiq::Worker
23
+ sidekiq_options queue: :inline
24
+
25
+ def perform(i)
26
+ puts "job N#{i} is finished"
27
+ end
28
+ end
29
+
30
+ class FinishJob
31
+ include Sidekiq::Worker
32
+ sidekiq_options queue: :inline
33
+
34
+ def perform
35
+ Process.kill 'INT', 0
36
+ end
37
+ end
38
+
39
+ total.times {|i| FastJob.perform_async i+1 }
40
+ FinishJob.perform_async
41
+
42
+ Sidekiq::CLI.instance.tap do |cli|
43
+ %w(validate! boot_system).each {|stub| cli.define_singleton_method(stub) {}}
44
+ cli.parse ['-q inline', '-q other', "-c #{concurrency}"]
45
+
46
+ puts Benchmark.measure {
47
+ begin
48
+ cli.run
49
+ rescue Exception
50
+ end
51
+ }
52
+ end
data/demo/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rails'
4
+ gem 'launchy'
5
+ gem 'sidekiq', github: 'mperham/sidekiq'
6
+ gem 'sidekiq-limit_fetch', path: '..'
7
+
data/demo/README.md ADDED
@@ -0,0 +1,37 @@
1
+ This is a demo rails app with a configured sidekiq-limit_fetch.
2
+
3
+ Its purpose is to check whether plugin works in certain situations.
4
+
5
+ Application is preconfigured with two workers:
6
+ - `app/workers/fast_worker.rb` which does `sleep 0.2`
7
+ - `app/workers/slow_worker.rb` which does `sleep 1`
8
+
9
+ There is also a rake task which can be invoked as `bundle exec rake demo:limit`:
10
+
11
+ - it prefills sidekiq tasks
12
+
13
+ ```ruby
14
+ 100.times do
15
+ SlowWorker.perform_async
16
+ FastWorker.perform_async
17
+ end
18
+ ```
19
+ - sets sidekiq config
20
+
21
+ ```yaml
22
+ :verbose: false
23
+ :concurrency: 4
24
+ :queues:
25
+ - slow
26
+ - fast
27
+ :limits:
28
+ slow: 1
29
+ ```
30
+
31
+ - and launches a sidekiq admin page with overview of queues in browser.
32
+ The page is set to live-poll so effects of limits can be seen directly.
33
+
34
+
35
+ To change simulation modify `Rakefile` or workers.
36
+
37
+ Any bugs related to the plugin should be demonstrated with a reproduction from this base app.
data/demo/Rakefile ADDED
@@ -0,0 +1,99 @@
1
+ require File.expand_path('../config/application', __FILE__)
2
+ Demo::Application.load_tasks
3
+
4
+ namespace :demo do
5
+ task limit: :environment do
6
+ puts '=> Creating sidekiq tasks'
7
+
8
+ 100.times do
9
+ SlowWorker.perform_async
10
+ FastWorker.perform_async
11
+ end
12
+
13
+ run_sidekiq_monitoring
14
+ run_sidekiq_workers config: <<-YAML
15
+ :verbose: false
16
+ :concurrency: 4
17
+ :queues:
18
+ - slow
19
+ - fast
20
+ :limits:
21
+ slow: 1
22
+ YAML
23
+ end
24
+
25
+ task blocking: :environment do
26
+ puts '=> Creating sidekiq tasks'
27
+
28
+ AWorker.perform_async
29
+ BWorker.perform_async
30
+ CWorker.perform_async
31
+
32
+ run_sidekiq_monitoring
33
+ run_sidekiq_workers config: <<-YAML
34
+ :verbose: false
35
+ :concurrency: 4
36
+ :queues:
37
+ - a
38
+ - b
39
+ - c
40
+ :blocking:
41
+ - a
42
+ YAML
43
+ end
44
+
45
+ task advanced_blocking: :environment do
46
+ puts '=> Creating sidekiq tasks'
47
+
48
+ AWorker.perform_async
49
+ BWorker.perform_async
50
+ CWorker.perform_async
51
+
52
+ run_sidekiq_monitoring
53
+ run_sidekiq_workers config: <<-YAML
54
+ :verbose: false
55
+ :concurrency: 4
56
+ :queues:
57
+ - a
58
+ - b
59
+ - c
60
+ :blocking:
61
+ - [a, b]
62
+ YAML
63
+ end
64
+ def with_sidekiq_config(config)
65
+ whitespace_offset = config[/\A */].size
66
+ config.gsub! /^ {#{whitespace_offset}}/, ''
67
+
68
+ puts "=> Use sidekiq config:\n#{config}"
69
+ File.write 'config/sidekiq.yml', config
70
+ yield
71
+ ensure
72
+ FileUtils.rm 'config/sidekiq.yml'
73
+ end
74
+
75
+ def run_sidekiq_monitoring
76
+ require 'sidekiq/web'
77
+ Thread.new do
78
+ Rack::Server.start app: Sidekiq::Web, Port: 3000
79
+ end
80
+ sleep 1
81
+ Launchy.open 'http://127.0.0.1:3000/busy?poll=true'
82
+ end
83
+
84
+ def run_sidekiq_workers(options)
85
+ require 'sidekiq/cli'
86
+ cli = Sidekiq::CLI.instance
87
+
88
+ %w(validate! boot_system).each do |stub|
89
+ cli.define_singleton_method(stub) {}
90
+ end
91
+
92
+ with_sidekiq_config options[:config] do
93
+ cli.send :setup_options, []
94
+ end
95
+
96
+ cli.run
97
+ end
98
+ end
99
+
@@ -0,0 +1,8 @@
1
+ class AWorker
2
+ include Sidekiq::Worker
3
+ sidekiq_options queue: :a
4
+
5
+ def perform
6
+ sleep 10
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ class BWorker
2
+ include Sidekiq::Worker
3
+ sidekiq_options queue: :b
4
+
5
+ def perform
6
+ sleep 10
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ class CWorker
2
+ include Sidekiq::Worker
3
+ sidekiq_options queue: :c
4
+
5
+ def perform
6
+ sleep 10
7
+ end
8
+ end
9
+
@@ -0,0 +1,8 @@
1
+ class FastWorker
2
+ include Sidekiq::Worker
3
+ sidekiq_options queue: :fast
4
+
5
+ def perform
6
+ sleep 0.2
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ class SlowWorker
2
+ include Sidekiq::Worker
3
+ sidekiq_options queue: :slow
4
+
5
+ def perform
6
+ sleep 1
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ require File.expand_path('../boot', __FILE__)
2
+
3
+ require 'action_controller/railtie'
4
+ require 'action_mailer/railtie'
5
+ require 'sprockets/railtie'
6
+
7
+ Bundler.require(:default, Rails.env)
8
+
9
+ module Demo
10
+ Application = Class.new Rails::Application
11
+ end
@@ -0,0 +1,2 @@
1
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
2
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
@@ -0,0 +1,2 @@
1
+ require File.expand_path('../application', __FILE__)
2
+ Demo::Application.initialize!
@@ -0,0 +1,9 @@
1
+ Demo::Application.configure do
2
+ config.cache_classes = false
3
+ config.eager_load = false
4
+ config.consider_all_requests_local = true
5
+ config.action_controller.perform_caching = false
6
+ config.action_mailer.raise_delivery_errors = false
7
+ config.active_support.deprecation = :log
8
+ config.assets.debug = true
9
+ end
@@ -0,0 +1,16 @@
1
+ class Sidekiq::Manager
2
+ module InitLimitFetch
3
+ def initialize(options={})
4
+ options[:fetch] = Sidekiq::LimitFetch
5
+ super
6
+ end
7
+
8
+ def start
9
+ Sidekiq::LimitFetch::Queues.start options
10
+ Sidekiq::LimitFetch::Global::Monitor.start!
11
+ super
12
+ end
13
+ end
14
+
15
+ prepend InitLimitFetch
16
+ end
@@ -0,0 +1,23 @@
1
+ module Sidekiq
2
+ class Queue
3
+ extend LimitFetch::Instances, Forwardable
4
+ attr_reader :rname
5
+
6
+ def_delegators :lock,
7
+ :limit, :limit=, :limit_changed?,
8
+ :process_limit, :process_limit=,
9
+ :acquire, :release,
10
+ :pause, :pause_for_ms, :unpause,
11
+ :block, :unblock,
12
+ :paused?, :blocking?,
13
+ :unblocked, :block_except,
14
+ :probed, :busy,
15
+ :increase_busy, :decrease_busy,
16
+ :local_busy?, :explain,
17
+ :remove_locks_except!
18
+
19
+ def lock
20
+ @lock ||= LimitFetch::Global::Semaphore.new name
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,72 @@
1
+ module Sidekiq::LimitFetch::Global
2
+ module Monitor
3
+ extend self
4
+
5
+ HEARTBEAT_PREFIX = 'limit:heartbeat:'
6
+ PROCESS_SET = 'limit:processes'
7
+ HEARTBEAT_TTL = 20
8
+ REFRESH_TIMEOUT = 5
9
+
10
+ def start!(ttl=HEARTBEAT_TTL, timeout=REFRESH_TIMEOUT)
11
+ Thread.new do
12
+ loop do
13
+ Sidekiq::LimitFetch.redis_retryable do
14
+ add_dynamic_queues
15
+ update_heartbeat ttl
16
+ invalidate_old_processes
17
+ end
18
+
19
+ sleep timeout
20
+ end
21
+ end
22
+ end
23
+
24
+ def all_processes
25
+ Sidekiq.redis {|it| it.smembers PROCESS_SET }
26
+ end
27
+
28
+ def old_processes
29
+ all_processes.reject do |process|
30
+ Sidekiq.redis {|it| it.get heartbeat_key process }
31
+ end
32
+ end
33
+
34
+ def remove_old_processes!
35
+ Sidekiq.redis do |it|
36
+ old_processes.each {|process| it.srem PROCESS_SET, process }
37
+ end
38
+ end
39
+
40
+ def add_dynamic_queues
41
+ queues = Sidekiq::LimitFetch::Queues
42
+ queues.add Sidekiq::Queue.all.map(&:name) if queues.dynamic?
43
+ end
44
+
45
+ private
46
+
47
+ def update_heartbeat(ttl)
48
+ Sidekiq.redis do |it|
49
+ it.multi do
50
+ it.set heartbeat_key, true
51
+ it.sadd PROCESS_SET, Selector.uuid
52
+ it.expire heartbeat_key, ttl
53
+ end
54
+ end
55
+ end
56
+
57
+ def invalidate_old_processes
58
+ Sidekiq.redis do |it|
59
+ remove_old_processes!
60
+ processes = all_processes
61
+
62
+ Sidekiq::Queue.instances.each do |queue|
63
+ queue.remove_locks_except! processes
64
+ end
65
+ end
66
+ end
67
+
68
+ def heartbeat_key(process=Selector.uuid)
69
+ HEARTBEAT_PREFIX + process
70
+ end
71
+ end
72
+ end