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 ADDED
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ *.DS_Store
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
6
+ .idea
7
+ spec/dump.rdb
8
+ spec/redis-server.log
9
+ spec/redis.pid
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby-19mode # JRuby in 1.9 mode
5
+ # - rbx-19mode
6
+ script: bundle exec rspec spec
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ 0.5.0
2
+ -----
3
+
4
+ Initial version ported from resque-dynamic-queues 0.8.1
5
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sidekiq-dynamic-queues.gemspec
4
+ gemspec
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
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require 'logger'
3
+
4
+ $LOAD_PATH.unshift ::File.expand_path(::File.dirname(__FILE__) + '/lib')
5
+ require 'sidekiq/web'
6
+ require 'sidekiq-dynamic-queues-server'
7
+
8
+ use Rack::ShowExceptions
9
+ run Sidekiq::Web
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'sidekiq-dynamic-queues'
@@ -0,0 +1,5 @@
1
+ require 'sidekiq-dynamic-queues'
2
+ require 'sidekiq/web'
3
+ require 'sidekiq/dynamic_queues/server'
4
+
5
+ Sidekiq::Web.register Sidekiq::DynamicQueues::Server
@@ -0,0 +1,3 @@
1
+ require 'sidekiq'
2
+ require 'sidekiq/dynamic_queues/attributes'
3
+ require 'sidekiq/dynamic_queues/fetch'
@@ -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
+ });