sidekiq 4.2.4 → 5.2.10
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.
- checksums.yaml +5 -5
- data/.circleci/config.yml +61 -0
- data/.github/issue_template.md +8 -1
- data/.gitignore +3 -0
- data/.travis.yml +5 -6
- data/5.0-Upgrade.md +56 -0
- data/COMM-LICENSE +12 -10
- data/Changes.md +220 -0
- data/Ent-Changes.md +94 -2
- data/Gemfile +12 -22
- data/LICENSE +1 -1
- data/Pro-4.0-Upgrade.md +35 -0
- data/Pro-Changes.md +176 -2
- data/README.md +10 -7
- data/Rakefile +3 -3
- data/bin/sidekiqctl +13 -92
- data/bin/sidekiqload +16 -34
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +1 -1
- data/lib/sidekiq/api.rb +166 -68
- data/lib/sidekiq/cli.rb +122 -77
- data/lib/sidekiq/client.rb +25 -18
- data/lib/sidekiq/core_ext.rb +1 -106
- data/lib/sidekiq/ctl.rb +221 -0
- data/lib/sidekiq/delay.rb +42 -0
- data/lib/sidekiq/exception_handler.rb +2 -4
- data/lib/sidekiq/extensions/generic_proxy.rb +7 -1
- data/lib/sidekiq/fetch.rb +1 -1
- data/lib/sidekiq/job_logger.rb +25 -0
- data/lib/sidekiq/job_retry.rb +262 -0
- data/lib/sidekiq/launcher.rb +49 -40
- data/lib/sidekiq/logging.rb +18 -2
- data/lib/sidekiq/manager.rb +6 -7
- data/lib/sidekiq/middleware/server/active_record.rb +10 -0
- data/lib/sidekiq/processor.rb +127 -37
- data/lib/sidekiq/rails.rb +16 -51
- data/lib/sidekiq/redis_connection.rb +50 -5
- data/lib/sidekiq/scheduled.rb +35 -8
- data/lib/sidekiq/testing.rb +24 -7
- data/lib/sidekiq/util.rb +6 -2
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +3 -7
- data/lib/sidekiq/web/application.rb +38 -22
- data/lib/sidekiq/web/helpers.rb +78 -27
- data/lib/sidekiq/web/router.rb +14 -10
- data/lib/sidekiq/web.rb +4 -4
- data/lib/sidekiq/worker.rb +118 -19
- data/lib/sidekiq.rb +27 -26
- data/sidekiq.gemspec +8 -13
- data/web/assets/javascripts/application.js +0 -0
- data/web/assets/javascripts/dashboard.js +33 -18
- data/web/assets/stylesheets/application-rtl.css +246 -0
- data/web/assets/stylesheets/application.css +371 -6
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +81 -0
- data/web/locales/en.yml +2 -0
- data/web/locales/es.yml +4 -3
- data/web/locales/fa.yml +80 -0
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +5 -3
- data/web/locales/ur.yml +80 -0
- data/web/views/_footer.erb +5 -2
- data/web/views/_job_info.erb +1 -1
- data/web/views/_nav.erb +4 -18
- data/web/views/_paging.erb +1 -1
- data/web/views/busy.erb +9 -5
- data/web/views/dashboard.erb +3 -3
- data/web/views/layout.erb +11 -2
- data/web/views/morgue.erb +14 -10
- data/web/views/queue.erb +11 -10
- data/web/views/queues.erb +4 -2
- data/web/views/retries.erb +17 -11
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +2 -2
- metadata +32 -151
- data/lib/sidekiq/middleware/server/logging.rb +0 -40
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
- data/test/config.yml +0 -9
- data/test/env_based_config.yml +0 -11
- data/test/fake_env.rb +0 -1
- data/test/fixtures/en.yml +0 -2
- data/test/helper.rb +0 -75
- data/test/test_actors.rb +0 -138
- data/test/test_api.rb +0 -528
- data/test/test_cli.rb +0 -418
- data/test/test_client.rb +0 -266
- data/test/test_exception_handler.rb +0 -56
- data/test/test_extensions.rb +0 -127
- data/test/test_fetch.rb +0 -50
- data/test/test_launcher.rb +0 -95
- data/test/test_logging.rb +0 -35
- data/test/test_manager.rb +0 -50
- data/test/test_middleware.rb +0 -158
- data/test/test_processor.rb +0 -235
- data/test/test_rails.rb +0 -22
- data/test/test_redis_connection.rb +0 -132
- data/test/test_retry.rb +0 -326
- data/test/test_retry_exhausted.rb +0 -149
- data/test/test_scheduled.rb +0 -115
- data/test/test_scheduling.rb +0 -58
- data/test/test_sidekiq.rb +0 -107
- data/test/test_testing.rb +0 -143
- data/test/test_testing_fake.rb +0 -357
- data/test/test_testing_inline.rb +0 -94
- data/test/test_util.rb +0 -13
- data/test/test_web.rb +0 -726
- data/test/test_web_helpers.rb +0 -54
data/lib/sidekiq/launcher.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# encoding: utf-8
|
3
2
|
require 'sidekiq/manager'
|
4
3
|
require 'sidekiq/fetch'
|
5
4
|
require 'sidekiq/scheduled'
|
@@ -14,6 +13,8 @@ module Sidekiq
|
|
14
13
|
|
15
14
|
attr_accessor :manager, :poller, :fetcher
|
16
15
|
|
16
|
+
STATS_TTL = 5*365*24*60*60
|
17
|
+
|
17
18
|
def initialize(options)
|
18
19
|
@manager = Sidekiq::Manager.new(options)
|
19
20
|
@poller = Sidekiq::Scheduled::Poller.new
|
@@ -39,7 +40,7 @@ module Sidekiq
|
|
39
40
|
# return until all work is complete and cleaned up.
|
40
41
|
# It can take up to the timeout to complete.
|
41
42
|
def stop
|
42
|
-
deadline =
|
43
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @options[:timeout]
|
43
44
|
|
44
45
|
@done = true
|
45
46
|
@manager.quiet
|
@@ -61,32 +62,36 @@ module Sidekiq
|
|
61
62
|
|
62
63
|
private unless $TESTING
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
def heartbeat(k, data, json)
|
67
|
-
results = Sidekiq::CLI::PROCTITLES.map {|x| x.(self, data) }
|
65
|
+
def heartbeat
|
66
|
+
results = Sidekiq::CLI::PROCTITLES.map {|x| x.(self, to_data) }
|
68
67
|
results.compact!
|
69
68
|
$0 = results.join(' ')
|
70
69
|
|
71
|
-
❤
|
70
|
+
❤
|
72
71
|
end
|
73
72
|
|
74
|
-
def ❤
|
73
|
+
def ❤
|
74
|
+
key = identity
|
75
75
|
fails = procd = 0
|
76
76
|
begin
|
77
|
-
Processor::FAILURE.
|
78
|
-
Processor::PROCESSED.
|
77
|
+
fails = Processor::FAILURE.reset
|
78
|
+
procd = Processor::PROCESSED.reset
|
79
|
+
curstate = Processor::WORKER_STATE.dup
|
79
80
|
|
80
|
-
workers_key = "#{key}:workers"
|
81
|
-
nowdate = Time.now.utc.strftime("%Y-%m-%d"
|
81
|
+
workers_key = "#{key}:workers"
|
82
|
+
nowdate = Time.now.utc.strftime("%Y-%m-%d")
|
82
83
|
Sidekiq.redis do |conn|
|
83
84
|
conn.multi do
|
84
|
-
conn.incrby("stat:processed"
|
85
|
+
conn.incrby("stat:processed", procd)
|
85
86
|
conn.incrby("stat:processed:#{nowdate}", procd)
|
86
|
-
conn.
|
87
|
+
conn.expire("stat:processed:#{nowdate}", STATS_TTL)
|
88
|
+
|
89
|
+
conn.incrby("stat:failed", fails)
|
87
90
|
conn.incrby("stat:failed:#{nowdate}", fails)
|
91
|
+
conn.expire("stat:failed:#{nowdate}", STATS_TTL)
|
92
|
+
|
88
93
|
conn.del(workers_key)
|
89
|
-
|
94
|
+
curstate.each_pair do |tid, hash|
|
90
95
|
conn.hset(workers_key, tid, Sidekiq.dump_json(hash))
|
91
96
|
end
|
92
97
|
conn.expire(workers_key, 60)
|
@@ -97,8 +102,8 @@ module Sidekiq
|
|
97
102
|
_, exists, _, _, msg = Sidekiq.redis do |conn|
|
98
103
|
conn.multi do
|
99
104
|
conn.sadd('processes', key)
|
100
|
-
conn.exists(key)
|
101
|
-
conn.hmset(key, 'info',
|
105
|
+
conn.exists?(key)
|
106
|
+
conn.hmset(key, 'info', to_json, 'busy', curstate.size, 'beat', Time.now.to_f, 'quiet', @done)
|
102
107
|
conn.expire(key, 60)
|
103
108
|
conn.rpop("#{key}-signals")
|
104
109
|
end
|
@@ -109,43 +114,47 @@ module Sidekiq
|
|
109
114
|
|
110
115
|
return unless msg
|
111
116
|
|
112
|
-
|
113
|
-
Sidekiq::CLI.instance.handle_signal(msg)
|
114
|
-
else
|
115
|
-
::Process.kill(msg, $$)
|
116
|
-
end
|
117
|
+
::Process.kill(msg, $$)
|
117
118
|
rescue => e
|
118
119
|
# ignore all redis/network issues
|
119
120
|
logger.error("heartbeat: #{e.message}")
|
120
121
|
# don't lose the counts if there was a network issue
|
121
|
-
Processor::PROCESSED.
|
122
|
-
Processor::FAILURE.
|
122
|
+
Processor::PROCESSED.incr(procd)
|
123
|
+
Processor::FAILURE.incr(fails)
|
123
124
|
end
|
124
125
|
end
|
125
126
|
|
126
127
|
def start_heartbeat
|
127
|
-
k = identity
|
128
|
-
data = {
|
129
|
-
'hostname' => hostname,
|
130
|
-
'started_at' => Time.now.to_f,
|
131
|
-
'pid' => $$,
|
132
|
-
'tag' => @options[:tag] || '',
|
133
|
-
'concurrency' => @options[:concurrency],
|
134
|
-
'queues' => @options[:queues].uniq,
|
135
|
-
'labels' => @options[:labels],
|
136
|
-
'identity' => k,
|
137
|
-
}
|
138
|
-
# this data doesn't change so dump it to a string
|
139
|
-
# now so we don't need to dump it every heartbeat.
|
140
|
-
json = Sidekiq.dump_json(data)
|
141
|
-
|
142
128
|
while true
|
143
|
-
heartbeat
|
129
|
+
heartbeat
|
144
130
|
sleep 5
|
145
131
|
end
|
146
132
|
Sidekiq.logger.info("Heartbeat stopping...")
|
147
133
|
end
|
148
134
|
|
135
|
+
def to_data
|
136
|
+
@data ||= begin
|
137
|
+
{
|
138
|
+
'hostname' => hostname,
|
139
|
+
'started_at' => Time.now.to_f,
|
140
|
+
'pid' => $$,
|
141
|
+
'tag' => @options[:tag] || '',
|
142
|
+
'concurrency' => @options[:concurrency],
|
143
|
+
'queues' => @options[:queues].uniq,
|
144
|
+
'labels' => @options[:labels],
|
145
|
+
'identity' => identity,
|
146
|
+
}
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def to_json
|
151
|
+
@json ||= begin
|
152
|
+
# this data changes infrequently so dump it to a string
|
153
|
+
# now so we don't need to dump it every heartbeat.
|
154
|
+
Sidekiq.dump_json(to_data)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
149
158
|
def clear_heartbeat
|
150
159
|
# Remove record from Redis since we are shutting down.
|
151
160
|
# Note we don't stop the heartbeat thread; if the process
|
data/lib/sidekiq/logging.rb
CHANGED
@@ -11,7 +11,7 @@ module Sidekiq
|
|
11
11
|
|
12
12
|
# Provide a call() method that returns the formatted message.
|
13
13
|
def call(severity, time, program_name, message)
|
14
|
-
"#{time.utc.iso8601(3)} #{::Process.pid} TID-#{
|
14
|
+
"#{time.utc.iso8601(3)} #{::Process.pid} TID-#{Sidekiq::Logging.tid}#{context} #{severity}: #{message}\n"
|
15
15
|
end
|
16
16
|
|
17
17
|
def context
|
@@ -22,10 +22,26 @@ module Sidekiq
|
|
22
22
|
|
23
23
|
class WithoutTimestamp < Pretty
|
24
24
|
def call(severity, time, program_name, message)
|
25
|
-
"#{::Process.pid} TID-#{
|
25
|
+
"#{::Process.pid} TID-#{Sidekiq::Logging.tid}#{context} #{severity}: #{message}\n"
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
+
def self.tid
|
30
|
+
Thread.current['sidekiq_tid'] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.job_hash_context(job_hash)
|
34
|
+
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
|
35
|
+
# attribute to expose the underlying thing.
|
36
|
+
klass = job_hash['wrapped'] || job_hash["class"]
|
37
|
+
bid = job_hash['bid']
|
38
|
+
"#{klass} JID-#{job_hash['jid']}#{" BID-#{bid}" if bid}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.with_job_hash_context(job_hash, &block)
|
42
|
+
with_context(job_hash_context(job_hash), &block)
|
43
|
+
end
|
44
|
+
|
29
45
|
def self.with_context(msg)
|
30
46
|
Thread.current[:sidekiq_context] ||= []
|
31
47
|
Thread.current[:sidekiq_context] << msg
|
data/lib/sidekiq/manager.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# encoding: utf-8
|
3
2
|
require 'sidekiq/util'
|
4
3
|
require 'sidekiq/processor'
|
5
4
|
require 'sidekiq/fetch'
|
@@ -10,7 +9,7 @@ module Sidekiq
|
|
10
9
|
|
11
10
|
##
|
12
11
|
# The Manager is the central coordination point in Sidekiq, controlling
|
13
|
-
# the lifecycle of the Processors
|
12
|
+
# the lifecycle of the Processors.
|
14
13
|
#
|
15
14
|
# Tasks:
|
16
15
|
#
|
@@ -31,7 +30,7 @@ module Sidekiq
|
|
31
30
|
def initialize(options={})
|
32
31
|
logger.debug { options.inspect }
|
33
32
|
@options = options
|
34
|
-
@count = options[:concurrency] ||
|
33
|
+
@count = options[:concurrency] || 10
|
35
34
|
raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1
|
36
35
|
|
37
36
|
@done = false
|
@@ -54,7 +53,7 @@ module Sidekiq
|
|
54
53
|
|
55
54
|
logger.info { "Terminating quiet workers" }
|
56
55
|
@workers.each { |x| x.terminate }
|
57
|
-
fire_event(:quiet, true)
|
56
|
+
fire_event(:quiet, reverse: true)
|
58
57
|
end
|
59
58
|
|
60
59
|
# hack for quicker development / testing environment #2774
|
@@ -62,7 +61,7 @@ module Sidekiq
|
|
62
61
|
|
63
62
|
def stop(deadline)
|
64
63
|
quiet
|
65
|
-
fire_event(:shutdown, true)
|
64
|
+
fire_event(:shutdown, reverse: true)
|
66
65
|
|
67
66
|
# some of the shutdown events can be async,
|
68
67
|
# we don't have any way to know when they're done but
|
@@ -71,11 +70,11 @@ module Sidekiq
|
|
71
70
|
return if @workers.empty?
|
72
71
|
|
73
72
|
logger.info { "Pausing to allow workers to finish..." }
|
74
|
-
remaining = deadline -
|
73
|
+
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
75
74
|
while remaining > PAUSE_TIME
|
76
75
|
return if @workers.empty?
|
77
76
|
sleep PAUSE_TIME
|
78
|
-
remaining = deadline -
|
77
|
+
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
79
78
|
end
|
80
79
|
return if @workers.empty?
|
81
80
|
|
@@ -1,7 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Sidekiq
|
2
3
|
module Middleware
|
3
4
|
module Server
|
4
5
|
class ActiveRecord
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
# With Rails 5+ we must use the Reloader **always**.
|
9
|
+
# The reloader handles code loading and db connection management.
|
10
|
+
if defined?(::Rails) && defined?(::Rails::VERSION) && ::Rails::VERSION::MAJOR >= 5
|
11
|
+
raise ArgumentError, "Rails 5 no longer needs or uses the ActiveRecord middleware."
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
5
15
|
def call(*args)
|
6
16
|
yield
|
7
17
|
ensure
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'sidekiq/util'
|
3
3
|
require 'sidekiq/fetch'
|
4
|
+
require 'sidekiq/job_logger'
|
5
|
+
require 'sidekiq/job_retry'
|
4
6
|
require 'thread'
|
5
|
-
require 'concurrent/map'
|
6
|
-
require 'concurrent/atomic/atomic_fixnum'
|
7
7
|
|
8
8
|
module Sidekiq
|
9
9
|
##
|
@@ -37,6 +37,8 @@ module Sidekiq
|
|
37
37
|
@thread = nil
|
38
38
|
@strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options)
|
39
39
|
@reloader = Sidekiq.options[:reloader]
|
40
|
+
@logging = (mgr.options[:job_logger] || Sidekiq::JobLogger).new
|
41
|
+
@retrier = Sidekiq::JobRetry.new
|
40
42
|
end
|
41
43
|
|
42
44
|
def terminate(wait=false)
|
@@ -85,7 +87,7 @@ module Sidekiq
|
|
85
87
|
def get_one
|
86
88
|
begin
|
87
89
|
work = @strategy.retrieve_work
|
88
|
-
(logger.info { "Redis is online, #{
|
90
|
+
(logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }; @down = nil) if @down
|
89
91
|
work
|
90
92
|
rescue Sidekiq::Shutdown
|
91
93
|
rescue => ex
|
@@ -105,47 +107,82 @@ module Sidekiq
|
|
105
107
|
|
106
108
|
def handle_fetch_exception(ex)
|
107
109
|
if !@down
|
108
|
-
@down =
|
110
|
+
@down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
109
111
|
logger.error("Error fetching job: #{ex}")
|
110
|
-
ex
|
111
|
-
logger.error(bt)
|
112
|
-
end
|
112
|
+
handle_exception(ex)
|
113
113
|
end
|
114
114
|
sleep(1)
|
115
115
|
nil
|
116
116
|
end
|
117
117
|
|
118
|
+
def dispatch(job_hash, queue)
|
119
|
+
# since middleware can mutate the job hash
|
120
|
+
# we clone here so we report the original
|
121
|
+
# job structure to the Web UI
|
122
|
+
pristine = cloned(job_hash)
|
123
|
+
|
124
|
+
Sidekiq::Logging.with_job_hash_context(job_hash) do
|
125
|
+
@retrier.global(pristine, queue) do
|
126
|
+
@logging.call(job_hash, queue) do
|
127
|
+
stats(pristine, queue) do
|
128
|
+
# Rails 5 requires a Reloader to wrap code execution. In order to
|
129
|
+
# constantize the worker and instantiate an instance, we have to call
|
130
|
+
# the Reloader. It handles code loading, db connection management, etc.
|
131
|
+
# Effectively this block denotes a "unit of work" to Rails.
|
132
|
+
@reloader.call do
|
133
|
+
klass = constantize(job_hash['class'])
|
134
|
+
worker = klass.new
|
135
|
+
worker.jid = job_hash['jid']
|
136
|
+
@retrier.local(worker, pristine, queue) do
|
137
|
+
yield worker
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
118
146
|
def process(work)
|
119
147
|
jobstr = work.job
|
120
148
|
queue = work.queue_name
|
121
149
|
|
122
|
-
|
150
|
+
# Treat malformed JSON as a special case: job goes straight to the morgue.
|
151
|
+
job_hash = nil
|
123
152
|
begin
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
end
|
153
|
+
job_hash = Sidekiq.load_json(jobstr)
|
154
|
+
rescue => ex
|
155
|
+
handle_exception(ex, { :context => "Invalid JSON for job", :jobstr => jobstr })
|
156
|
+
# we can't notify because the job isn't a valid hash payload.
|
157
|
+
DeadSet.new.kill(jobstr, notify_failure: false)
|
158
|
+
return work.acknowledge
|
159
|
+
end
|
160
|
+
|
161
|
+
ack = true
|
162
|
+
begin
|
163
|
+
dispatch(job_hash, queue) do |worker|
|
164
|
+
Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
|
165
|
+
execute_job(worker, cloned(job_hash['args']))
|
138
166
|
end
|
139
|
-
ack = true
|
140
167
|
end
|
141
168
|
rescue Sidekiq::Shutdown
|
142
169
|
# Had to force kill this job because it didn't finish
|
143
170
|
# within the timeout. Don't acknowledge the work since
|
144
171
|
# we didn't properly finish it.
|
145
172
|
ack = false
|
173
|
+
rescue Sidekiq::JobRetry::Handled => h
|
174
|
+
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
175
|
+
# signals that we created a retry successfully. We can acknowlege the job.
|
176
|
+
e = h.cause ? h.cause : h
|
177
|
+
handle_exception(e, { :context => "Job raised exception", :job => job_hash, :jobstr => jobstr })
|
178
|
+
raise e
|
146
179
|
rescue Exception => ex
|
147
|
-
|
148
|
-
|
180
|
+
# Unexpected error! This is very bad and indicates an exception that got past
|
181
|
+
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
182
|
+
# so it can be rescued when using Sidekiq Pro.
|
183
|
+
ack = false
|
184
|
+
handle_exception(ex, { :context => "Internal exception!", :job => job_hash, :jobstr => jobstr })
|
185
|
+
raise e
|
149
186
|
ensure
|
150
187
|
work.acknowledge if ack
|
151
188
|
end
|
@@ -155,34 +192,87 @@ module Sidekiq
|
|
155
192
|
worker.perform(*cloned_args)
|
156
193
|
end
|
157
194
|
|
158
|
-
|
159
|
-
|
195
|
+
# Ruby doesn't provide atomic counters out of the box so we'll
|
196
|
+
# implement something simple ourselves.
|
197
|
+
# https://bugs.ruby-lang.org/issues/14706
|
198
|
+
class Counter
|
199
|
+
def initialize
|
200
|
+
@value = 0
|
201
|
+
@lock = Mutex.new
|
202
|
+
end
|
203
|
+
|
204
|
+
def incr(amount=1)
|
205
|
+
@lock.synchronize { @value = @value + amount }
|
206
|
+
end
|
207
|
+
|
208
|
+
def reset
|
209
|
+
@lock.synchronize { val = @value; @value = 0; val }
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
|
214
|
+
class SharedWorkerState
|
215
|
+
def initialize
|
216
|
+
@worker_state = {}
|
217
|
+
@lock = Mutex.new
|
218
|
+
end
|
219
|
+
|
220
|
+
def set(tid, hash)
|
221
|
+
@lock.synchronize { @worker_state[tid] = hash }
|
222
|
+
end
|
223
|
+
|
224
|
+
def delete(tid)
|
225
|
+
@lock.synchronize { @worker_state.delete(tid) }
|
226
|
+
end
|
227
|
+
|
228
|
+
def dup
|
229
|
+
@lock.synchronize { @worker_state.dup }
|
230
|
+
end
|
231
|
+
|
232
|
+
def size
|
233
|
+
@lock.synchronize { @worker_state.size }
|
234
|
+
end
|
235
|
+
|
236
|
+
def clear
|
237
|
+
@lock.synchronize { @worker_state.clear }
|
238
|
+
end
|
160
239
|
end
|
161
240
|
|
162
|
-
|
163
|
-
|
164
|
-
|
241
|
+
PROCESSED = Counter.new
|
242
|
+
FAILURE = Counter.new
|
243
|
+
WORKER_STATE = SharedWorkerState.new
|
165
244
|
|
166
|
-
def stats(
|
167
|
-
tid =
|
168
|
-
WORKER_STATE
|
245
|
+
def stats(job_hash, queue)
|
246
|
+
tid = Sidekiq::Logging.tid
|
247
|
+
WORKER_STATE.set(tid, {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i })
|
169
248
|
|
170
249
|
begin
|
171
250
|
yield
|
172
251
|
rescue Exception
|
173
|
-
FAILURE.
|
252
|
+
FAILURE.incr
|
174
253
|
raise
|
175
254
|
ensure
|
176
255
|
WORKER_STATE.delete(tid)
|
177
|
-
PROCESSED.
|
256
|
+
PROCESSED.incr
|
178
257
|
end
|
179
258
|
end
|
180
259
|
|
181
260
|
# Deep clone the arguments passed to the worker so that if
|
182
261
|
# the job fails, what is pushed back onto Redis hasn't
|
183
262
|
# been mutated by the worker.
|
184
|
-
def cloned(
|
185
|
-
Marshal.load(Marshal.dump(
|
263
|
+
def cloned(thing)
|
264
|
+
Marshal.load(Marshal.dump(thing))
|
265
|
+
end
|
266
|
+
|
267
|
+
def constantize(str)
|
268
|
+
names = str.split('::')
|
269
|
+
names.shift if names.empty? || names.first.empty?
|
270
|
+
|
271
|
+
names.inject(Object) do |constant, name|
|
272
|
+
# the false flag limits search for name to under the constant namespace
|
273
|
+
# which mimics Rails' behaviour
|
274
|
+
constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name)
|
275
|
+
end
|
186
276
|
end
|
187
277
|
|
188
278
|
end
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -1,36 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
module Sidekiq
|
3
|
-
def self.hook_rails!
|
4
|
-
return if defined?(@delay_removed)
|
5
|
-
|
6
|
-
ActiveSupport.on_load(:active_record) do
|
7
|
-
include Sidekiq::Extensions::ActiveRecord
|
8
|
-
end
|
9
|
-
|
10
|
-
ActiveSupport.on_load(:action_mailer) do
|
11
|
-
extend Sidekiq::Extensions::ActionMailer
|
12
|
-
end
|
13
|
-
|
14
|
-
Module.__send__(:include, Sidekiq::Extensions::Klass)
|
15
|
-
end
|
16
|
-
|
17
|
-
# Removes the generic aliases which MAY clash with names of already
|
18
|
-
# created methods by other applications. The methods `sidekiq_delay`,
|
19
|
-
# `sidekiq_delay_for` and `sidekiq_delay_until` can be used instead.
|
20
|
-
def self.remove_delay!
|
21
|
-
@delay_removed = true
|
22
|
-
|
23
|
-
[Extensions::ActiveRecord,
|
24
|
-
Extensions::ActionMailer,
|
25
|
-
Extensions::Klass].each do |mod|
|
26
|
-
mod.module_eval do
|
27
|
-
remove_method :delay if respond_to?(:delay)
|
28
|
-
remove_method :delay_for if respond_to?(:delay_for)
|
29
|
-
remove_method :delay_until if respond_to?(:delay_until)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
2
|
|
3
|
+
module Sidekiq
|
34
4
|
class Rails < ::Rails::Engine
|
35
5
|
# We need to setup this up before any application configuration which might
|
36
6
|
# change Sidekiq middleware.
|
@@ -48,26 +18,16 @@ module Sidekiq
|
|
48
18
|
end
|
49
19
|
end
|
50
20
|
|
51
|
-
initializer 'sidekiq' do
|
52
|
-
Sidekiq.hook_rails!
|
53
|
-
end
|
54
|
-
|
55
|
-
# We have to add the reloader after initialize to see if cache_classes has
|
56
|
-
# been turned on.
|
57
|
-
#
|
58
|
-
# This hook happens after all initialziers are run, just before returning
|
59
|
-
# from config/environment.rb back to sidekiq/cli.rb.
|
60
21
|
config.after_initialize do
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
else
|
22
|
+
# This hook happens after all initializers are run, just before returning
|
23
|
+
# from config/environment.rb back to sidekiq/cli.rb.
|
24
|
+
# We have to add the reloader after initialize to see if cache_classes has
|
25
|
+
# been turned on.
|
26
|
+
#
|
27
|
+
# None of this matters on the client-side, only within the Sidekiq process itself.
|
28
|
+
#
|
29
|
+
Sidekiq.configure_server do |_|
|
30
|
+
if ::Rails::VERSION::MAJOR >= 5
|
71
31
|
Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
|
72
32
|
end
|
73
33
|
end
|
@@ -75,7 +35,6 @@ module Sidekiq
|
|
75
35
|
|
76
36
|
class Reloader
|
77
37
|
def initialize(app = ::Rails.application)
|
78
|
-
Sidekiq.logger.debug "Enabling Rails 5+ live code reloading, so hot!" unless app.config.cache_classes
|
79
38
|
@app = app
|
80
39
|
end
|
81
40
|
|
@@ -91,3 +50,9 @@ module Sidekiq
|
|
91
50
|
end
|
92
51
|
end if defined?(::Rails)
|
93
52
|
end
|
53
|
+
|
54
|
+
if defined?(::Rails) && ::Rails::VERSION::MAJOR < 4
|
55
|
+
$stderr.puts("**************************************************")
|
56
|
+
$stderr.puts("⛔️ WARNING: Sidekiq server is no longer supported by Rails 3.2 - please ensure your server/workers are updated")
|
57
|
+
$stderr.puts("**************************************************")
|
58
|
+
end
|
@@ -8,11 +8,22 @@ module Sidekiq
|
|
8
8
|
class << self
|
9
9
|
|
10
10
|
def create(options={})
|
11
|
-
options
|
11
|
+
options.keys.each do |key|
|
12
|
+
options[key.to_sym] = options.delete(key)
|
13
|
+
end
|
12
14
|
|
15
|
+
options[:id] = "Sidekiq-#{Sidekiq.server? ? "server" : "client"}-PID-#{$$}" if !options.has_key?(:id)
|
13
16
|
options[:url] ||= determine_redis_provider
|
14
17
|
|
15
|
-
size = options[:size]
|
18
|
+
size = if options[:size]
|
19
|
+
options[:size]
|
20
|
+
elsif Sidekiq.server?
|
21
|
+
Sidekiq.options[:concurrency] + 5
|
22
|
+
elsif ENV['RAILS_MAX_THREADS']
|
23
|
+
Integer(ENV['RAILS_MAX_THREADS'])
|
24
|
+
else
|
25
|
+
5
|
26
|
+
end
|
16
27
|
|
17
28
|
verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server?
|
18
29
|
|
@@ -35,7 +46,7 @@ module Sidekiq
|
|
35
46
|
# - enterprise's leader election
|
36
47
|
# - enterprise's cron support
|
37
48
|
def verify_sizing(size, concurrency)
|
38
|
-
raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but
|
49
|
+
raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size <= concurrency
|
39
50
|
end
|
40
51
|
|
41
52
|
def build_client(options)
|
@@ -67,7 +78,14 @@ module Sidekiq
|
|
67
78
|
opts.delete(:network_timeout)
|
68
79
|
end
|
69
80
|
|
70
|
-
opts[:driver]
|
81
|
+
opts[:driver] ||= Redis::Connection.drivers.last || 'ruby'
|
82
|
+
|
83
|
+
# Issue #3303, redis-rb will silently retry an operation.
|
84
|
+
# This can lead to duplicate jobs if Sidekiq::Client's LPUSH
|
85
|
+
# is performed twice but I believe this is much, much rarer
|
86
|
+
# than the reconnect silently fixing a problem; we keep it
|
87
|
+
# on by default.
|
88
|
+
opts[:reconnect_attempts] ||= 1
|
71
89
|
|
72
90
|
opts
|
73
91
|
end
|
@@ -91,7 +109,34 @@ module Sidekiq
|
|
91
109
|
end
|
92
110
|
|
93
111
|
def determine_redis_provider
|
94
|
-
|
112
|
+
# If you have this in your environment:
|
113
|
+
# MY_REDIS_URL=redis://hostname.example.com:1238/4
|
114
|
+
# then set:
|
115
|
+
# REDIS_PROVIDER=MY_REDIS_URL
|
116
|
+
# and Sidekiq will find your custom URL variable with no custom
|
117
|
+
# initialization code at all.
|
118
|
+
p = ENV['REDIS_PROVIDER']
|
119
|
+
if p && p =~ /\:/
|
120
|
+
Sidekiq.logger.error <<-EOM
|
121
|
+
|
122
|
+
#################################################################################
|
123
|
+
|
124
|
+
REDIS_PROVIDER should be set to the **name** of the variable which contains the Redis URL, not a URL itself.
|
125
|
+
Platforms like Heroku sell addons that publish a *_URL variable. You tell Sidekiq with REDIS_PROVIDER, e.g.:
|
126
|
+
|
127
|
+
REDIS_PROVIDER=REDISTOGO_URL
|
128
|
+
REDISTOGO_URL=redis://somehost.example.com:6379/4
|
129
|
+
|
130
|
+
Use REDIS_URL if you wish to point Sidekiq to a URL directly.
|
131
|
+
|
132
|
+
This configuration error will crash starting in Sidekiq 5.3.
|
133
|
+
|
134
|
+
#################################################################################
|
135
|
+
EOM
|
136
|
+
end
|
137
|
+
ENV[
|
138
|
+
ENV['REDIS_PROVIDER'] || 'REDIS_URL'
|
139
|
+
]
|
95
140
|
end
|
96
141
|
|
97
142
|
end
|