sidekiq 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- data/.travis.yml +3 -0
- data/Changes.md +15 -0
- data/README.md +1 -1
- data/TODO.md +0 -2
- data/examples/chef/cookbooks/sidekiq/README.rdoc +7 -0
- data/examples/chef/cookbooks/sidekiq/recipes/default.rb +54 -0
- data/examples/chef/cookbooks/sidekiq/templates/default/monitrc.conf.erb +8 -0
- data/examples/chef/cookbooks/sidekiq/templates/default/sidekiq.erb +219 -0
- data/examples/chef/cookbooks/sidekiq/templates/default/sidekiq.yml.erb +28 -0
- data/examples/monitrc.conf +6 -0
- data/examples/por.rb +10 -0
- data/lib/sidekiq.rb +70 -0
- data/lib/sidekiq/cli.rb +72 -30
- data/lib/sidekiq/client.rb +16 -16
- data/lib/sidekiq/extensions/action_mailer.rb +27 -0
- data/lib/sidekiq/extensions/active_record.rb +29 -0
- data/lib/sidekiq/extensions/generic_proxy.rb +21 -0
- data/lib/sidekiq/manager.rb +16 -12
- data/lib/sidekiq/middleware/chain.rb +23 -23
- data/lib/sidekiq/middleware/client/resque_web_compatibility.rb +1 -4
- data/lib/sidekiq/middleware/client/unique_jobs.rb +5 -13
- data/lib/sidekiq/middleware/server/failure_jobs.rb +24 -0
- data/lib/sidekiq/middleware/server/unique_jobs.rb +1 -5
- data/lib/sidekiq/processor.rb +11 -13
- data/lib/sidekiq/redis_connection.rb +1 -1
- data/lib/sidekiq/util.rb +20 -1
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/worker.rb +1 -1
- data/myapp/Gemfile +0 -2
- data/myapp/app/controllers/work_controller.rb +25 -1
- data/myapp/app/mailers/user_mailer.rb +9 -0
- data/myapp/app/models/post.rb +3 -0
- data/myapp/app/views/user_mailer/greetings.html.erb +3 -0
- data/myapp/app/workers/hard_worker.rb +2 -2
- data/myapp/config/initializers/sidekiq.rb +6 -1
- data/myapp/config/routes.rb +3 -0
- data/sidekiq.gemspec +2 -0
- data/test/config.yml +11 -0
- data/test/test_cli.rb +119 -3
- data/test/test_client.rb +21 -10
- data/test/test_extensions.rb +45 -0
- data/test/test_manager.rb +2 -2
- data/test/test_middleware.rb +7 -12
- data/test/test_stats.rb +1 -2
- metadata +55 -16
data/lib/sidekiq/cli.rb
CHANGED
@@ -9,8 +9,9 @@ trap 'TERM' do
|
|
9
9
|
Thread.main.raise Interrupt
|
10
10
|
end
|
11
11
|
|
12
|
+
require 'yaml'
|
12
13
|
require 'optparse'
|
13
|
-
require 'sidekiq
|
14
|
+
require 'sidekiq'
|
14
15
|
require 'sidekiq/util'
|
15
16
|
require 'sidekiq/redis_connection'
|
16
17
|
require 'sidekiq/manager'
|
@@ -20,7 +21,7 @@ module Sidekiq
|
|
20
21
|
include Util
|
21
22
|
|
22
23
|
# Used for CLI testing
|
23
|
-
attr_accessor :
|
24
|
+
attr_accessor :code
|
24
25
|
|
25
26
|
def initialize
|
26
27
|
@code = nil
|
@@ -28,14 +29,21 @@ module Sidekiq
|
|
28
29
|
|
29
30
|
def parse(args=ARGV)
|
30
31
|
Sidekiq::Util.logger
|
31
|
-
|
32
|
+
|
33
|
+
cli = parse_options(args)
|
34
|
+
config = parse_config(cli)
|
35
|
+
options.merge!(config.merge(cli))
|
36
|
+
|
37
|
+
set_logger_level_to_debug if options[:verbose]
|
38
|
+
|
39
|
+
write_pid
|
32
40
|
validate!
|
33
41
|
boot_system
|
34
42
|
end
|
35
43
|
|
36
44
|
def run
|
37
|
-
Sidekiq
|
38
|
-
manager = Sidekiq::Manager.new(
|
45
|
+
Sidekiq.redis ||= RedisConnection.create(:url => options[:server], :namespace => options[:namespace])
|
46
|
+
manager = Sidekiq::Manager.new(options)
|
39
47
|
begin
|
40
48
|
logger.info 'Starting processing, hit Ctrl-C to stop'
|
41
49
|
manager.start!
|
@@ -44,7 +52,7 @@ module Sidekiq
|
|
44
52
|
sleep
|
45
53
|
rescue Interrupt
|
46
54
|
# TODO Need clean shutdown support from Celluloid
|
47
|
-
logger.info 'Shutting down
|
55
|
+
logger.info 'Shutting down'
|
48
56
|
manager.stop!
|
49
57
|
manager.wait(:shutdown)
|
50
58
|
end
|
@@ -56,29 +64,33 @@ module Sidekiq
|
|
56
64
|
exit(code)
|
57
65
|
end
|
58
66
|
|
67
|
+
def options
|
68
|
+
Sidekiq.options
|
69
|
+
end
|
70
|
+
|
59
71
|
def detected_environment
|
60
|
-
|
72
|
+
options[:environment] ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
61
73
|
end
|
62
74
|
|
63
75
|
def boot_system
|
64
76
|
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = detected_environment
|
65
77
|
|
66
|
-
raise ArgumentError, "#{
|
78
|
+
raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
|
67
79
|
|
68
|
-
if File.directory?(
|
69
|
-
require File.expand_path("#{
|
80
|
+
if File.directory?(options[:require])
|
81
|
+
require File.expand_path("#{options[:require]}/config/environment.rb")
|
70
82
|
::Rails.application.eager_load!
|
71
83
|
else
|
72
|
-
require
|
84
|
+
require options[:require]
|
73
85
|
end
|
74
86
|
end
|
75
87
|
|
76
88
|
def validate!
|
77
|
-
|
78
|
-
|
89
|
+
options[:queues] << 'default' if options[:queues].empty?
|
90
|
+
options[:queues].shuffle!
|
79
91
|
|
80
|
-
if !File.exist?(
|
81
|
-
(File.directory?(
|
92
|
+
if !File.exist?(options[:require]) ||
|
93
|
+
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
82
94
|
logger.info "=================================================================="
|
83
95
|
logger.info " Please point sidekiq to a Rails 3 application or a Ruby file "
|
84
96
|
logger.info " to load your worker classes with -r [DIR|FILE]."
|
@@ -89,43 +101,44 @@ module Sidekiq
|
|
89
101
|
end
|
90
102
|
|
91
103
|
def parse_options(argv)
|
92
|
-
|
93
|
-
:queues => [],
|
94
|
-
:processor_count => 25,
|
95
|
-
:require => '.',
|
96
|
-
:environment => nil,
|
97
|
-
}
|
104
|
+
opts = {}
|
98
105
|
|
99
106
|
@parser = OptionParser.new do |o|
|
100
107
|
o.on "-q", "--queue QUEUE,WEIGHT", "Queue to process, with optional weight" do |arg|
|
101
108
|
(q, weight) = arg.split(",")
|
102
|
-
(
|
103
|
-
@options[:queues] << q
|
104
|
-
end
|
109
|
+
parse_queues(opts, q, weight)
|
105
110
|
end
|
106
111
|
|
107
112
|
o.on "-v", "--verbose", "Print more verbose output" do
|
108
|
-
|
113
|
+
set_logger_level_to_debug
|
109
114
|
end
|
110
115
|
|
111
116
|
o.on "-n", "--namespace NAMESPACE", "namespace worker queues are under" do |arg|
|
112
|
-
|
117
|
+
opts[:namespace] = arg
|
113
118
|
end
|
114
119
|
|
115
120
|
o.on "-s", "--server LOCATION", "Where to find Redis" do |arg|
|
116
|
-
|
121
|
+
opts[:server] = arg
|
117
122
|
end
|
118
123
|
|
119
124
|
o.on '-e', '--environment ENV', "Application environment" do |arg|
|
120
|
-
|
125
|
+
opts[:environment] = arg
|
121
126
|
end
|
122
127
|
|
123
128
|
o.on '-r', '--require [PATH|DIR]', "Location of Rails application with workers or file to require" do |arg|
|
124
|
-
|
129
|
+
opts[:require] = arg
|
125
130
|
end
|
126
131
|
|
127
132
|
o.on '-c', '--concurrency INT', "processor threads to use" do |arg|
|
128
|
-
|
133
|
+
opts[:concurrency] = arg.to_i
|
134
|
+
end
|
135
|
+
|
136
|
+
o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
|
137
|
+
opts[:pidfile] = arg
|
138
|
+
end
|
139
|
+
|
140
|
+
o.on '-C', '--config PATH', "path to YAML config file" do |arg|
|
141
|
+
opts[:config_file] = arg
|
129
142
|
end
|
130
143
|
end
|
131
144
|
|
@@ -135,6 +148,35 @@ module Sidekiq
|
|
135
148
|
die 1
|
136
149
|
end
|
137
150
|
@parser.parse!(argv)
|
151
|
+
opts
|
152
|
+
end
|
153
|
+
|
154
|
+
def write_pid
|
155
|
+
if path = options[:pidfile]
|
156
|
+
File.open(path, 'w') do |f|
|
157
|
+
f.puts Process.pid
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def parse_config(cli)
|
163
|
+
opts = {}
|
164
|
+
if cli[:config_file] && File.exist?(cli[:config_file])
|
165
|
+
opts = YAML.load_file cli[:config_file]
|
166
|
+
queues = opts.delete(:queues) || []
|
167
|
+
queues.each { |pair| parse_queues(opts, *pair) }
|
168
|
+
end
|
169
|
+
opts
|
170
|
+
end
|
171
|
+
|
172
|
+
def parse_queues(opts, q, weight)
|
173
|
+
(weight || 1).to_i.times do
|
174
|
+
(opts[:queues] ||= []) << q
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def set_logger_level_to_debug
|
179
|
+
Sidekiq::Util.logger.level = Logger::DEBUG
|
138
180
|
end
|
139
181
|
|
140
182
|
end
|
data/lib/sidekiq/client.rb
CHANGED
@@ -10,26 +10,26 @@ module Sidekiq
|
|
10
10
|
class Client
|
11
11
|
|
12
12
|
def self.middleware
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
m
|
13
|
+
raise "Sidekiq::Client.middleware is now Sidekiq.client_middleware"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.default_middleware
|
17
|
+
Middleware::Chain.new do |m|
|
18
|
+
m.add Middleware::Client::UniqueJobs
|
19
|
+
m.add Middleware::Client::ResqueWebCompatibility
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def self.
|
24
|
-
|
23
|
+
def self.registered_workers
|
24
|
+
Sidekiq.redis.smembers('workers')
|
25
25
|
end
|
26
26
|
|
27
|
-
def self.
|
28
|
-
|
27
|
+
def self.registered_queues
|
28
|
+
Sidekiq.redis.smembers('queues')
|
29
29
|
end
|
30
30
|
|
31
|
-
def self.
|
32
|
-
@
|
31
|
+
def self.queue_mappings
|
32
|
+
@queue_mappings ||= {}
|
33
33
|
end
|
34
34
|
|
35
35
|
# Example usage:
|
@@ -38,13 +38,13 @@ module Sidekiq
|
|
38
38
|
raise(ArgumentError, "Message must be a Hash of the form: { 'class' => SomeClass, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash)
|
39
39
|
raise(ArgumentError, "Message must include a class and set of arguments: #{item.inspect}") if !item['class'] || !item['args']
|
40
40
|
|
41
|
-
queue = queue ||
|
41
|
+
queue = queue || queue_mappings[item['class'].to_s] || 'default'
|
42
42
|
|
43
43
|
item['class'] = item['class'].to_s if !item['class'].is_a?(String)
|
44
44
|
|
45
45
|
pushed = false
|
46
|
-
|
47
|
-
redis.rpush("queue:#{queue}", MultiJson.encode(item))
|
46
|
+
Sidekiq.client_middleware.invoke(item, queue) do
|
47
|
+
Sidekiq.redis.rpush("queue:#{queue}", MultiJson.encode(item))
|
48
48
|
pushed = true
|
49
49
|
end
|
50
50
|
pushed
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'sidekiq/extensions/generic_proxy'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Extensions
|
5
|
+
##
|
6
|
+
# Adds a 'delay' method to ActionMailer to offload arbitrary email
|
7
|
+
# delivery to Sidekiq. Example:
|
8
|
+
#
|
9
|
+
# UserMailer.delay.send_welcome_email(new_user)
|
10
|
+
class DelayedMailer
|
11
|
+
include Sidekiq::Worker
|
12
|
+
|
13
|
+
def perform(yml)
|
14
|
+
(target, method_name, args) = YAML.load(yml)
|
15
|
+
target.send(method_name, *args).deliver
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ActionMailer
|
20
|
+
def delay
|
21
|
+
Proxy.new(DelayedMailer, self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
::ActionMailer::Base.extend(ActionMailer)
|
26
|
+
end
|
27
|
+
end if defined?(::ActionMailer)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'sidekiq/extensions/generic_proxy'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Extensions
|
5
|
+
##
|
6
|
+
# Adds a 'delay' method to ActiveRecord to offload arbitrary method
|
7
|
+
# execution to Sidekiq. Examples:
|
8
|
+
#
|
9
|
+
# User.delay.delete_inactive
|
10
|
+
# User.recent_signups.each { |user| user.delay.mark_as_awesome }
|
11
|
+
class DelayedModel
|
12
|
+
include Sidekiq::Worker
|
13
|
+
|
14
|
+
def perform(yml)
|
15
|
+
(target, method_name, args) = YAML.load(yml)
|
16
|
+
target.send(method_name, *args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ActiveRecord
|
21
|
+
def delay
|
22
|
+
Proxy.new(DelayedModel, self)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
::ActiveRecord::Base.extend(ActiveRecord)
|
27
|
+
::ActiveRecord::Base.send(:include, ActiveRecord)
|
28
|
+
end
|
29
|
+
end if defined?(::ActiveRecord)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Extensions
|
3
|
+
class Proxy < ::BasicObject
|
4
|
+
def initialize(performable, target)
|
5
|
+
@performable = performable
|
6
|
+
@target = target
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(name, *args)
|
10
|
+
# Sidekiq has a limitation in that its message must be JSON.
|
11
|
+
# JSON can't round trip real Ruby objects so we use YAML to
|
12
|
+
# serialize the objects to a String. The YAML will be converted
|
13
|
+
# to JSON and then deserialized on the other side back into a
|
14
|
+
# Ruby object.
|
15
|
+
obj = [@target, name, args]
|
16
|
+
::Sidekiq::Client.push('class' => @performable.name, 'args' => [::YAML.dump(obj)])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/sidekiq/manager.rb
CHANGED
@@ -18,14 +18,10 @@ module Sidekiq
|
|
18
18
|
|
19
19
|
trap_exit :processor_died
|
20
20
|
|
21
|
-
class << self
|
22
|
-
attr_accessor :redis
|
23
|
-
end
|
24
|
-
|
25
21
|
def initialize(options={})
|
26
22
|
logger.info "Booting sidekiq #{Sidekiq::VERSION} with Redis at #{redis.client.location}"
|
27
23
|
logger.debug { options.inspect }
|
28
|
-
@count = options[:
|
24
|
+
@count = options[:concurrency] || 25
|
29
25
|
@queues = options[:queues]
|
30
26
|
@done_callback = nil
|
31
27
|
|
@@ -39,15 +35,23 @@ module Sidekiq
|
|
39
35
|
@ready.each(&:terminate)
|
40
36
|
@ready.clear
|
41
37
|
|
42
|
-
after(5) do
|
43
|
-
signal(:shutdown)
|
44
|
-
end
|
45
|
-
|
46
38
|
redis.with_connection do |conn|
|
47
|
-
conn.smembers('workers')
|
48
|
-
|
39
|
+
workers = conn.smembers('workers')
|
40
|
+
workers.each do |name|
|
41
|
+
conn.srem('workers', name) if name =~ /:#{process_id}-/
|
49
42
|
end
|
50
43
|
end
|
44
|
+
|
45
|
+
if @busy.empty?
|
46
|
+
return signal(:shutdown)
|
47
|
+
end
|
48
|
+
|
49
|
+
logger.info("Pausing 5 seconds to allow workers to finish...")
|
50
|
+
after(5) do
|
51
|
+
@busy.each(&:terminate)
|
52
|
+
#@busy.each(&:requeue)
|
53
|
+
signal(:shutdown)
|
54
|
+
end
|
51
55
|
end
|
52
56
|
|
53
57
|
def start
|
@@ -63,7 +67,7 @@ module Sidekiq
|
|
63
67
|
@done_callback.call(processor) if @done_callback
|
64
68
|
@busy.delete(processor)
|
65
69
|
if stopped?
|
66
|
-
processor.terminate
|
70
|
+
processor.terminate if processor.alive?
|
67
71
|
else
|
68
72
|
@ready << processor
|
69
73
|
end
|
@@ -5,34 +5,37 @@ module Sidekiq
|
|
5
5
|
# (pushing jobs onto the queue) as well as the server
|
6
6
|
# side (when jobs are actually processed).
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# To add middleware for the client:
|
9
9
|
#
|
10
|
-
# Sidekiq
|
11
|
-
#
|
12
|
-
# use Middleware::Server::ActiveRecord
|
10
|
+
# Sidekiq.client_middleware do |chain|
|
11
|
+
# chain.add MyClientHook
|
13
12
|
# end
|
14
13
|
#
|
15
|
-
# To
|
14
|
+
# To modify middleware for the server, just call
|
15
|
+
# with another block:
|
16
16
|
#
|
17
|
-
# Sidekiq
|
18
|
-
#
|
17
|
+
# Sidekiq.server_middleware do |chain|
|
18
|
+
# chain.add MyServerHook
|
19
|
+
# chain.remove ActiveRecord
|
19
20
|
# end
|
20
21
|
#
|
21
|
-
#
|
22
|
+
# This is an example of a minimal server middleware:
|
22
23
|
#
|
23
|
-
#
|
24
|
-
#
|
24
|
+
# class MyServerHook
|
25
|
+
# def call(worker, msg, queue)
|
26
|
+
# puts "Before work"
|
27
|
+
# yield
|
28
|
+
# puts "After work"
|
29
|
+
# end
|
25
30
|
# end
|
26
31
|
#
|
27
|
-
# This is an example of a minimal middleware:
|
32
|
+
# This is an example of a minimal client middleware:
|
28
33
|
#
|
29
|
-
# class
|
30
|
-
# def
|
31
|
-
#
|
32
|
-
# def call(worker, msg)
|
33
|
-
# puts "Before work"
|
34
|
+
# class MyClientHook
|
35
|
+
# def call(msg, queue)
|
36
|
+
# puts "Before push"
|
34
37
|
# yield
|
35
|
-
# puts "After
|
38
|
+
# puts "After push"
|
36
39
|
# end
|
37
40
|
# end
|
38
41
|
#
|
@@ -42,17 +45,14 @@ module Sidekiq
|
|
42
45
|
|
43
46
|
def initialize
|
44
47
|
@entries = []
|
48
|
+
yield self if block_given?
|
45
49
|
end
|
46
50
|
|
47
|
-
def
|
48
|
-
instance_eval(&block)
|
49
|
-
end
|
50
|
-
|
51
|
-
def unregister(klass)
|
51
|
+
def remove(klass)
|
52
52
|
entries.delete_if { |entry| entry.klass == klass }
|
53
53
|
end
|
54
54
|
|
55
|
-
def
|
55
|
+
def add(klass, *args)
|
56
56
|
entries << Entry.new(klass, *args) unless exists?(klass)
|
57
57
|
end
|
58
58
|
|