sidekiq-dynamic-queues 0.5.0
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 +9 -0
- data/.travis.yml +6 -0
- data/CHANGELOG +5 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +64 -0
- data/Rakefile +67 -0
- data/config.ru +9 -0
- data/init.rb +1 -0
- data/lib/sidekiq-dynamic-queues-server.rb +5 -0
- data/lib/sidekiq-dynamic-queues.rb +3 -0
- data/lib/sidekiq/dynamic_queues/attributes.rb +65 -0
- data/lib/sidekiq/dynamic_queues/fetch.rb +84 -0
- data/lib/sidekiq/dynamic_queues/server.rb +69 -0
- data/lib/sidekiq/dynamic_queues/server/views/dynamicqueue.slim +63 -0
- data/lib/sidekiq/dynamic_queues/version.rb +5 -0
- data/sidekiq-dynamic-queues.gemspec +31 -0
- data/spec/queues_spec.rb +198 -0
- data/spec/redis-test.conf +312 -0
- data/spec/server_spec.rb +131 -0
- data/spec/spec_helper.rb +93 -0
- metadata +169 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
The MIT License (MIT)
|
3
|
+
Copyright (c) 2013 Matt Conway (matt@conwaysplace.com)
|
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,64 @@
|
|
1
|
+
A sidekiq plugin for specifying the queues a worker pulls from with wildcards, negations, or dynamic look up from redis.
|
2
|
+
|
3
|
+
Authored against Sidekiq 2.9.0, so it at least works with that - try running the tests if you use a different version of sidekiq
|
4
|
+
|
5
|
+
[](http://travis-ci.org/wr0ngway/sidekiq-dynamic-queues)
|
6
|
+
|
7
|
+
Usage:
|
8
|
+
|
9
|
+
If creating a gem of your own that uses sidekiq-dynamic-queues, you may have to add an explicit require statement at the top of your Rakefile:
|
10
|
+
|
11
|
+
require 'sidekiq-dynamic-queues'
|
12
|
+
|
13
|
+
Configure by setting Sidekiq.options[:fetch] = Sidekiq::DynamicQueues::Fetch
|
14
|
+
|
15
|
+
Start your workers with a QUEUE that can contain '\*' for zero-or more of any character, '!' to exclude the following pattern, or @key to look up the patterns from redis. Some examples help:
|
16
|
+
|
17
|
+
sidekiq -q foo
|
18
|
+
|
19
|
+
Pulls jobs from the queue 'foo'
|
20
|
+
|
21
|
+
sidekiq -q *
|
22
|
+
|
23
|
+
Pulls jobs from any queue
|
24
|
+
|
25
|
+
sidekiq -q *foo
|
26
|
+
|
27
|
+
Pulls jobs from queues that end in foo
|
28
|
+
|
29
|
+
sidekiq -q *foo*
|
30
|
+
|
31
|
+
Pulls jobs from queues whose names contain foo
|
32
|
+
|
33
|
+
sidekiq -q *foo* -q !foobar
|
34
|
+
|
35
|
+
Pulls jobs from queues whose names contain foo except the foobar queue
|
36
|
+
|
37
|
+
sidekiq -q *foo* -q !*bar
|
38
|
+
|
39
|
+
Pulls jobs from queues whose names contain foo except queues whose names end in bar
|
40
|
+
|
41
|
+
QUEUE='@key' sidekiq -q @key
|
42
|
+
|
43
|
+
Pulls jobs from queue names stored in redis (use Sidekiq::DynamicQueues::Attributes.set\_dynamic\_queue("key", ["queuename1", "queuename2"]) to set them)
|
44
|
+
|
45
|
+
sidekiq -q * -q !@key
|
46
|
+
|
47
|
+
Pulls jobs from any queue except ones stored in redis
|
48
|
+
|
49
|
+
sidekiq -q @
|
50
|
+
|
51
|
+
Pulls jobs from queue names stored in redis using the hostname of the worker
|
52
|
+
|
53
|
+
Sidekiq::DynamicQueues::Attributes.set_dynamic_queue("key", ["*foo*", "!*bar"])
|
54
|
+
sidekiq -q @key
|
55
|
+
|
56
|
+
Pulls jobs from queue names stored in redis, with wildcards/negations
|
57
|
+
|
58
|
+
|
59
|
+
There is also a tab in the sidekiq-web UI that allows you to define the dynamic queues To activate it, you need to require 'sidekiq-dynamic-queues-server' in whatever initializer you use to bring up sidekiq-web.
|
60
|
+
|
61
|
+
|
62
|
+
Contributors:
|
63
|
+
|
64
|
+
Matt Conway ( https://github.com/wr0ngway )
|
data/Rakefile
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
task :my_release => ['changelog', 'release'] do
|
5
|
+
end
|
6
|
+
|
7
|
+
task :changelog do
|
8
|
+
|
9
|
+
helper = Bundler::GemHelper.new(Dir.pwd)
|
10
|
+
version = "v#{helper.gemspec.version}"
|
11
|
+
|
12
|
+
changelog_file = 'CHANGELOG'
|
13
|
+
entries = ""
|
14
|
+
|
15
|
+
# Get a list of current tags
|
16
|
+
tags = `git tag -l`.split
|
17
|
+
tags = tags.sort_by {|t| t[1..-1].split(".").collect {|s| s.to_i } }
|
18
|
+
newest_tag = tags[-1]
|
19
|
+
|
20
|
+
if version == newest_tag
|
21
|
+
puts "You need to update version, same as most recent tag: #{version}"
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
# If we already have a changelog, make the last tag be the
|
26
|
+
# last one in the changelog, and the next one be the one
|
27
|
+
# following that in the tag list
|
28
|
+
newest_changelog_version = nil
|
29
|
+
if File.exist?(changelog_file)
|
30
|
+
entries = File.read(changelog_file)
|
31
|
+
head = entries.split.first
|
32
|
+
if head =~ /\d\.\d\.\d/
|
33
|
+
newest_changelog_version = "v#{head}"
|
34
|
+
|
35
|
+
if version == newest_changelog_version
|
36
|
+
puts "You need to update version, same as most recent changelog: #{version}"
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Generate changelog from repo
|
44
|
+
log=`git log --pretty='format:%s <%h> [%cn]' #{newest_tag}..HEAD`
|
45
|
+
|
46
|
+
# Strip out maintenance entries
|
47
|
+
log = log.lines.to_a.delete_if do |l|
|
48
|
+
l =~ /^Regenerated? gemspec/ ||
|
49
|
+
l =~ /^version bump/i ||
|
50
|
+
l =~ /^Updated changelog/ ||
|
51
|
+
l =~ /^Merged? branch/
|
52
|
+
end
|
53
|
+
|
54
|
+
# Write out changelog file
|
55
|
+
File.open(changelog_file, 'w') do |out|
|
56
|
+
out.puts version.gsub(/^v/, '')
|
57
|
+
out.puts "-----"
|
58
|
+
out.puts "\n"
|
59
|
+
out.puts log
|
60
|
+
out.puts "\n"
|
61
|
+
out.puts entries
|
62
|
+
end
|
63
|
+
|
64
|
+
# Commit and push
|
65
|
+
sh "git ci -m'Updated changelog' #{changelog_file}"
|
66
|
+
sh "git push"
|
67
|
+
end
|
data/config.ru
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'sidekiq-dynamic-queues'
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module DynamicQueues
|
3
|
+
|
4
|
+
DYNAMIC_QUEUE_KEY = "dynamic_queue"
|
5
|
+
FALLBACK_KEY = "default"
|
6
|
+
|
7
|
+
module Attributes
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def json_encode(data)
|
11
|
+
Sidekiq.dump_json(data)
|
12
|
+
end
|
13
|
+
|
14
|
+
def json_decode(data)
|
15
|
+
return nil unless data
|
16
|
+
Sidekiq.load_json(data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_dynamic_queue(key, fallback=['*'])
|
20
|
+
data = Sidekiq.redis {|r| r.hget(DYNAMIC_QUEUE_KEY, key) }
|
21
|
+
queue_names = json_decode(data)
|
22
|
+
|
23
|
+
if queue_names.nil? || queue_names.size == 0
|
24
|
+
data = Sidekiq.redis {|r| r.hget(DYNAMIC_QUEUE_KEY, FALLBACK_KEY) }
|
25
|
+
queue_names = json_decode(data)
|
26
|
+
end
|
27
|
+
|
28
|
+
if queue_names.nil? || queue_names.size == 0
|
29
|
+
queue_names = fallback
|
30
|
+
end
|
31
|
+
|
32
|
+
return queue_names
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_dynamic_queue(key, values)
|
36
|
+
if values.nil? or values.size == 0
|
37
|
+
Sidekiq.redis {|r| r.hdel(DYNAMIC_QUEUE_KEY, key) }
|
38
|
+
else
|
39
|
+
Sidekiq.redis {|r| r.hset(DYNAMIC_QUEUE_KEY, key, json_encode(values)) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_dynamic_queues(dynamic_queues)
|
44
|
+
Sidekiq.redis do |r|
|
45
|
+
r.multi do
|
46
|
+
r.del(DYNAMIC_QUEUE_KEY)
|
47
|
+
dynamic_queues.each do |k, v|
|
48
|
+
set_dynamic_queue(k, v)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_dynamic_queues
|
55
|
+
result = {}
|
56
|
+
queues = Sidekiq.redis {|r| r.hgetall(DYNAMIC_QUEUE_KEY) }
|
57
|
+
queues.each {|k, v| result[k] = json_decode(v) }
|
58
|
+
result[FALLBACK_KEY] ||= ['*']
|
59
|
+
return result
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'sidekiq/fetch'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module DynamicQueues
|
5
|
+
|
6
|
+
# enable with Sidekiq.options[:fetch] = Sidekiq::DynamicQueues::Fetch
|
7
|
+
class Fetch < Sidekiq::BasicFetch
|
8
|
+
|
9
|
+
include Sidekiq::Util
|
10
|
+
include Sidekiq::DynamicQueues::Attributes
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
super
|
14
|
+
@dynamic_queues = options[:queues]
|
15
|
+
end
|
16
|
+
|
17
|
+
# overriding Sidekiq::BasicFetch#queues_cmd
|
18
|
+
def queues_cmd
|
19
|
+
if @dynamic_queues.grep(/(^!)|(^@)|(\*)/).size == 0
|
20
|
+
super
|
21
|
+
else
|
22
|
+
queues = expanded_queues
|
23
|
+
queues = @strictly_ordered_queues ? queues : queues.shuffle
|
24
|
+
queues << Sidekiq::Fetcher::TIMEOUT
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a list of queues to use when searching for a job.
|
29
|
+
#
|
30
|
+
# A splat ("*") means you want every queue (in alpha order) - this
|
31
|
+
# can be useful for dynamically adding new queues.
|
32
|
+
#
|
33
|
+
# The splat can also be used as a wildcard within a queue name,
|
34
|
+
# e.g. "*high*", and negation can be indicated with a prefix of "!"
|
35
|
+
#
|
36
|
+
# An @key can be used to dynamically look up the queue list for key from redis.
|
37
|
+
# If no key is supplied, it defaults to the worker's hostname, and wildcards
|
38
|
+
# and negations can be used inside this dynamic queue list. Set the queue
|
39
|
+
# list for a key with
|
40
|
+
# Sidekiq::DynamicQueues::Attributes.set_dynamic_queue(key, ["q1", "q2"]
|
41
|
+
#
|
42
|
+
def expanded_queues
|
43
|
+
queue_names = @dynamic_queues.dup
|
44
|
+
|
45
|
+
real_queues = Sidekiq::Client.registered_queues
|
46
|
+
matched_queues = []
|
47
|
+
|
48
|
+
while q = queue_names.shift
|
49
|
+
q = q.to_s
|
50
|
+
|
51
|
+
if q =~ /^(!)?@(.*)/
|
52
|
+
key = $2.strip
|
53
|
+
key = hostname if key.size == 0
|
54
|
+
|
55
|
+
add_queues = get_dynamic_queue(key)
|
56
|
+
add_queues.map! { |q| q.gsub!(/^!/, '') || q.gsub!(/^/, '!') } if $1
|
57
|
+
|
58
|
+
queue_names.concat(add_queues)
|
59
|
+
next
|
60
|
+
end
|
61
|
+
|
62
|
+
if q =~ /^!/
|
63
|
+
negated = true
|
64
|
+
q = q[1..-1]
|
65
|
+
end
|
66
|
+
|
67
|
+
patstr = q.gsub(/\*/, ".*")
|
68
|
+
pattern = /^#{patstr}$/
|
69
|
+
if negated
|
70
|
+
matched_queues -= matched_queues.grep(pattern)
|
71
|
+
else
|
72
|
+
matches = real_queues.grep(/^#{pattern}$/)
|
73
|
+
matches = [q] if matches.size == 0 && q == patstr
|
74
|
+
matched_queues.concat(matches)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
return matched_queues.collect { |q| "queue:#{q}" }.uniq.sort
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'sidekiq-dynamic-queues'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module DynamicQueues
|
5
|
+
module Server
|
6
|
+
|
7
|
+
Attr = Sidekiq::DynamicQueues::Attributes
|
8
|
+
|
9
|
+
def self.registered(app)
|
10
|
+
|
11
|
+
app.helpers do
|
12
|
+
|
13
|
+
def find_template(view,*a,&b)
|
14
|
+
dir = File.expand_path("../server/views/", __FILE__)
|
15
|
+
super(dir,*a,&b)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
app.get "/dynamicqueue" do
|
22
|
+
@queues = []
|
23
|
+
dqueues = Attr.get_dynamic_queues
|
24
|
+
dqueues.each do |k, v|
|
25
|
+
fetch = Fetch.new(:queues => ["@#{k}"], :strict => true)
|
26
|
+
expanded = fetch.queues_cmd
|
27
|
+
expanded.pop
|
28
|
+
expanded = expanded.collect {|q| q.split(":").last }
|
29
|
+
view_data = {
|
30
|
+
'name' => k,
|
31
|
+
'value' => Array(v).join(", "),
|
32
|
+
'expanded' => expanded.join(", ")
|
33
|
+
}
|
34
|
+
@queues << view_data
|
35
|
+
end
|
36
|
+
|
37
|
+
@queues.sort! do |a, b|
|
38
|
+
an = a['name']
|
39
|
+
bn = b['name']
|
40
|
+
if an == 'default'
|
41
|
+
1
|
42
|
+
elsif bn == 'default'
|
43
|
+
-1
|
44
|
+
else
|
45
|
+
an <=> bn
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
slim :dynamicqueue
|
50
|
+
end
|
51
|
+
|
52
|
+
app.post "/dynamicqueue" do
|
53
|
+
dynamic_queues = Array(params['queues'])
|
54
|
+
queues = {}
|
55
|
+
dynamic_queues.each do |queue|
|
56
|
+
key = queue['name']
|
57
|
+
values = queue['value'].to_s.split(',').collect{|q| q.gsub(/\s/, '') }
|
58
|
+
queues[key] = values
|
59
|
+
end
|
60
|
+
Attr.set_dynamic_queues(queues)
|
61
|
+
redirect "/dynamicqueue"
|
62
|
+
end
|
63
|
+
|
64
|
+
app.tabs["DynamicQueues"] = "dynamicqueue"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
h3 Dynamic Queues
|
2
|
+
|
3
|
+
p class="intro"
|
4
|
+
| The list below shows the dynamic queues currently defined. When you start
|
5
|
+
a worker with a dynamic queue key (@key_name), that key is looked up from
|
6
|
+
the list below to determine the actual queues the worker should pull from.
|
7
|
+
Wildcards (*) and negation (leading !) can be used to select the queues the
|
8
|
+
worker should process. There is always a fallback key - @default, which
|
9
|
+
workers will use if the key for that worker is empty. If both the key and
|
10
|
+
the fallback are empty, the worker defaults to processing '*'
|
11
|
+
|
12
|
+
form action="/dynamicqueue" method="POST"
|
13
|
+
|
14
|
+
table class="queues table table-hover table-bordered table-striped table-white"
|
15
|
+
thead
|
16
|
+
th Name
|
17
|
+
th Value
|
18
|
+
th Expanded
|
19
|
+
th
|
20
|
+
- @queues.each_with_index do |data, i|
|
21
|
+
tr class="line"
|
22
|
+
td
|
23
|
+
input type="text" id="input-#{{i}}-name" name="queues[][name]" value="#{{data['name']}}"
|
24
|
+
td
|
25
|
+
input type="text" id="input-#{{i}}-value" name="queues[][value]" value="#{{data['value']}}"
|
26
|
+
td class="expanded"
|
27
|
+
= data['expanded']
|
28
|
+
td
|
29
|
+
a href="#remove" class="remove" Remove
|
30
|
+
|
31
|
+
a href="#add" class="add" Add
|
32
|
+
|
33
|
+
input type="submit" value="Save"
|
34
|
+
|
35
|
+
javascript:
|
36
|
+
function markDirty()
|
37
|
+
{
|
38
|
+
$("input[type=submit]").css({border:"3px orange solid"});
|
39
|
+
}
|
40
|
+
|
41
|
+
jQuery(function($) {
|
42
|
+
|
43
|
+
$("input").live("keypress", markDirty);
|
44
|
+
|
45
|
+
$("a.add").live("click", function(e) {
|
46
|
+
e.preventDefault();
|
47
|
+
var $table = $("table.queues");
|
48
|
+
var $newRow = $table.find("tr.line:first").clone();
|
49
|
+
$newRow.find("input[type=text]").attr("value", "");
|
50
|
+
$newRow.find("td.expanded").html("")
|
51
|
+
$newRow.appendTo($table);
|
52
|
+
markDirty();
|
53
|
+
});
|
54
|
+
|
55
|
+
$("a.remove").live("click", function(e) {
|
56
|
+
e.preventDefault();
|
57
|
+
var $link = $(this);
|
58
|
+
$link.parents("tr").remove();
|
59
|
+
markDirty();
|
60
|
+
});
|
61
|
+
|
62
|
+
|
63
|
+
});
|