sidekiq 0.9.1 → 0.10.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/.gitignore +1 -0
- data/Changes.md +9 -0
- data/README.md +4 -4
- data/Rakefile +2 -1
- data/TODO.md +1 -1
- data/bin/sidekiqctl +43 -0
- data/examples/chef/cookbooks/sidekiq/README.rdoc +4 -0
- data/examples/chef/cookbooks/sidekiq/recipes/default.rb +2 -1
- data/examples/chef/cookbooks/sidekiq/templates/default/sidekiq.erb +2 -2
- data/examples/chef/cookbooks/sidekiq/templates/default/sidekiq.yml.erb +0 -6
- data/examples/por.rb +2 -2
- data/examples/sinkiq.rb +1 -1
- data/examples/web-ui.png +0 -0
- data/lib/sidekiq.rb +1 -0
- data/lib/sidekiq/capistrano.rb +3 -3
- data/lib/sidekiq/cli.rb +9 -5
- data/lib/sidekiq/manager.rb +1 -1
- data/lib/sidekiq/middleware/chain.rb +4 -0
- data/lib/sidekiq/middleware/client/unique_jobs.rb +13 -3
- data/lib/sidekiq/middleware/server/retry_jobs.rb +59 -0
- data/lib/sidekiq/middleware/server/unique_jobs.rb +3 -1
- data/lib/sidekiq/processor.rb +2 -1
- data/lib/sidekiq/rails.rb +1 -0
- data/lib/sidekiq/retry.rb +57 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web.rb +11 -0
- data/myapp/app/controllers/work_controller.rb +5 -0
- data/myapp/app/workers/hard_worker.rb +1 -0
- data/myapp/config/routes.rb +1 -0
- data/sidekiq.gemspec +4 -4
- data/test/helper.rb +2 -3
- data/test/test_client.rb +4 -2
- data/test/test_extensions.rb +22 -4
- data/test/test_middleware.rb +20 -4
- data/test/test_processor.rb +1 -21
- data/test/test_retry.rb +83 -0
- data/web/views/index.slim +20 -0
- metadata +36 -29
data/.gitignore
CHANGED
data/Changes.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
0.10.0
|
2
|
+
-----------
|
3
|
+
|
4
|
+
- Reworked capistrano recipe to make it more fault-tolerant [#94].
|
5
|
+
- Automatic failure retry! Sidekiq will now save failed messages
|
6
|
+
and retry them, with an exponential backoff, over about 20 days.
|
7
|
+
Did a message fail to process? Just deploy a bug fix in the next
|
8
|
+
few days and Sidekiq will retry the message eventually.
|
9
|
+
|
1
10
|
0.9.1
|
2
11
|
-----------
|
3
12
|
|
data/README.md
CHANGED
@@ -3,14 +3,12 @@ Sidekiq
|
|
3
3
|
|
4
4
|
Simple, efficient message processing for Ruby.
|
5
5
|
|
6
|
-
Sidekiq
|
6
|
+
Sidekiq is compatible with Resque. It uses the exact same
|
7
7
|
message format as Resque so it can integrate into an existing Resque processing farm.
|
8
8
|
You can have Sidekiq and Resque run side-by-side at the same time and
|
9
9
|
use the Resque client to enqueue messages in Redis to be processed by Sidekiq.
|
10
10
|
|
11
|
-
Sidekiq
|
12
|
-
processes many messages concurrently per process. Resque only processes
|
13
|
-
one message at a time per process so it is far less memory efficient.
|
11
|
+
At the same time, Sidekiq uses multithreading so it much more memory efficient than Resque (which forks a new process for every job).
|
14
12
|
You'll find that you might need 50 200MB resque processes to peg your CPU
|
15
13
|
whereas one 300MB Sidekiq process will peg the same CPU and perform the
|
16
14
|
same amount of work. Please see [my blog post on Resque's memory
|
@@ -18,6 +16,8 @@ efficiency](http://blog.carbonfive.com/2011/09/16/improving-resques-memory-effic
|
|
18
16
|
and how I was able to shrink a Carbon Five client's resque processing farm
|
19
17
|
from 9 machines to 1 machine.
|
20
18
|
|
19
|
+
In sum, if your jobs are well-behaved and threadsafe, Sidekiq is probably a good replacement for Resque. If your jobs are not thread-safe or they leak memory, you may want to continue using Resque, because its forking model gives you more protection.
|
20
|
+
|
21
21
|
|
22
22
|
Requirements
|
23
23
|
-----------------
|
data/Rakefile
CHANGED
data/TODO.md
CHANGED
@@ -1 +1 @@
|
|
1
|
-
-
|
1
|
+
- Make the Web UI less ugly
|
data/bin/sidekiqctl
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
stage = ARGV[0]
|
6
|
+
pidfile = ARGV[1]
|
7
|
+
timeout = ARGV[2].to_i
|
8
|
+
timeout = 10 if timeout == 0
|
9
|
+
|
10
|
+
def done(msg)
|
11
|
+
puts msg
|
12
|
+
exit(0)
|
13
|
+
end
|
14
|
+
|
15
|
+
done 'No pidfile given' if !pidfile
|
16
|
+
done 'Pidfile does not exist' if !File.exist?(pidfile)
|
17
|
+
|
18
|
+
pid = File.read(pidfile).to_i
|
19
|
+
done 'Invalid pidfile content' if pid == 0
|
20
|
+
|
21
|
+
begin
|
22
|
+
Process.getpgid(pid)
|
23
|
+
rescue Errno::ESRCH
|
24
|
+
done "Process doesn't exist"
|
25
|
+
end
|
26
|
+
|
27
|
+
case stage
|
28
|
+
when 'quiet'
|
29
|
+
`kill -USR1 #{pid}`
|
30
|
+
when 'stop'
|
31
|
+
`kill -TERM #{pid}`
|
32
|
+
timeout.times do
|
33
|
+
begin
|
34
|
+
Process.getpgid(pid)
|
35
|
+
rescue Errno::ESRCH
|
36
|
+
FileUtils.rm_f pidfile
|
37
|
+
done 'Sidekiq shut down gracefully.'
|
38
|
+
end
|
39
|
+
sleep 1
|
40
|
+
end
|
41
|
+
`kill -9 #{pid}`
|
42
|
+
done 'Sidekiq shut down forcefully.'
|
43
|
+
end
|
@@ -5,3 +5,7 @@ Sidekiq is a Redis-backed Ruby library for creating background jobs, placing tho
|
|
5
5
|
= USAGE:
|
6
6
|
|
7
7
|
add require_recipe "sidekiq" to main/recipes/default.rb
|
8
|
+
|
9
|
+
= NOTES:
|
10
|
+
|
11
|
+
I setup a basic size for the Sidekiq workers based on the instance_type, if you need more or less workers please modify the recipe itself.
|
@@ -140,13 +140,13 @@ APP_SHARED="${APP_DIR}/shared"
|
|
140
140
|
APP_CONFIG="${APP_SHARED}/config"
|
141
141
|
|
142
142
|
if [ -e "${APP_CONFIG}/${CONF_FILE}" ]; then
|
143
|
-
logger -t "sidekiq_${APP}" -s "Good, found a
|
143
|
+
logger -t "sidekiq_${APP}" -s "Good, found a conf file. Proceeding..."
|
144
144
|
else
|
145
145
|
logger -t "sidekiq_${APP}" -s "/data/${APP}/shared/config/${CONF_FILE} not found for app: ${APP}"
|
146
146
|
exit 1
|
147
147
|
fi
|
148
148
|
|
149
|
-
WORKER_REF=`echo $CONF_FILE | sed s/.
|
149
|
+
WORKER_REF=`echo $CONF_FILE | sed s/.conf//`
|
150
150
|
LOG_FILE="$APP_ROOT/log/$WORKER_REF.log"
|
151
151
|
LOCK_FILE="/tmp/$WORKER_REF.monit-lock"
|
152
152
|
PID_FILE="/var/run/engineyard/sidekiq/$APP/$WORKER_REF.pid"
|
data/examples/por.rb
CHANGED
@@ -2,12 +2,12 @@ require 'sidekiq'
|
|
2
2
|
|
3
3
|
# If your client is single-threaded, we just need a single connection in our Redis connection pool
|
4
4
|
Sidekiq.configure_client do |config|
|
5
|
-
config.redis = Sidekiq::RedisConnection.create(:namespace => 'x', :size => 1, :url => 'redis://redis.host:1234/
|
5
|
+
config.redis = Sidekiq::RedisConnection.create(:namespace => 'x', :size => 1, :url => 'redis://redis.host:1234/14')
|
6
6
|
end
|
7
7
|
|
8
8
|
# Sidekiq server is multi-threaded so our Redis connection pool size defaults to concurrency (-c)
|
9
9
|
Sidekiq.configure_server do |config|
|
10
|
-
config.redis = Sidekiq::RedisConnection.create(:namespace => 'x', :url => 'redis://redis.host:1234/
|
10
|
+
config.redis = Sidekiq::RedisConnection.create(:namespace => 'x', :url => 'redis://redis.host:1234/14')
|
11
11
|
end
|
12
12
|
|
13
13
|
# Start up sidekiq via
|
data/examples/sinkiq.rb
CHANGED
data/examples/web-ui.png
ADDED
Binary file
|
data/lib/sidekiq.rb
CHANGED
data/lib/sidekiq/capistrano.rb
CHANGED
@@ -2,18 +2,18 @@ Capistrano::Configuration.instance.load do
|
|
2
2
|
before "deploy", "sidekiq:quiet"
|
3
3
|
after "deploy", "sidekiq:restart"
|
4
4
|
|
5
|
-
_cset(:sidekiq_timeout) {
|
5
|
+
_cset(:sidekiq_timeout) { 10 }
|
6
6
|
|
7
7
|
namespace :sidekiq do
|
8
8
|
|
9
9
|
desc "Quiet sidekiq (stop accepting new work)"
|
10
10
|
task :quiet do
|
11
|
-
run "cd #{current_path} &&
|
11
|
+
run "cd #{current_path} && sidekiqctl quiet #{current_path}/tmp/pids/sidekiq.pid"
|
12
12
|
end
|
13
13
|
|
14
14
|
desc "Stop sidekiq"
|
15
15
|
task :stop do
|
16
|
-
run "cd #{current_path} &&
|
16
|
+
run "cd #{current_path} && sidekiqctl stop #{current_path}/tmp/pids/sidekiq.pid #{fetch :sidekiq_timeout}"
|
17
17
|
end
|
18
18
|
|
19
19
|
desc "Start sidekiq"
|
data/lib/sidekiq/cli.rb
CHANGED
@@ -28,11 +28,11 @@ module Sidekiq
|
|
28
28
|
include Singleton
|
29
29
|
|
30
30
|
# Used for CLI testing
|
31
|
-
attr_accessor :code
|
31
|
+
attr_accessor :code
|
32
|
+
attr_accessor :manager
|
32
33
|
|
33
34
|
def initialize
|
34
35
|
@code = nil
|
35
|
-
@manager = nil
|
36
36
|
end
|
37
37
|
|
38
38
|
def parse(args=ARGV)
|
@@ -44,6 +44,7 @@ module Sidekiq
|
|
44
44
|
options.merge!(config.merge(cli))
|
45
45
|
|
46
46
|
Sidekiq::Util.logger.level = Logger::DEBUG if options[:verbose]
|
47
|
+
Celluloid.logger = nil
|
47
48
|
|
48
49
|
validate!
|
49
50
|
write_pid
|
@@ -52,14 +53,17 @@ module Sidekiq
|
|
52
53
|
|
53
54
|
def run
|
54
55
|
@manager = Sidekiq::Manager.new(options)
|
56
|
+
poller = Sidekiq::Retry::Poller.new
|
55
57
|
begin
|
56
58
|
logger.info 'Starting processing, hit Ctrl-C to stop'
|
57
|
-
manager.start!
|
59
|
+
@manager.start!
|
60
|
+
poller.poll!
|
58
61
|
sleep
|
59
62
|
rescue Interrupt
|
60
63
|
logger.info 'Shutting down'
|
61
|
-
|
62
|
-
manager.
|
64
|
+
poller.terminate
|
65
|
+
@manager.stop!(:shutdown => true, :timeout => options[:timeout])
|
66
|
+
@manager.wait(:shutdown)
|
63
67
|
end
|
64
68
|
end
|
65
69
|
|
data/lib/sidekiq/manager.rb
CHANGED
@@ -9,13 +9,23 @@ module Sidekiq
|
|
9
9
|
|
10
10
|
def call(item, queue)
|
11
11
|
payload_hash = Digest::MD5.hexdigest(MultiJson.encode(item))
|
12
|
+
unique = false
|
13
|
+
|
12
14
|
Sidekiq.redis do |conn|
|
13
|
-
|
14
|
-
|
15
|
+
conn.watch(payload_hash)
|
16
|
+
|
17
|
+
if conn.get(payload_hash)
|
18
|
+
conn.unwatch
|
19
|
+
else
|
20
|
+
unique = conn.multi do
|
21
|
+
conn.setex(payload_hash, HASH_KEY_EXPIRATION, 1)
|
22
|
+
end
|
23
|
+
end
|
15
24
|
end
|
16
25
|
|
17
|
-
yield
|
26
|
+
yield if unique
|
18
27
|
end
|
28
|
+
|
19
29
|
end
|
20
30
|
end
|
21
31
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'sidekiq/retry'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Middleware
|
5
|
+
module Server
|
6
|
+
##
|
7
|
+
# Automatically retry jobs that fail in Sidekiq.
|
8
|
+
# A message looks like:
|
9
|
+
#
|
10
|
+
# { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'] }
|
11
|
+
#
|
12
|
+
# We'll add a bit more data to the message to support retries:
|
13
|
+
#
|
14
|
+
# * 'queue' - the queue to use
|
15
|
+
# * 'retry_count' - number of times we've retried so far.
|
16
|
+
# * 'error_message' - the message from the exception
|
17
|
+
# * 'error_class' - the exception class
|
18
|
+
# * 'failed_at' - the first time it failed
|
19
|
+
# * 'retried_at' - the last time it was retried
|
20
|
+
#
|
21
|
+
# We don't store the backtrace as that can add a lot of overhead
|
22
|
+
# to the message and everyone is using Airbrake, right?
|
23
|
+
class RetryJobs
|
24
|
+
include Sidekiq::Util
|
25
|
+
include Sidekiq::Retry
|
26
|
+
|
27
|
+
def call(worker, msg, queue)
|
28
|
+
yield
|
29
|
+
rescue => e
|
30
|
+
msg['queue'] = queue
|
31
|
+
msg['error_message'] = e.message
|
32
|
+
msg['error_class'] = e.class.name
|
33
|
+
count = if msg['retry_count']
|
34
|
+
msg['retried_at'] = Time.now.utc
|
35
|
+
msg['retry_count'] += 1
|
36
|
+
else
|
37
|
+
msg['failed_at'] = Time.now.utc
|
38
|
+
msg['retry_count'] = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
if count <= MAX_COUNT
|
42
|
+
delay = DELAY.call(count)
|
43
|
+
logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
|
44
|
+
retry_at = Time.now.to_f + delay
|
45
|
+
payload = MultiJson.encode(msg)
|
46
|
+
Sidekiq.redis do |conn|
|
47
|
+
conn.zadd('retry', retry_at.to_s, payload)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
# Goodbye dear message, you (re)tried your best I'm sure.
|
51
|
+
logger.debug { "Dropping message after hitting the retry maximum: #{msg}" }
|
52
|
+
end
|
53
|
+
raise
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -5,7 +5,9 @@ module Sidekiq
|
|
5
5
|
def call(*args)
|
6
6
|
yield
|
7
7
|
ensure
|
8
|
-
|
8
|
+
json = MultiJson.encode(args[1])
|
9
|
+
hash = Digest::MD5.hexdigest(json)
|
10
|
+
Sidekiq.redis {|conn| conn.del(hash) }
|
9
11
|
end
|
10
12
|
end
|
11
13
|
end
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -4,7 +4,7 @@ require 'sidekiq/util'
|
|
4
4
|
require 'sidekiq/middleware/server/active_record'
|
5
5
|
require 'sidekiq/middleware/server/exception_handler'
|
6
6
|
require 'sidekiq/middleware/server/unique_jobs'
|
7
|
-
require 'sidekiq/middleware/server/
|
7
|
+
require 'sidekiq/middleware/server/retry_jobs'
|
8
8
|
require 'sidekiq/middleware/server/logging'
|
9
9
|
|
10
10
|
module Sidekiq
|
@@ -21,6 +21,7 @@ module Sidekiq
|
|
21
21
|
m.add Middleware::Server::ExceptionHandler
|
22
22
|
m.add Middleware::Server::Logging
|
23
23
|
m.add Middleware::Server::UniqueJobs
|
24
|
+
m.add Middleware::Server::RetryJobs
|
24
25
|
m.add Middleware::Server::ActiveRecord
|
25
26
|
end
|
26
27
|
end
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
require 'celluloid'
|
3
|
+
|
4
|
+
module Sidekiq
|
5
|
+
##
|
6
|
+
# Sidekiq's retry support assumes a typical development lifecycle:
|
7
|
+
# 0. push some code changes with a bug in it
|
8
|
+
# 1. bug causes message processing to fail, sidekiq's middleware captures
|
9
|
+
# the message and pushes it onto a retry queue
|
10
|
+
# 2. sidekiq retries messages in the retry queue multiple times with
|
11
|
+
# an exponential delay, the message continues to fail
|
12
|
+
# 3. after a few days, a developer deploys a fix. the message is
|
13
|
+
# reprocessed successfully.
|
14
|
+
# 4. if 3 never happens, sidekiq will eventually give up and throw the
|
15
|
+
# message away.
|
16
|
+
module Retry
|
17
|
+
|
18
|
+
# delayed_job uses the same basic formula
|
19
|
+
MAX_COUNT = 25
|
20
|
+
DELAY = proc { |count| (count ** 4) + 15 }
|
21
|
+
POLL_INTERVAL = 15
|
22
|
+
|
23
|
+
##
|
24
|
+
# The Poller checks Redis every N seconds for messages in the retry
|
25
|
+
# set have passed their retry timestamp and should be retried. If so, it
|
26
|
+
# just pops the message back onto its original queue so the
|
27
|
+
# workers can pick it up like any other message.
|
28
|
+
class Poller
|
29
|
+
include Celluloid
|
30
|
+
include Sidekiq::Util
|
31
|
+
|
32
|
+
def poll
|
33
|
+
watchdog('retry poller thread died!') do
|
34
|
+
|
35
|
+
Sidekiq.redis do |conn|
|
36
|
+
# A message's "score" in Redis is the time at which it should be retried.
|
37
|
+
# Just check Redis for the set of messages with a timestamp before now.
|
38
|
+
messages = nil
|
39
|
+
now = Time.now.to_f.to_s
|
40
|
+
(messages, _) = conn.multi do
|
41
|
+
conn.zrangebyscore('retry', '-inf', now)
|
42
|
+
conn.zremrangebyscore('retry', '-inf', now)
|
43
|
+
end
|
44
|
+
|
45
|
+
messages.each do |message|
|
46
|
+
logger.debug { "Retrying #{message}" }
|
47
|
+
msg = MultiJson.decode(message)
|
48
|
+
conn.rpush("queue:#{msg['queue']}", message)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
after(POLL_INTERVAL) { poll }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web.rb
CHANGED
@@ -56,6 +56,17 @@ module Sidekiq
|
|
56
56
|
Sidekiq.redis { |conn| conn.get('stat:failed') } || 0
|
57
57
|
end
|
58
58
|
|
59
|
+
def retry_count
|
60
|
+
Sidekiq.redis { |conn| conn.zcard('retry') }
|
61
|
+
end
|
62
|
+
|
63
|
+
def retries
|
64
|
+
Sidekiq.redis do |conn|
|
65
|
+
results = conn.zrange('retry', 0, 25, :withscores => true)
|
66
|
+
results.each_slice(2).map { |msg, score| [MultiJson.decode(msg), Float(score)] }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
59
70
|
def queues
|
60
71
|
Sidekiq.redis do |conn|
|
61
72
|
conn.smembers('queues').map do |q|
|
data/myapp/config/routes.rb
CHANGED
data/sidekiq.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |gem|
|
|
7
7
|
gem.description = gem.summary = "Simple, efficient message processing for Ruby"
|
8
8
|
gem.homepage = "http://mperham.github.com/sidekiq"
|
9
9
|
|
10
|
-
gem.executables = ['sidekiq']
|
10
|
+
gem.executables = ['sidekiq', 'sidekiqctl']
|
11
11
|
gem.files = `git ls-files`.split("\n")
|
12
12
|
gem.test_files = `git ls-files -- test/*`.split("\n")
|
13
13
|
gem.name = "sidekiq"
|
@@ -15,13 +15,13 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.version = Sidekiq::VERSION
|
16
16
|
gem.add_dependency 'redis'
|
17
17
|
gem.add_dependency 'redis-namespace'
|
18
|
-
gem.add_dependency 'connection_pool', '
|
18
|
+
gem.add_dependency 'connection_pool', '~> 0.9.0'
|
19
19
|
gem.add_dependency 'celluloid'
|
20
20
|
gem.add_dependency 'multi_json'
|
21
21
|
gem.add_development_dependency 'minitest'
|
22
22
|
gem.add_development_dependency 'sinatra'
|
23
23
|
gem.add_development_dependency 'slim'
|
24
24
|
gem.add_development_dependency 'rake'
|
25
|
-
gem.add_development_dependency 'actionmailer'
|
26
|
-
gem.add_development_dependency 'activerecord'
|
25
|
+
gem.add_development_dependency 'actionmailer', '~> 3'
|
26
|
+
gem.add_development_dependency 'activerecord', '~> 3'
|
27
27
|
end
|
data/test/helper.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
if false
|
1
|
+
if ENV.has_key?("SIMPLECOV")
|
3
2
|
require 'simplecov'
|
4
3
|
SimpleCov.start
|
5
4
|
end
|
@@ -13,4 +12,4 @@ require 'sidekiq/util'
|
|
13
12
|
Sidekiq::Util.logger.level = Logger::ERROR
|
14
13
|
|
15
14
|
require 'sidekiq/redis_connection'
|
16
|
-
REDIS = Sidekiq::RedisConnection.create(:url =>
|
15
|
+
REDIS = Sidekiq::RedisConnection.create(:url => "redis://localhost/15")
|
data/test/test_client.rb
CHANGED
@@ -28,17 +28,19 @@ class TestClient < MiniTest::Unit::TestCase
|
|
28
28
|
describe 'with mock redis' do
|
29
29
|
before do
|
30
30
|
@redis = MiniTest::Mock.new
|
31
|
-
def @redis.multi; yield
|
31
|
+
def @redis.multi; yield if block_given?; end
|
32
32
|
def @redis.set(*); true; end
|
33
33
|
def @redis.sadd(*); true; end
|
34
34
|
def @redis.srem(*); true; end
|
35
35
|
def @redis.get(*); nil; end
|
36
36
|
def @redis.del(*); nil; end
|
37
37
|
def @redis.incrby(*); nil; end
|
38
|
-
def @redis.setex(*);
|
38
|
+
def @redis.setex(*); true; end
|
39
39
|
def @redis.expire(*); true; end
|
40
|
+
def @redis.watch(*); true; end
|
40
41
|
def @redis.with_connection; yield self; end
|
41
42
|
def @redis.with; yield self; end
|
43
|
+
def @redis.exec; true; end
|
42
44
|
Sidekiq.instance_variable_set(:@redis, @redis)
|
43
45
|
end
|
44
46
|
|
data/test/test_extensions.rb
CHANGED
@@ -28,10 +28,6 @@ class TestExtensions < MiniTest::Unit::TestCase
|
|
28
28
|
assert_equal 1, Sidekiq.redis.llen('queue:default')
|
29
29
|
end
|
30
30
|
|
31
|
-
it 'allows delayed exection of ActiveRecord instance methods' do
|
32
|
-
skip('requires a database')
|
33
|
-
end
|
34
|
-
|
35
31
|
class UserMailer < ActionMailer::Base
|
36
32
|
def greetings(a, b)
|
37
33
|
raise "Should not be called!"
|
@@ -45,6 +41,28 @@ class TestExtensions < MiniTest::Unit::TestCase
|
|
45
41
|
assert_equal ['default'], Sidekiq::Client.registered_queues
|
46
42
|
assert_equal 1, Sidekiq.redis.llen('queue:default')
|
47
43
|
end
|
44
|
+
end
|
48
45
|
|
46
|
+
describe 'sidekiq rails extensions configuration' do
|
47
|
+
before do
|
48
|
+
@options = Sidekiq.options
|
49
|
+
end
|
50
|
+
|
51
|
+
after do
|
52
|
+
Sidekiq.options = @options
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should set enable_rails_extensions option to true by default' do
|
56
|
+
assert Sidekiq.options[:enable_rails_extensions]
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should extend ActiveRecord and ActiveMailer if enable_rails_extensions is true' do
|
60
|
+
assert Sidekiq.hook_rails!
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should not extend ActiveRecord and ActiveMailer if enable_rails_extensions is false' do
|
64
|
+
Sidekiq.options = { :enable_rails_extensions => false }
|
65
|
+
refute Sidekiq.hook_rails!
|
66
|
+
end
|
49
67
|
end
|
50
68
|
end
|
data/test/test_middleware.rb
CHANGED
@@ -6,9 +6,22 @@ require 'sidekiq/processor'
|
|
6
6
|
class TestMiddleware < MiniTest::Unit::TestCase
|
7
7
|
describe 'middleware chain' do
|
8
8
|
before do
|
9
|
+
$errors = []
|
9
10
|
Sidekiq.redis = REDIS
|
10
11
|
end
|
11
12
|
|
13
|
+
it 'handles errors' do
|
14
|
+
handler = Sidekiq::Middleware::Server::ExceptionHandler.new
|
15
|
+
|
16
|
+
assert_raises ArgumentError do
|
17
|
+
handler.call('', { :a => 1 }, 'default') do
|
18
|
+
raise ArgumentError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
assert_equal 1, $errors.size
|
22
|
+
assert_equal({ :a => 1 }, $errors[0][:parameters])
|
23
|
+
end
|
24
|
+
|
12
25
|
class CustomMiddleware
|
13
26
|
def initialize(name, recorder)
|
14
27
|
@name = name
|
@@ -42,10 +55,6 @@ class TestMiddleware < MiniTest::Unit::TestCase
|
|
42
55
|
end
|
43
56
|
|
44
57
|
it 'executes middleware in the proper order' do
|
45
|
-
Sidekiq::Middleware::Server::UniqueJobs.class_eval do
|
46
|
-
def call(*args); yield; end
|
47
|
-
end
|
48
|
-
|
49
58
|
recorder = []
|
50
59
|
msg = { 'class' => CustomWorker.to_s, 'args' => [recorder] }
|
51
60
|
|
@@ -74,3 +83,10 @@ class TestMiddleware < MiniTest::Unit::TestCase
|
|
74
83
|
end
|
75
84
|
end
|
76
85
|
end
|
86
|
+
|
87
|
+
class FakeAirbrake
|
88
|
+
def self.notify(ex, hash)
|
89
|
+
$errors << hash
|
90
|
+
end
|
91
|
+
end
|
92
|
+
Airbrake = FakeAirbrake
|
data/test/test_processor.rb
CHANGED
@@ -8,6 +8,7 @@ class TestProcessor < MiniTest::Unit::TestCase
|
|
8
8
|
$errors = []
|
9
9
|
@boss = MiniTest::Mock.new
|
10
10
|
Celluloid.logger = nil
|
11
|
+
Sidekiq.redis = REDIS
|
11
12
|
end
|
12
13
|
|
13
14
|
class MockWorker
|
@@ -27,26 +28,5 @@ class TestProcessor < MiniTest::Unit::TestCase
|
|
27
28
|
assert_equal 1, $invokes
|
28
29
|
assert_equal 0, $errors.size
|
29
30
|
end
|
30
|
-
|
31
|
-
it 'handles exceptions' do
|
32
|
-
msg = { 'class' => MockWorker.to_s, 'args' => ['boom'] }
|
33
|
-
processor = ::Sidekiq::Processor.new(@boss)
|
34
|
-
assert_raises RuntimeError do
|
35
|
-
processor.process(msg, 'default')
|
36
|
-
end
|
37
|
-
@boss.verify
|
38
|
-
assert_equal 0, $invokes
|
39
|
-
assert_equal 1, $errors.size
|
40
|
-
assert_equal msg, $errors[0][:parameters]
|
41
|
-
end
|
42
|
-
|
43
31
|
end
|
44
32
|
end
|
45
|
-
|
46
|
-
class FakeAirbrake
|
47
|
-
def self.notify(ex, hash)
|
48
|
-
$errors << hash
|
49
|
-
end
|
50
|
-
end
|
51
|
-
Airbrake = FakeAirbrake
|
52
|
-
|
data/test/test_retry.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'sidekiq/retry'
|
3
|
+
require 'sidekiq/middleware/server/retry_jobs'
|
4
|
+
|
5
|
+
class TestRetry < MiniTest::Unit::TestCase
|
6
|
+
describe 'middleware' do
|
7
|
+
before do
|
8
|
+
@redis = MiniTest::Mock.new
|
9
|
+
# Ugh, this is terrible.
|
10
|
+
Sidekiq.instance_variable_set(:@redis, @redis)
|
11
|
+
|
12
|
+
def @redis.with; yield self; end
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'handles a new failed message' do
|
16
|
+
@redis.expect :zadd, 1, ['retry', String, String]
|
17
|
+
msg = { 'class' => 'Bob', 'args' => [1,2,'foo'] }
|
18
|
+
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
19
|
+
assert_raises RuntimeError do
|
20
|
+
handler.call('', msg, 'default') do
|
21
|
+
raise "kerblammo!"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
assert_equal 'default', msg["queue"]
|
25
|
+
assert_equal 'kerblammo!', msg["error_message"]
|
26
|
+
assert_equal 'RuntimeError', msg["error_class"]
|
27
|
+
assert_equal 0, msg["retry_count"]
|
28
|
+
assert msg["failed_at"]
|
29
|
+
@redis.verify
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'handles a recurring failed message' do
|
33
|
+
@redis.expect :zadd, 1, ['retry', String, String]
|
34
|
+
now = Time.now.utc
|
35
|
+
msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>10}
|
36
|
+
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
37
|
+
assert_raises RuntimeError do
|
38
|
+
handler.call('', msg, 'default') do
|
39
|
+
raise "kerblammo!"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
assert_equal 'default', msg["queue"]
|
43
|
+
assert_equal 'kerblammo!', msg["error_message"]
|
44
|
+
assert_equal 'RuntimeError', msg["error_class"]
|
45
|
+
assert_equal 11, msg["retry_count"]
|
46
|
+
assert msg["failed_at"]
|
47
|
+
@redis.verify
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'throws away old messages after too many retries' do
|
51
|
+
now = Time.now.utc
|
52
|
+
msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>25}
|
53
|
+
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
54
|
+
assert_raises RuntimeError do
|
55
|
+
handler.call('', msg, 'default') do
|
56
|
+
raise "kerblammo!"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
@redis.verify
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'poller' do
|
64
|
+
before do
|
65
|
+
@redis = MiniTest::Mock.new
|
66
|
+
Sidekiq.instance_variable_set(:@redis, @redis)
|
67
|
+
|
68
|
+
def @redis.with; yield self; end
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should poll like a bad mother...SHUT YO MOUTH' do
|
72
|
+
fake_msg = MultiJson.encode({ 'class' => 'Bob', 'args' => [1,2], 'queue' => 'someq' })
|
73
|
+
@redis.expect :multi, [[fake_msg], 1], []
|
74
|
+
@redis.expect :rpush, 1, ['queue:someq', fake_msg]
|
75
|
+
|
76
|
+
inst = Sidekiq::Retry::Poller.new
|
77
|
+
inst.poll
|
78
|
+
|
79
|
+
@redis.verify
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
data/web/views/index.slim
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
p Processed: #{processed}
|
5
5
|
p Failed: #{failed}
|
6
6
|
p Workers: #{workers.size}
|
7
|
+
p Retries Pending: #{retry_count}
|
7
8
|
|
8
9
|
.tabbable
|
9
10
|
ul.nav.nav-tabs
|
@@ -11,6 +12,8 @@
|
|
11
12
|
a href="#workers" data-toggle="tab" Workers
|
12
13
|
li
|
13
14
|
a href="#queues" data-toggle="tab" Queues
|
15
|
+
li
|
16
|
+
a href="#retries" data-toggle="tab" Retries
|
14
17
|
.tab-content
|
15
18
|
#workers.tab-pane.active
|
16
19
|
table class="table table-striped table-bordered"
|
@@ -40,3 +43,20 @@
|
|
40
43
|
a href="queues/#{queue}" #{queue}
|
41
44
|
td= size
|
42
45
|
|
46
|
+
#retries.tab-pane
|
47
|
+
table class="table table-striped table-bordered"
|
48
|
+
tr
|
49
|
+
th Next Retry
|
50
|
+
th Retry Count
|
51
|
+
th Queue
|
52
|
+
th Worker
|
53
|
+
th Args
|
54
|
+
- retries.each do |(msg, score)|
|
55
|
+
tr
|
56
|
+
td= Time.at(score)
|
57
|
+
td= msg['retry_count']
|
58
|
+
td
|
59
|
+
a href="queues/#{msg['queue']}" #{msg['queue']}
|
60
|
+
td= msg['class']
|
61
|
+
td= msg['args'].inspect[0..100]
|
62
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03-
|
12
|
+
date: 2012-03-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
16
|
-
requirement: &
|
16
|
+
requirement: &70249655983600 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70249655983600
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: redis-namespace
|
27
|
-
requirement: &
|
27
|
+
requirement: &70249655982380 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,21 +32,21 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70249655982380
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: connection_pool
|
38
|
-
requirement: &
|
38
|
+
requirement: &70249655981700 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
|
-
- -
|
41
|
+
- - ~>
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: 0.9.0
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70249655981700
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: celluloid
|
49
|
-
requirement: &
|
49
|
+
requirement: &70249655981280 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70249655981280
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: multi_json
|
60
|
-
requirement: &
|
60
|
+
requirement: &70249655980820 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70249655980820
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: minitest
|
71
|
-
requirement: &
|
71
|
+
requirement: &70249655980400 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70249655980400
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: sinatra
|
82
|
-
requirement: &
|
82
|
+
requirement: &70249656007360 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70249656007360
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: slim
|
93
|
-
requirement: &
|
93
|
+
requirement: &70249656006940 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ! '>='
|
@@ -98,10 +98,10 @@ dependencies:
|
|
98
98
|
version: '0'
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *70249656006940
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: rake
|
104
|
-
requirement: &
|
104
|
+
requirement: &70249656006520 !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
107
|
- - ! '>='
|
@@ -109,34 +109,35 @@ dependencies:
|
|
109
109
|
version: '0'
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
|
-
version_requirements: *
|
112
|
+
version_requirements: *70249656006520
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: actionmailer
|
115
|
-
requirement: &
|
115
|
+
requirement: &70249656006020 !ruby/object:Gem::Requirement
|
116
116
|
none: false
|
117
117
|
requirements:
|
118
|
-
- -
|
118
|
+
- - ~>
|
119
119
|
- !ruby/object:Gem::Version
|
120
|
-
version: '
|
120
|
+
version: '3'
|
121
121
|
type: :development
|
122
122
|
prerelease: false
|
123
|
-
version_requirements: *
|
123
|
+
version_requirements: *70249656006020
|
124
124
|
- !ruby/object:Gem::Dependency
|
125
125
|
name: activerecord
|
126
|
-
requirement: &
|
126
|
+
requirement: &70249656005520 !ruby/object:Gem::Requirement
|
127
127
|
none: false
|
128
128
|
requirements:
|
129
|
-
- -
|
129
|
+
- - ~>
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
131
|
+
version: '3'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
|
-
version_requirements: *
|
134
|
+
version_requirements: *70249656005520
|
135
135
|
description: Simple, efficient message processing for Ruby
|
136
136
|
email:
|
137
137
|
- mperham@gmail.com
|
138
138
|
executables:
|
139
139
|
- sidekiq
|
140
|
+
- sidekiqctl
|
140
141
|
extensions: []
|
141
142
|
extra_rdoc_files: []
|
142
143
|
files:
|
@@ -151,6 +152,7 @@ files:
|
|
151
152
|
- TODO.md
|
152
153
|
- bin/client
|
153
154
|
- bin/sidekiq
|
155
|
+
- bin/sidekiqctl
|
154
156
|
- config.ru
|
155
157
|
- examples/chef/cookbooks/sidekiq/README.rdoc
|
156
158
|
- examples/chef/cookbooks/sidekiq/recipes/default.rb
|
@@ -162,6 +164,7 @@ files:
|
|
162
164
|
- examples/por.rb
|
163
165
|
- examples/scheduling.rb
|
164
166
|
- examples/sinkiq.rb
|
167
|
+
- examples/web-ui.png
|
165
168
|
- lib/sidekiq.rb
|
166
169
|
- lib/sidekiq/capistrano.rb
|
167
170
|
- lib/sidekiq/cli.rb
|
@@ -176,10 +179,12 @@ files:
|
|
176
179
|
- lib/sidekiq/middleware/server/exception_handler.rb
|
177
180
|
- lib/sidekiq/middleware/server/failure_jobs.rb
|
178
181
|
- lib/sidekiq/middleware/server/logging.rb
|
182
|
+
- lib/sidekiq/middleware/server/retry_jobs.rb
|
179
183
|
- lib/sidekiq/middleware/server/unique_jobs.rb
|
180
184
|
- lib/sidekiq/processor.rb
|
181
185
|
- lib/sidekiq/rails.rb
|
182
186
|
- lib/sidekiq/redis_connection.rb
|
187
|
+
- lib/sidekiq/retry.rb
|
183
188
|
- lib/sidekiq/testing.rb
|
184
189
|
- lib/sidekiq/util.rb
|
185
190
|
- lib/sidekiq/version.rb
|
@@ -235,6 +240,7 @@ files:
|
|
235
240
|
- test/test_manager.rb
|
236
241
|
- test/test_middleware.rb
|
237
242
|
- test/test_processor.rb
|
243
|
+
- test/test_retry.rb
|
238
244
|
- test/test_stats.rb
|
239
245
|
- test/test_testing.rb
|
240
246
|
- test/test_web.rb
|
@@ -295,6 +301,7 @@ test_files:
|
|
295
301
|
- test/test_manager.rb
|
296
302
|
- test/test_middleware.rb
|
297
303
|
- test/test_processor.rb
|
304
|
+
- test/test_retry.rb
|
298
305
|
- test/test_stats.rb
|
299
306
|
- test/test_testing.rb
|
300
307
|
- test/test_web.rb
|