sidekiq-qlimit 0.0.4
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.
- checksums.yaml +7 -0
- data/lib/sidekiq/qlimit/qlimit_fetch.rb +279 -0
- data/lib/sidekiq/qlimit/version.rb +8 -0
- data/lib/sidekiq/qlimit/views/index.html.erb +39 -0
- data/lib/sidekiq/qlimit/web.rb +22 -0
- data/lib/sidekiq/qlimit/web_extension.rb +19 -0
- data/lib/sidekiq/qlimit.rb +32 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d62aada6eb22eb9c8e3c23a875d538fcabf8613d
|
4
|
+
data.tar.gz: dcac1cc3a9a1e4d98b19ffebf7c717992a2c5863
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dff8269278b27266ac7b846042178d49fd53d089c0022ef623bd2100e5e46c941d926380ef307dcf0e19af5ee258407bb80b3f4dc2a9e045813c62bc58478a44
|
7
|
+
data.tar.gz: 81f1586e34d2697ed9ae2575e70d0891aea34f88d2c3bcd4fd3ad9095a6f4245f88f0bf0dc5b44ab4e26744c7d0e651acbf410c97f74c16bef310848f08889a7
|
@@ -0,0 +1,279 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "celluloid" if Sidekiq::VERSION < "4.0.0"
|
4
|
+
require "sidekiq"
|
5
|
+
require "sidekiq/fetch"
|
6
|
+
|
7
|
+
module Sidekiq
|
8
|
+
module Qlimit
|
9
|
+
##
|
10
|
+
# Throttled version of `Sidekiq::QlimitFetch` fetcher strategy.
|
11
|
+
#
|
12
|
+
#
|
13
|
+
# Just add somewhere in your bootstrap:
|
14
|
+
#
|
15
|
+
# require "sidekiq/qlimit"
|
16
|
+
# Sidekiq::Qlimit.setup!
|
17
|
+
#
|
18
|
+
# Establish max # of total workers per queue
|
19
|
+
#
|
20
|
+
# sidekiq.yml
|
21
|
+
# --
|
22
|
+
#
|
23
|
+
# :qlimit:
|
24
|
+
# queue_name_1: 2
|
25
|
+
# queue_name_2: 4
|
26
|
+
#
|
27
|
+
#--
|
28
|
+
# TODO: Store current limits in redis and read from redis to display
|
29
|
+
#++
|
30
|
+
class QlimitFetch < ::Sidekiq::BasicFetch
|
31
|
+
|
32
|
+
# Redis Script SHA tracking
|
33
|
+
@@qlimit_increment_sha = ""
|
34
|
+
@@qlimit_decrement_sha = ""
|
35
|
+
|
36
|
+
# Qlimit aware UnitOfWork
|
37
|
+
UnitOfWork = Struct.new(:queue, :job) do
|
38
|
+
def acknowledge
|
39
|
+
# Reduce qlimit on acknowledge
|
40
|
+
QlimitFetch.qlimit_decrement(queue_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def queue_name
|
44
|
+
queue.sub(/.*queue:/, ''.freeze)
|
45
|
+
end
|
46
|
+
|
47
|
+
def requeue
|
48
|
+
Sidekiq.redis do |conn|
|
49
|
+
conn.rpush("queue:#{queue_name}", job)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Modified Initialize Function - Reads :qlimit from config source such as sidekiq.yml
|
56
|
+
def initialize(options)
|
57
|
+
super(options)
|
58
|
+
|
59
|
+
# puts options
|
60
|
+
# {:queues=>["unlimited", "limited"], :labels=>[], :concurrency=>5, :require=>".", :environment=>nil, :timeout=>8, :poll_interval_average=>nil, :average_scheduled_poll_interval=>15, :error_handlers=>[#<Sidekiq::ExceptionHandler::Logger:0x007fda0c30fe38>], :lifecycle_events=>{:startup=>[], :quiet=>[], :shutdown=>[]}, :dead_max_jobs=>10000, :dead_timeout_in_seconds=>15552000, :pidfile=>"tmp/pids/sidekiq.pid", :qlimit=>[{"limited"=>2}, {"fake"=>3}], :config_file=>"config/sidekiq.yml", :strict=>true, :fetch=>Sidekiq::Qlimit::QlimitFetch, :tag=>"example"}
|
61
|
+
|
62
|
+
|
63
|
+
# Get our limits
|
64
|
+
@per_queue_limits = {}
|
65
|
+
unless options[:qlimit].nil?
|
66
|
+
options[:qlimit].each do |limit_hash|
|
67
|
+
limit_hash.each do |k, v|
|
68
|
+
@per_queue_limits[k] = v
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# TODO: Store current limits in redis and read from redis to display
|
74
|
+
|
75
|
+
QlimitFetch.qlimit_script_load
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
##
|
83
|
+
# Returns an array of queue names that are NOT too busy
|
84
|
+
def qualifying_queues
|
85
|
+
# Working copy of @queues list
|
86
|
+
allowed_queues = @queues.dup
|
87
|
+
|
88
|
+
# Remove any queue which has hit the maximum number of concurrent jobs
|
89
|
+
# NOTE: Could remove a queue for which a job has just finished, but we'll catch that job on the next loop
|
90
|
+
@per_queue_limits.each do |k, v|
|
91
|
+
Sidekiq.logger.debug("Checking #{k} => #{v}")
|
92
|
+
Sidekiq.redis do |conn|
|
93
|
+
jobs_in_queue = QlimitFetch.qlimit_get(k)
|
94
|
+
if jobs_in_queue.to_i >= v # detected a maximum concurrent case
|
95
|
+
allowed_queues.delete("queue:#{k}")
|
96
|
+
Sidekiq.logger.debug("Remove: queue:#{k}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
Sidekiq.logger.debug("Allowed Queues: #{allowed_queues}")
|
103
|
+
|
104
|
+
# Follow original sidekiq fetch strategy
|
105
|
+
if @strictly_ordered_queues
|
106
|
+
allowed_queues
|
107
|
+
else
|
108
|
+
allowed_queues = allowed_queues.shuffle.uniq
|
109
|
+
allowed_queues << TIMEOUT # brpop should Wait X number of seconds before returning nil
|
110
|
+
allowed_queues
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
##
|
116
|
+
# Returns a "UnitOfWork" from a qualifying queue if available
|
117
|
+
def retrieve_work
|
118
|
+
work_text = Sidekiq.redis { |conn| conn.brpop(*qualifying_queues) }
|
119
|
+
work = UnitOfWork.new(*work_text) if work_text
|
120
|
+
|
121
|
+
if work.nil?
|
122
|
+
Sidekiq.logger.debug("No Work")
|
123
|
+
return
|
124
|
+
end
|
125
|
+
|
126
|
+
# We hack around the simultaneous zero starting problem by incrementing an expiring counter
|
127
|
+
okay_to_continue = QlimitFetch.qlimit_increment(work.queue_name, @per_queue_limits[work.queue_name])
|
128
|
+
Sidekiq.logger.debug("QlimitIncrement Result => #{okay_to_continue}")
|
129
|
+
|
130
|
+
if okay_to_continue
|
131
|
+
Sidekiq.logger.debug("Perform #{work}")
|
132
|
+
return work
|
133
|
+
else
|
134
|
+
Sidekiq.logger.debug("Requeue #{work}")
|
135
|
+
Sidekiq.redis { |conn| conn.lpush(work.queue, work.job) }
|
136
|
+
return nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
##
|
142
|
+
# Returns a "UnitOfWork" from a qualifying queue if available
|
143
|
+
def self.qlimit_script_load
|
144
|
+
# Note:
|
145
|
+
# This is not theadsafe. Instead we *blindly* increment if current < max.
|
146
|
+
# We assume there is little or no penalty for running a few too many workers
|
147
|
+
qlimit_increment_script = <<-EOF
|
148
|
+
local max = tonumber(ARGV[1])
|
149
|
+
local current = tonumber(redis.call('get',KEYS[1]))
|
150
|
+
local current_i = 0
|
151
|
+
if nil == current then
|
152
|
+
current_i = 0
|
153
|
+
else
|
154
|
+
current_i = tonumber(current)
|
155
|
+
end
|
156
|
+
|
157
|
+
if current_i < max then
|
158
|
+
redis.call('incr',KEYS[1])
|
159
|
+
redis.call('expire',KEYS[1], 14400)
|
160
|
+
return true
|
161
|
+
else
|
162
|
+
return false
|
163
|
+
end
|
164
|
+
EOF
|
165
|
+
|
166
|
+
# Note:
|
167
|
+
# If we zero a counter or reduce a limit, we could go "negative" on a decrement.
|
168
|
+
# Limit minimum at 0 which is self correcting.
|
169
|
+
qlimit_decrement_script = <<-EOF
|
170
|
+
redis.call('decrby', KEYS[1], ARGV[1])
|
171
|
+
local current = tonumber(redis.call('get',KEYS[1]))
|
172
|
+
if current < 0 then
|
173
|
+
redis.call('set',KEYS[1], 0)
|
174
|
+
end
|
175
|
+
redis.call('expire',KEYS[1], 14400)
|
176
|
+
EOF
|
177
|
+
|
178
|
+
Sidekiq.redis do |conn|
|
179
|
+
@@qlimit_increment_sha = conn.script(:load, qlimit_increment_script)
|
180
|
+
@@qlimit_decrement_sha = conn.script(:load, qlimit_decrement_script)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Returns a hash of current count of running jobs in queues
|
186
|
+
#
|
187
|
+
# Example: { "queue1": 123, "queue2": 456 }
|
188
|
+
def self.qlimit_hash
|
189
|
+
qlimits = {}
|
190
|
+
Sidekiq.redis do |conn|
|
191
|
+
conn.scan_each(match: "qlimit:*") do |key|
|
192
|
+
qkey = key.sub(/.*qlimit:/, ''.freeze)
|
193
|
+
qvalue = conn.get(key)
|
194
|
+
qvalue ||= 0
|
195
|
+
qlimits[qkey] ||= qvalue.to_i
|
196
|
+
end
|
197
|
+
end
|
198
|
+
qlimits
|
199
|
+
end
|
200
|
+
|
201
|
+
##
|
202
|
+
# Increment current count of running jobs in queue by amount NOT to exceed limit
|
203
|
+
#
|
204
|
+
# return 1 if incrementing count would NOT exceed limit
|
205
|
+
# return 0 if incrementing count would exceed limit
|
206
|
+
def self.qlimit_increment(queue, limit)
|
207
|
+
return 1 if limit.nil? # No limit, no processing
|
208
|
+
|
209
|
+
Sidekiq.redis do |conn|
|
210
|
+
result = conn.evalsha(@@qlimit_increment_sha,["qlimit:#{queue}"],[limit])
|
211
|
+
#Sidekiq.logger.debug("Checking Qlimit #{queue} => #{result}")
|
212
|
+
return result
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
##
|
217
|
+
# Decrement current count of running jobs in queue by amount (default: 1)
|
218
|
+
def self.qlimit_decrement(queue, amount = 1)
|
219
|
+
Sidekiq.logger.debug("Qlimit Decrement: #{queue} by #{amount}")
|
220
|
+
|
221
|
+
Sidekiq.redis do |conn|
|
222
|
+
result = conn.evalsha(@@qlimit_decrement_sha,["qlimit:#{queue}"], [amount])
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
##
|
227
|
+
# Set current count of running jobs in queue to 0
|
228
|
+
def self.qlimit_reset(queue)
|
229
|
+
self.qlimit_set(queue, 0)
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# Set current count of running jobs in queue
|
234
|
+
def self.qlimit_set(queue, amount = 0)
|
235
|
+
Sidekiq.logger.debug("Qlimit Set: #{queue} => #{amount}")
|
236
|
+
Sidekiq.redis do |conn|
|
237
|
+
conn.set("qlimit:#{queue}", amount)
|
238
|
+
conn.expire("qlimit:#{queue}", 14400)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
##
|
243
|
+
# Get current count of running jobs from queue
|
244
|
+
def self.qlimit_get(queue)
|
245
|
+
Sidekiq.redis do |conn|
|
246
|
+
result = conn.get("qlimit:#{queue}")
|
247
|
+
return result.to_i unless result.nil?
|
248
|
+
return 0
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
##
|
253
|
+
# Used to requeue jobs on sidekiq shutdown/termination
|
254
|
+
def self.bulk_requeue(inprogress, options)
|
255
|
+
return if inprogress.empty?
|
256
|
+
|
257
|
+
Sidekiq.logger.debug { "Re-queueing terminated jobs" }
|
258
|
+
jobs_to_requeue = {}
|
259
|
+
inprogress.each do |unit_of_work|
|
260
|
+
jobs_to_requeue[unit_of_work.queue_name] ||= []
|
261
|
+
jobs_to_requeue[unit_of_work.queue_name] << unit_of_work.job
|
262
|
+
end
|
263
|
+
|
264
|
+
Sidekiq.redis do |conn|
|
265
|
+
conn.pipelined do
|
266
|
+
jobs_to_requeue.each do |queue, jobs|
|
267
|
+
conn.rpush("queue:#{queue}", jobs)
|
268
|
+
# Reduce qlimit on requeue of amount: jobs.length
|
269
|
+
self.qlimit_decrement(queue, jobs.length)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
Sidekiq.logger.debug("Pushed #{inprogress.size} jobs back to Redis")
|
274
|
+
rescue => ex
|
275
|
+
Sidekiq.logger.warn("Failed to requeue #{inprogress.size} jobs: #{ex.message}")
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<header class='row'>
|
2
|
+
<div class='span5 col-sm-5'>
|
3
|
+
<h3><%=t 'Qlimits' %></h3>
|
4
|
+
</div>
|
5
|
+
</header>
|
6
|
+
|
7
|
+
<table class="table table-hover table-bordered table-striped table-white">
|
8
|
+
<thead>
|
9
|
+
<tr>
|
10
|
+
<th>Queue</th>
|
11
|
+
<th style="text-align:center;">Current Running</th>
|
12
|
+
<th style="text-align:center;">Limit</th>
|
13
|
+
<th style="text-align:center;">Zero</th>
|
14
|
+
</tr>
|
15
|
+
</thead>
|
16
|
+
<tbody>
|
17
|
+
<% Sidekiq::Qlimit::QlimitFetch.qlimit_hash.each do |k,v| %>
|
18
|
+
<tr>
|
19
|
+
<td style="vertical-align:middle;text-align:center;">
|
20
|
+
<%= k %>
|
21
|
+
</td>
|
22
|
+
<td style="vertical-align:middle;text-align:center;">
|
23
|
+
<%= v %>
|
24
|
+
</td>
|
25
|
+
<td style="vertical-align:middle;text-align:center;">
|
26
|
+
-Later-
|
27
|
+
<!-- TODO: Store current limits in redis and read from redis to display -->
|
28
|
+
</td>
|
29
|
+
<td style="vertical-align:middle;text-align:center;">
|
30
|
+
<form action="<%= root_path %>qlimit/<%= k %>" method="POST">
|
31
|
+
<%= csrf_tag %>
|
32
|
+
<input type="hidden" name="_method" value="delete" />
|
33
|
+
<button class="btn btn-danger" type="submit">Reset</button>
|
34
|
+
</form>
|
35
|
+
</td>
|
36
|
+
</tr>
|
37
|
+
<% end %>
|
38
|
+
</tbody>
|
39
|
+
</table>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# stdlib
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
# 3rd party
|
6
|
+
require "sidekiq"
|
7
|
+
require "sidekiq/web"
|
8
|
+
|
9
|
+
# internal
|
10
|
+
require "sidekiq/qlimit/web_extension"
|
11
|
+
|
12
|
+
|
13
|
+
if defined?(Sidekiq::Web)
|
14
|
+
Sidekiq::Web.register Sidekiq::Qlimit::WebExtension
|
15
|
+
|
16
|
+
if Sidekiq::Web.tabs.is_a?(Array)
|
17
|
+
# For sidekiq < 2.5
|
18
|
+
Sidekiq::Web.tabs << "cron"
|
19
|
+
else
|
20
|
+
Sidekiq::Web.tabs["Qlimit"] = "qlimit"
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'tilt/erubis'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Qlimit
|
5
|
+
module WebExtension
|
6
|
+
def self.registered(app)
|
7
|
+
view_path = File.join(File.expand_path("..", __FILE__), "views")
|
8
|
+
app.get "/qlimit" do
|
9
|
+
render(:erb, File.read(File.join(view_path, "index.html.erb")))
|
10
|
+
end
|
11
|
+
|
12
|
+
app.delete "/qlimit/:id" do |id|
|
13
|
+
Sidekiq::Qlimit::QlimitFetch.qlimit_reset(id)
|
14
|
+
redirect "#{root_path}qlimit"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# 3rd party
|
3
|
+
require "sidekiq"
|
4
|
+
|
5
|
+
# internal
|
6
|
+
require "sidekiq/version"
|
7
|
+
require_relative "qlimit/qlimit_fetch"
|
8
|
+
require_relative 'qlimit/web'
|
9
|
+
|
10
|
+
|
11
|
+
# @see https://github.com/mperham/sidekiq/
|
12
|
+
module Sidekiq
|
13
|
+
# Sidekiq per queue 'soft' limiting
|
14
|
+
#
|
15
|
+
# Just add somewhere in your bootstrap (config/initializers/sidekiq-qlimit.rb):
|
16
|
+
#
|
17
|
+
# require "sidekiq/qlimit"
|
18
|
+
# Sidekiq::Qlimit.setup!
|
19
|
+
#
|
20
|
+
module Qlimit
|
21
|
+
class << self
|
22
|
+
# Hooks Qlimit into sidekiq.
|
23
|
+
# @return [void]
|
24
|
+
def setup!
|
25
|
+
Sidekiq.configure_server do |config|
|
26
|
+
require "sidekiq/qlimit/qlimit_fetch"
|
27
|
+
Sidekiq.options[:fetch] = Sidekiq::Qlimit::QlimitFetch
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sidekiq-qlimit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeff Chan
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sidekiq
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tilt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sinatra
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.4.7
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.4.7
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.10'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.10'
|
69
|
+
description: Sidekiq per queue 'soft' limiting. It ain't perfect, but it's enough.
|
70
|
+
email:
|
71
|
+
- jeff@braincommerce.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- lib/sidekiq/qlimit.rb
|
77
|
+
- lib/sidekiq/qlimit/qlimit_fetch.rb
|
78
|
+
- lib/sidekiq/qlimit/version.rb
|
79
|
+
- lib/sidekiq/qlimit/views/index.html.erb
|
80
|
+
- lib/sidekiq/qlimit/web.rb
|
81
|
+
- lib/sidekiq/qlimit/web_extension.rb
|
82
|
+
homepage: https://github.com/braincom/sidekiq-qlimit
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options:
|
88
|
+
- "--title"
|
89
|
+
- Sidekiq-Qlimit - A Soft Limiter
|
90
|
+
- "--main"
|
91
|
+
- README.md
|
92
|
+
- "--exclude"
|
93
|
+
- example
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.5.2
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Sidekiq per queue 'soft' limiting.
|
112
|
+
test_files: []
|