sidekiq 2.3.0 → 2.3.1
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/Changes.md +8 -0
- data/lib/sidekiq.rb +1 -0
- data/lib/sidekiq/capistrano.rb +18 -6
- data/lib/sidekiq/client.rb +65 -21
- data/lib/sidekiq/processor.rb +14 -11
- data/lib/sidekiq/version.rb +1 -1
- data/test/test_client.rb +28 -0
- data/web/views/layout.slim +2 -2
- metadata +2 -2
data/Changes.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
2.3.1
|
2
|
+
-----------
|
3
|
+
|
4
|
+
- Add Sidekiq::Client.push\_bulk for bulk adding of jobs to Redis.
|
5
|
+
My own simple test case shows pushing 10,000 jobs goes from 5 sec to 1.5 sec.
|
6
|
+
- Add support for multiple processes per host to Capistrano recipe
|
7
|
+
- Re-enable Celluloid::Actor#defer to fix stack overflow issues [#398]
|
8
|
+
|
1
9
|
2.3.0
|
2
10
|
-----------
|
3
11
|
|
data/lib/sidekiq.rb
CHANGED
data/lib/sidekiq/capistrano.rb
CHANGED
@@ -4,26 +4,38 @@ Capistrano::Configuration.instance.load do
|
|
4
4
|
after "deploy:start", "sidekiq:start"
|
5
5
|
after "deploy:restart", "sidekiq:restart"
|
6
6
|
|
7
|
-
_cset(:sidekiq_timeout)
|
8
|
-
_cset(:sidekiq_role)
|
9
|
-
_cset(:sidekiq_pid)
|
7
|
+
_cset(:sidekiq_timeout) { 10 }
|
8
|
+
_cset(:sidekiq_role) { :app }
|
9
|
+
_cset(:sidekiq_pid) { "#{current_path}/tmp/pids/sidekiq.pid" }
|
10
|
+
_cset(:sidekiq_processes) { 1 }
|
10
11
|
|
11
12
|
namespace :sidekiq do
|
13
|
+
def for_each_process(&block)
|
14
|
+
0.upto(fetch(:sidekiq_processes) - 1) do |process|
|
15
|
+
yield process == 0 ? "#{fetch(:sidekiq_pid)}" : "#{fetch(:sidekiq_pid)}-#{process}"
|
16
|
+
end
|
17
|
+
end
|
12
18
|
|
13
19
|
desc "Quiet sidekiq (stop accepting new work)"
|
14
20
|
task :quiet, :roles => lambda { fetch(:sidekiq_role) }, :on_no_matching_servers => :continue do
|
15
|
-
|
21
|
+
for_each_process do |pid_file|
|
22
|
+
run "if [ -d #{current_path} ] && [ -f #{pid_file} ]; then cd #{current_path} && #{fetch(:bundle_cmd, "bundle")} exec sidekiqctl quiet #{pid_file} ; fi"
|
23
|
+
end
|
16
24
|
end
|
17
25
|
|
18
26
|
desc "Stop sidekiq"
|
19
27
|
task :stop, :roles => lambda { fetch(:sidekiq_role) }, :on_no_matching_servers => :continue do
|
20
|
-
|
28
|
+
for_each_process do |pid_file|
|
29
|
+
run "if [ -d #{current_path} ] && [ -f #{pid_file} ]; then cd #{current_path} && #{fetch(:bundle_cmd, "bundle")} exec sidekiqctl stop #{pid_file} #{fetch :sidekiq_timeout} ; fi"
|
30
|
+
end
|
21
31
|
end
|
22
32
|
|
23
33
|
desc "Start sidekiq"
|
24
34
|
task :start, :roles => lambda { fetch(:sidekiq_role) }, :on_no_matching_servers => :continue do
|
25
35
|
rails_env = fetch(:rails_env, "production")
|
26
|
-
|
36
|
+
for_each_process do |pid_file|
|
37
|
+
run "cd #{current_path} ; nohup #{fetch(:bundle_cmd, "bundle")} exec sidekiq -e #{rails_env} -C #{current_path}/config/sidekiq.yml -P #{pid_file} >> #{current_path}/log/sidekiq.log 2>&1 &", :pty => false
|
38
|
+
end
|
27
39
|
end
|
28
40
|
|
29
41
|
desc "Restart sidekiq"
|
data/lib/sidekiq/client.rb
CHANGED
@@ -36,33 +36,52 @@ module Sidekiq
|
|
36
36
|
# Sidekiq::Client.push('queue' => 'my_queue', 'class' => MyWorker, 'args' => ['foo', 1, :bat => 'bar'])
|
37
37
|
#
|
38
38
|
def self.push(item)
|
39
|
-
|
40
|
-
|
41
|
-
raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item['class'].ancestors.inspect}") if !item['class'].is_a?(Class) || !item['class'].respond_to?('get_sidekiq_options')
|
39
|
+
normed = normalize_item(item)
|
40
|
+
normed, payload = process_single(item['class'], normed)
|
42
41
|
|
43
|
-
|
44
|
-
|
42
|
+
pushed = false
|
43
|
+
Sidekiq.redis do |conn|
|
44
|
+
if normed['at']
|
45
|
+
pushed = conn.zadd('schedule', normed['at'].to_s, payload)
|
46
|
+
else
|
47
|
+
_, pushed = conn.multi do
|
48
|
+
conn.sadd('queues', normed['queue'])
|
49
|
+
conn.rpush("queue:#{normed['queue']}", payload)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end if normed
|
53
|
+
pushed ? normed['jid'] : nil
|
54
|
+
end
|
45
55
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
56
|
+
##
|
57
|
+
# Push a large number of jobs to Redis. In practice this method is only
|
58
|
+
# useful if you are pushing tens of thousands of jobs or more. This method
|
59
|
+
# basically cuts down on the redis round trip latency.
|
60
|
+
#
|
61
|
+
# Takes the same arguments as Client.push except that args is expected to be
|
62
|
+
# an Array of Arrays. All other keys are duplicated for each job. Each job
|
63
|
+
# is run through the client middleware pipeline and each job gets its own Job ID
|
64
|
+
# as normal.
|
65
|
+
#
|
66
|
+
# Returns the number of jobs pushed or nil if the pushed failed. The number of jobs
|
67
|
+
# pushed can be less than the number given if the middleware stopped processing for one
|
68
|
+
# or more jobs.
|
69
|
+
def self.push_bulk(items)
|
70
|
+
normed = normalize_item(items)
|
71
|
+
payloads = items['args'].map do |args|
|
72
|
+
_, payload = process_single(items['class'], normed.merge('args' => args, 'jid' => SecureRandom.hex(12)))
|
73
|
+
payload
|
74
|
+
end.compact
|
50
75
|
|
51
76
|
pushed = false
|
52
|
-
Sidekiq.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
pushed = conn.zadd('schedule', item['at'].to_s, payload)
|
57
|
-
else
|
58
|
-
_, pushed = conn.multi do
|
59
|
-
conn.sadd('queues', queue)
|
60
|
-
conn.rpush("queue:#{queue}", payload)
|
61
|
-
end
|
62
|
-
end
|
77
|
+
Sidekiq.redis do |conn|
|
78
|
+
_, pushed = conn.multi do
|
79
|
+
conn.sadd('queues', normed['queue'])
|
80
|
+
conn.rpush("queue:#{normed['queue']}", payloads)
|
63
81
|
end
|
64
82
|
end
|
65
|
-
|
83
|
+
|
84
|
+
pushed ? payloads.size : nil
|
66
85
|
end
|
67
86
|
|
68
87
|
# Resque compatibility helpers.
|
@@ -82,5 +101,30 @@ module Sidekiq
|
|
82
101
|
def self.enqueue_to(queue, klass, *args)
|
83
102
|
klass.client_push('queue' => queue, 'class' => klass, 'args' => args)
|
84
103
|
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def self.process_single(worker_class, item)
|
108
|
+
queue = item['queue']
|
109
|
+
|
110
|
+
Sidekiq.client_middleware.invoke(worker_class, item, queue) do
|
111
|
+
payload = Sidekiq.dump_json(item)
|
112
|
+
return item, payload
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.normalize_item(item)
|
117
|
+
raise(ArgumentError, "Message must be a Hash of the form: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash)
|
118
|
+
raise(ArgumentError, "Message must include a class and set of arguments: #{item.inspect}") if !item['class'] || !item['args']
|
119
|
+
raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item['class'].ancestors.inspect}") if !item['class'].is_a?(Class) || !item['class'].respond_to?('get_sidekiq_options')
|
120
|
+
|
121
|
+
normalized_item = item.dup
|
122
|
+
normalized_item['class'] = normalized_item['class'].to_s
|
123
|
+
normalized_item['retry'] = !!normalized_item['retry']
|
124
|
+
normalized_item['jid'] = SecureRandom.hex(12)
|
125
|
+
|
126
|
+
item['class'].get_sidekiq_options.merge normalized_item
|
127
|
+
end
|
128
|
+
|
85
129
|
end
|
86
130
|
end
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'socket'
|
1
2
|
require 'celluloid'
|
2
3
|
require 'sidekiq/util'
|
3
4
|
|
@@ -31,19 +32,21 @@ module Sidekiq
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def process(msgstr, queue)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
defer do
|
36
|
+
begin
|
37
|
+
msg = Sidekiq.load_json(msgstr)
|
38
|
+
klass = constantize(msg['class'])
|
39
|
+
worker = klass.new
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
41
|
+
stats(worker, msg, queue) do
|
42
|
+
Sidekiq.server_middleware.invoke(worker, msg, queue) do
|
43
|
+
worker.perform(*cloned(msg['args']))
|
44
|
+
end
|
42
45
|
end
|
46
|
+
rescue Exception => ex
|
47
|
+
handle_exception(ex, msg || { :message => msgstr })
|
48
|
+
raise
|
43
49
|
end
|
44
|
-
rescue Exception => ex
|
45
|
-
handle_exception(ex, msg || { :message => msgstr })
|
46
|
-
raise
|
47
50
|
end
|
48
51
|
@boss.processor_done!(current_actor)
|
49
52
|
end
|
@@ -105,7 +108,7 @@ module Sidekiq
|
|
105
108
|
end
|
106
109
|
|
107
110
|
def hostname
|
108
|
-
@h ||=
|
111
|
+
@h ||= Socket.gethostname
|
109
112
|
end
|
110
113
|
end
|
111
114
|
end
|
data/lib/sidekiq/version.rb
CHANGED
data/test/test_client.rb
CHANGED
@@ -99,6 +99,14 @@ class TestClient < MiniTest::Unit::TestCase
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
describe 'bulk' do
|
103
|
+
it 'can push a large set of jobs at once' do
|
104
|
+
a = Time.now
|
105
|
+
count = Sidekiq::Client.push_bulk('class' => QueuedWorker, 'args' => (1..1_000).to_a.map { |x| Array(x) })
|
106
|
+
assert_equal 1_000, count
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
102
110
|
class BaseWorker
|
103
111
|
include Sidekiq::Worker
|
104
112
|
sidekiq_options 'retry' => 'base'
|
@@ -109,6 +117,26 @@ class TestClient < MiniTest::Unit::TestCase
|
|
109
117
|
sidekiq_options 'retry' => 'b'
|
110
118
|
end
|
111
119
|
|
120
|
+
describe 'client middleware' do
|
121
|
+
|
122
|
+
class Stopper
|
123
|
+
def call(worker_class, message, queue)
|
124
|
+
yield if message['args'].first.odd?
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'can stop some of the jobs from pushing' do
|
129
|
+
Sidekiq.client_middleware.add Stopper
|
130
|
+
begin
|
131
|
+
assert_equal nil, Sidekiq::Client.push('class' => MyWorker, 'args' => [0])
|
132
|
+
assert_match /[0-9a-f]{12}/, Sidekiq::Client.push('class' => MyWorker, 'args' => [1])
|
133
|
+
assert_equal 1, Sidekiq::Client.push_bulk('class' => MyWorker, 'args' => [[0], [1]])
|
134
|
+
ensure
|
135
|
+
Sidekiq.client_middleware.remove Stopper
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
112
140
|
describe 'inheritance' do
|
113
141
|
it 'should inherit sidekiq options' do
|
114
142
|
assert_equal 'base', AWorker.get_sidekiq_options['retry']
|
data/web/views/layout.slim
CHANGED
@@ -2,7 +2,7 @@ doctype html
|
|
2
2
|
html
|
3
3
|
head
|
4
4
|
link href='#{{root_path}}assets/application.css' media='screen' rel='stylesheet' type='text/css'
|
5
|
-
title Sidekiq
|
5
|
+
title= Sidekiq::NAME
|
6
6
|
body
|
7
7
|
.navbar.navbar-fixed-top.navbar-inverse
|
8
8
|
.navbar-inner
|
@@ -12,7 +12,7 @@ html
|
|
12
12
|
span.icon-bar
|
13
13
|
span.icon-bar
|
14
14
|
a.brand href='#{{root_path}}'
|
15
|
-
|
15
|
+
= Sidekiq::NAME
|
16
16
|
div.nav-collapse
|
17
17
|
ul.nav
|
18
18
|
li
|
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.3.
|
4
|
+
version: 2.3.1
|
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: 2012-09-
|
12
|
+
date: 2012-09-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|