sidekiq 2.6.4 → 2.6.5
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/.rvmrc +1 -1
- data/Changes.md +11 -0
- data/examples/sidekiq +92 -0
- data/lib/sidekiq/capistrano.rb +6 -6
- data/lib/sidekiq/fetch.rb +17 -3
- data/lib/sidekiq/manager.rb +20 -12
- data/lib/sidekiq/middleware/server/retry_jobs.rb +5 -1
- data/lib/sidekiq/version.rb +1 -1
- data/test/test_fetch.rb +12 -1
- data/test/test_retry.rb +18 -0
- metadata +3 -2
data/.rvmrc
CHANGED
data/Changes.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
2.6.5
|
2
|
+
-----------
|
3
|
+
|
4
|
+
- Several reliability fixes for job requeueing upon termination [apinstein, #622, #624]
|
5
|
+
- Fix typo in capistrano recipe
|
6
|
+
- Add `retry_queue` option so retries can be given lower priority [ryanlower, #620]
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
sidekiq_options queue: 'high', retry_queue: 'low'
|
10
|
+
```
|
11
|
+
|
1
12
|
2.6.4
|
2
13
|
-----------
|
3
14
|
|
data/examples/sidekiq
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
# sidekiq Init script for Sidekiq
|
3
|
+
# chkconfig: 345 100 75
|
4
|
+
#
|
5
|
+
# Description: Starts and Stops Sidekiq message processor for Stratus application.
|
6
|
+
#
|
7
|
+
# User-specified exit parameters used in this script:
|
8
|
+
#
|
9
|
+
# Exit Code 5 - Incorrect User ID
|
10
|
+
# Exit Code 6 - Directory not found
|
11
|
+
|
12
|
+
|
13
|
+
#Variable Set
|
14
|
+
APP="stratus"
|
15
|
+
APP_DIR="/opt/railsapps/${APP}"
|
16
|
+
APP_CONFIG="${APP_DIR}/config"
|
17
|
+
LOG_FILE="$APP_DIR/log/sidekiq.log"
|
18
|
+
LOCK_FILE="$APP_DIR/${APP}-lock"
|
19
|
+
PID_FILE="$APP_DIR/${APP}.pid"
|
20
|
+
GEMFILE="$APP_DIR/Gemfile"
|
21
|
+
SIDEKIQ="sidekiq"
|
22
|
+
APP_ENV="production"
|
23
|
+
BUNDLE="bundle"
|
24
|
+
|
25
|
+
START_CMD="$BUNDLE exec $SIDEKIQ -e $APP_ENV -P $PID_FILE"
|
26
|
+
RETVAL=0
|
27
|
+
|
28
|
+
|
29
|
+
start() {
|
30
|
+
|
31
|
+
status
|
32
|
+
if [ $? -eq 1 ]; then
|
33
|
+
|
34
|
+
[ `id -u` == '0' ] || (echo "$SIDEKIQ runs as root only .."; exit 5)
|
35
|
+
[ -d $APP_DIR ] || (echo "$APP_DIR not found!.. Exiting"; exit 6)
|
36
|
+
cd $APP_DIR
|
37
|
+
echo "Starting $SIDEKIQ message processor .. "
|
38
|
+
$START_CMD >> $LOG_FILE 2>&1 &
|
39
|
+
RETVAL=$?
|
40
|
+
#Sleeping for 8 seconds for process to be precisely visible in process table - See status ()
|
41
|
+
sleep 8
|
42
|
+
[ $RETVAL -eq 0 ] && touch $LOCK_FILE
|
43
|
+
return $RETVAL
|
44
|
+
else
|
45
|
+
echo "$SIDEKIQ message processor is already running .. "
|
46
|
+
fi
|
47
|
+
|
48
|
+
|
49
|
+
}
|
50
|
+
|
51
|
+
stop() {
|
52
|
+
|
53
|
+
echo "Stopping $SIDEKIQ message processor .."
|
54
|
+
SIG="INT"
|
55
|
+
kill -$SIG `cat $PID_FILE`
|
56
|
+
RETVAL=$?
|
57
|
+
[ $RETVAL -eq 0 ] && rm -f $LOCK_FILE
|
58
|
+
return $RETVAL
|
59
|
+
}
|
60
|
+
|
61
|
+
|
62
|
+
status() {
|
63
|
+
|
64
|
+
ps -ef | grep 'sidekiq [0-9].[0-9].[0-9]' | grep -v grep
|
65
|
+
return $?
|
66
|
+
}
|
67
|
+
|
68
|
+
|
69
|
+
case "$1" in
|
70
|
+
start)
|
71
|
+
start
|
72
|
+
;;
|
73
|
+
stop)
|
74
|
+
stop
|
75
|
+
;;
|
76
|
+
status)
|
77
|
+
status
|
78
|
+
|
79
|
+
if [ $? -eq 0 ]; then
|
80
|
+
echo "$SIDEKIQ message processor is running .."
|
81
|
+
RETVAL=0
|
82
|
+
else
|
83
|
+
echo "$SIDEKIQ message processor is stopped .."
|
84
|
+
RETVAL=1
|
85
|
+
fi
|
86
|
+
;;
|
87
|
+
*)
|
88
|
+
echo "Usage: $0 {start|stop|status}"
|
89
|
+
exit 0
|
90
|
+
;;
|
91
|
+
esac
|
92
|
+
exit $RETVAL
|
data/lib/sidekiq/capistrano.rb
CHANGED
@@ -13,21 +13,21 @@ Capistrano::Configuration.instance.load do
|
|
13
13
|
|
14
14
|
namespace :sidekiq do
|
15
15
|
def for_each_process(&block)
|
16
|
-
|
17
|
-
yield
|
16
|
+
fetch(:sidekiq_processes).times do |idx|
|
17
|
+
yield((idx == 0 ? "#{fetch(:sidekiq_pid)}" : "#{fetch(:sidekiq_pid)}-#{idx}"), idx)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
desc "Quiet sidekiq (stop accepting new work)"
|
22
22
|
task :quiet, :roles => lambda { fetch(:sidekiq_role) }, :on_no_matching_servers => :continue do
|
23
|
-
for_each_process do |pid_file|
|
23
|
+
for_each_process do |pid_file, idx|
|
24
24
|
run "if [ -d #{current_path} ] && [ -f #{pid_file} ]; then cd #{current_path} && #{fetch(:sidekiqctl_cmd)} quiet #{pid_file} ; fi"
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
desc "Stop sidekiq"
|
29
29
|
task :stop, :roles => lambda { fetch(:sidekiq_role) }, :on_no_matching_servers => :continue do
|
30
|
-
for_each_process do |pid_file|
|
30
|
+
for_each_process do |pid_file, idx|
|
31
31
|
run "if [ -d #{current_path} ] && [ -f #{pid_file} ]; then cd #{current_path} && #{fetch(:sidekiqctl_cmd)} stop #{pid_file} #{fetch :sidekiq_timeout} ; fi"
|
32
32
|
end
|
33
33
|
end
|
@@ -35,8 +35,8 @@ Capistrano::Configuration.instance.load do
|
|
35
35
|
desc "Start sidekiq"
|
36
36
|
task :start, :roles => lambda { fetch(:sidekiq_role) }, :on_no_matching_servers => :continue do
|
37
37
|
rails_env = fetch(:rails_env, "production")
|
38
|
-
for_each_process do |pid_file|
|
39
|
-
run "cd #{current_path} ; nohup #{fetch(:sidekiq_cmd)} -e #{rails_env} -C #{current_path}/config/sidekiq.yml -P #{pid_file} >> #{current_path}/log/sidekiq.log 2>&1 &", :pty => false
|
38
|
+
for_each_process do |pid_file, idx|
|
39
|
+
run "cd #{current_path} ; nohup #{fetch(:sidekiq_cmd)} -e #{rails_env} -C #{current_path}/config/sidekiq.yml -i #{idx} -P #{pid_file} >> #{current_path}/log/sidekiq.log 2>&1 &", :pty => false
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
data/lib/sidekiq/fetch.rb
CHANGED
@@ -71,6 +71,22 @@ module Sidekiq
|
|
71
71
|
UnitOfWork.new(*work) if work
|
72
72
|
end
|
73
73
|
|
74
|
+
def self.bulk_requeue(inprogress)
|
75
|
+
Sidekiq.logger.debug { "Re-queueing terminated jobs" }
|
76
|
+
jobs_to_requeue = {}
|
77
|
+
inprogress.each do |unit_of_work|
|
78
|
+
jobs_to_requeue[unit_of_work.queue] ||= []
|
79
|
+
jobs_to_requeue[unit_of_work.queue] << unit_of_work.message
|
80
|
+
end
|
81
|
+
|
82
|
+
Sidekiq.redis do |conn|
|
83
|
+
jobs_to_requeue.each do |queue, jobs|
|
84
|
+
conn.rpush(queue, jobs)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
Sidekiq.logger.info("Pushed #{inprogress.size} messages back to Redis")
|
88
|
+
end
|
89
|
+
|
74
90
|
UnitOfWork = Struct.new(:queue, :message) do
|
75
91
|
def acknowledge
|
76
92
|
# nothing to do
|
@@ -93,9 +109,7 @@ module Sidekiq
|
|
93
109
|
# recreate the queue command each time we invoke Redis#blpop
|
94
110
|
# to honor weights and avoid queue starvation.
|
95
111
|
def queues_cmd
|
96
|
-
|
97
|
-
queues = @queues.sample(@unique_queues.size).uniq
|
98
|
-
queues.concat(@unique_queues - queues)
|
112
|
+
queues = @strictly_ordered_queues ? @unique_queues.dup : @queues.shuffle.uniq
|
99
113
|
queues << Sidekiq::Fetcher::TIMEOUT
|
100
114
|
end
|
101
115
|
end
|
data/lib/sidekiq/manager.rb
CHANGED
@@ -112,23 +112,31 @@ module Sidekiq
|
|
112
112
|
# They must die but their messages shall live on.
|
113
113
|
logger.info("Still waiting for #{@busy.size} busy workers")
|
114
114
|
|
115
|
+
# Re-enqueue terminated jobs
|
116
|
+
# NOTE: You may notice that we may push a job back to redis before
|
117
|
+
# the worker thread is terminated. This is ok because Sidekiq's
|
118
|
+
# contract says that jobs are run AT LEAST once. Process termination
|
119
|
+
# is delayed until we're certain the jobs are back in Redis because
|
120
|
+
# it is worse to lose a job than to run it twice.
|
121
|
+
Sidekiq.options[:fetch].bulk_requeue(@in_progress.values)
|
122
|
+
|
123
|
+
# Clearing workers in Redis
|
124
|
+
# NOTE: we do this before terminating worker threads because the
|
125
|
+
# process will likely receive a hard shutdown soon anyway, which
|
126
|
+
# means the threads will killed.
|
127
|
+
logger.debug { "Clearing workers in redis" }
|
115
128
|
Sidekiq.redis do |conn|
|
116
|
-
logger.debug { "Clearing workers in redis" }
|
117
129
|
workers = conn.smembers('workers')
|
118
|
-
workers.
|
119
|
-
|
130
|
+
workers_to_remove = workers.select do |worker_name|
|
131
|
+
worker_name =~ /:#{process_id}-/
|
120
132
|
end
|
133
|
+
conn.srem('workers', workers_to_remove)
|
134
|
+
end
|
121
135
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
# we'll use the object_id to track the worker's data here.
|
126
|
-
processor.terminate if processor.alive?
|
127
|
-
msg, queue = @in_progress[processor.object_id]
|
128
|
-
conn.lpush("queue:#{queue}", msg)
|
129
|
-
end
|
136
|
+
logger.debug { "Terminating worker threads" }
|
137
|
+
@busy.each do |processor|
|
138
|
+
processor.terminate if processor.alive?
|
130
139
|
end
|
131
|
-
logger.info("Pushed #{@busy.size} messages back to Redis")
|
132
140
|
|
133
141
|
after(0) { signal(:shutdown) }
|
134
142
|
end
|
@@ -51,7 +51,11 @@ module Sidekiq
|
|
51
51
|
raise e unless msg['retry']
|
52
52
|
max_retry_attempts = retry_attempts_from(msg['retry'], DEFAULT_MAX_RETRY_ATTEMPTS)
|
53
53
|
|
54
|
-
msg['queue'] =
|
54
|
+
msg['queue'] = if msg['retry_queue']
|
55
|
+
msg['retry_queue']
|
56
|
+
else
|
57
|
+
queue
|
58
|
+
end
|
55
59
|
msg['error_message'] = e.message
|
56
60
|
msg['error_class'] = e.class.name
|
57
61
|
count = if msg['retry_count']
|
data/lib/sidekiq/version.rb
CHANGED
data/test/test_fetch.rb
CHANGED
@@ -5,7 +5,7 @@ class TestFetcher < MiniTest::Unit::TestCase
|
|
5
5
|
|
6
6
|
def setup
|
7
7
|
Sidekiq.redis do |conn|
|
8
|
-
conn.
|
8
|
+
conn.flushdb
|
9
9
|
conn.rpush('queue:basic', 'msg')
|
10
10
|
end
|
11
11
|
end
|
@@ -28,4 +28,15 @@ class TestFetcher < MiniTest::Unit::TestCase
|
|
28
28
|
cmd = fetch.queues_cmd
|
29
29
|
assert_equal cmd, ['queue:basic', 'queue:bar', 1]
|
30
30
|
end
|
31
|
+
|
32
|
+
def test_basic_fetch_bulk_requeue
|
33
|
+
q1 = Sidekiq::Queue.new('foo')
|
34
|
+
q2 = Sidekiq::Queue.new('bar')
|
35
|
+
assert_equal 0, q1.size
|
36
|
+
assert_equal 0, q2.size
|
37
|
+
uow = Sidekiq::BasicFetch::UnitOfWork
|
38
|
+
Sidekiq::BasicFetch.bulk_requeue([uow.new('queue:foo', 'bob'), uow.new('queue:foo', 'bar'), uow.new('queue:bar', 'widget')])
|
39
|
+
assert_equal 2, q1.size
|
40
|
+
assert_equal 1, q2.size
|
41
|
+
end
|
31
42
|
end
|
data/test/test_retry.rb
CHANGED
@@ -86,6 +86,24 @@ class TestRetry < MiniTest::Unit::TestCase
|
|
86
86
|
@redis.verify
|
87
87
|
end
|
88
88
|
|
89
|
+
it 'allows a retry queue' do
|
90
|
+
@redis.expect :zadd, 1, ['retry', String, String]
|
91
|
+
msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true, 'retry_queue' => 'retry' }
|
92
|
+
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
93
|
+
assert_raises RuntimeError do
|
94
|
+
handler.call('', msg, 'default') do
|
95
|
+
raise "kerblammo!"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
assert_equal 'retry', msg["queue"]
|
99
|
+
assert_equal 'kerblammo!', msg["error_message"]
|
100
|
+
assert_equal 'RuntimeError', msg["error_class"]
|
101
|
+
assert_equal 0, msg["retry_count"]
|
102
|
+
refute msg["error_backtrace"]
|
103
|
+
assert msg["failed_at"]
|
104
|
+
@redis.verify
|
105
|
+
end
|
106
|
+
|
89
107
|
it 'handles a recurring failed message' do
|
90
108
|
@redis.expect :zadd, 1, ['retry', String, String]
|
91
109
|
now = Time.now.utc
|
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: 2.6.
|
4
|
+
version: 2.6.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -219,6 +219,7 @@ files:
|
|
219
219
|
- examples/monitrc.conf
|
220
220
|
- examples/por.rb
|
221
221
|
- examples/scheduling.rb
|
222
|
+
- examples/sidekiq
|
222
223
|
- examples/sinkiq.rb
|
223
224
|
- examples/web-ui.png
|
224
225
|
- lib/sidekiq.rb
|