sidekiq-dynamic-queues 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/wr0ngway/sidekiq-dynamic-queues.png)](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
|
+
});
|