sidekiq 5.2.10 → 6.0.0.pre1

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.

Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +20 -0
  3. data/.travis.yml +5 -2
  4. data/6.0-Upgrade.md +58 -0
  5. data/Changes.md +21 -16
  6. data/Gemfile +15 -10
  7. data/Rakefile +5 -4
  8. data/bin/sidekiqctl +1 -10
  9. data/lib/generators/sidekiq/worker_generator.rb +12 -14
  10. data/lib/sidekiq/api.rb +133 -148
  11. data/lib/sidekiq/cli.rb +95 -147
  12. data/lib/sidekiq/client.rb +44 -45
  13. data/lib/sidekiq/ctl.rb +35 -109
  14. data/lib/sidekiq/delay.rb +5 -6
  15. data/lib/sidekiq/exception_handler.rb +10 -12
  16. data/lib/sidekiq/extensions/action_mailer.rb +10 -20
  17. data/lib/sidekiq/extensions/active_record.rb +9 -7
  18. data/lib/sidekiq/extensions/class_methods.rb +9 -7
  19. data/lib/sidekiq/extensions/generic_proxy.rb +4 -4
  20. data/lib/sidekiq/fetch.rb +5 -6
  21. data/lib/sidekiq/job_logger.rb +37 -7
  22. data/lib/sidekiq/job_retry.rb +45 -58
  23. data/lib/sidekiq/launcher.rb +59 -48
  24. data/lib/sidekiq/logger.rb +69 -0
  25. data/lib/sidekiq/manager.rb +6 -8
  26. data/lib/sidekiq/middleware/chain.rb +2 -1
  27. data/lib/sidekiq/middleware/i18n.rb +5 -7
  28. data/lib/sidekiq/paginator.rb +11 -12
  29. data/lib/sidekiq/processor.rb +42 -45
  30. data/lib/sidekiq/rails.rb +2 -26
  31. data/lib/sidekiq/redis_connection.rb +31 -37
  32. data/lib/sidekiq/scheduled.rb +17 -19
  33. data/lib/sidekiq/testing/inline.rb +2 -1
  34. data/lib/sidekiq/testing.rb +22 -23
  35. data/lib/sidekiq/util.rb +18 -15
  36. data/lib/sidekiq/version.rb +2 -1
  37. data/lib/sidekiq/web/action.rb +15 -11
  38. data/lib/sidekiq/web/application.rb +59 -59
  39. data/lib/sidekiq/web/helpers.rb +66 -67
  40. data/lib/sidekiq/web/router.rb +17 -14
  41. data/lib/sidekiq/web.rb +36 -44
  42. data/lib/sidekiq/worker.rb +12 -13
  43. data/lib/sidekiq.rb +53 -42
  44. data/sidekiq.gemspec +7 -7
  45. metadata +20 -32
  46. data/lib/sidekiq/core_ext.rb +0 -1
  47. data/lib/sidekiq/logging.rb +0 -122
  48. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/scheduled'
3
- require 'sidekiq/api'
2
+
3
+ require "sidekiq/scheduled"
4
+ require "sidekiq/api"
4
5
 
5
6
  module Sidekiq
6
7
  ##
@@ -81,22 +82,19 @@ module Sidekiq
81
82
  # ignore, will be pushed back onto queue during hard_shutdown
82
83
  raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e)
83
84
 
84
- if msg['retry']
85
+ if msg["retry"]
85
86
  attempt_retry(nil, msg, queue, e)
86
87
  else
87
88
  Sidekiq.death_handlers.each do |handler|
88
- begin
89
- handler.call(msg, e)
90
- rescue => handler_ex
91
- handle_exception(handler_ex, { context: "Error calling death handler", job: msg })
92
- end
89
+ handler.call(msg, e)
90
+ rescue => handler_ex
91
+ handle_exception(handler_ex, {context: "Error calling death handler", job: msg})
93
92
  end
94
93
  end
95
94
 
96
95
  raise Handled
97
96
  end
98
97
 
99
-
100
98
  # The local retry support means that any errors that occur within
101
99
  # this block can be associated with the given worker instance.
102
100
  # This is required to support the `sidekiq_retries_exhausted` block.
@@ -116,11 +114,11 @@ module Sidekiq
116
114
  # ignore, will be pushed back onto queue during hard_shutdown
117
115
  raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e)
118
116
 
119
- if msg['retry'] == nil
120
- msg['retry'] = worker.class.get_sidekiq_options['retry']
117
+ if msg["retry"].nil?
118
+ msg["retry"] = worker.class.get_sidekiq_options["retry"]
121
119
  end
122
120
 
123
- raise e unless msg['retry']
121
+ raise e unless msg["retry"]
124
122
  attempt_retry(worker, msg, queue, e)
125
123
  # We've handled this error associated with this job, don't
126
124
  # need to handle it at the global level
@@ -133,13 +131,9 @@ module Sidekiq
133
131
  # instantiate the worker instance. All access must be guarded and
134
132
  # best effort.
135
133
  def attempt_retry(worker, msg, queue, exception)
136
- max_retry_attempts = retry_attempts_from(msg['retry'], @max_retries)
134
+ max_retry_attempts = retry_attempts_from(msg["retry"], @max_retries)
137
135
 
138
- msg['queue'] = if msg['retry_queue']
139
- msg['retry_queue']
140
- else
141
- queue
142
- end
136
+ msg["queue"] = (msg["retry_queue"] || queue)
143
137
 
144
138
  m = exception_message(exception)
145
139
  if m.respond_to?(:scrub!)
@@ -147,32 +141,32 @@ module Sidekiq
147
141
  m.scrub!
148
142
  end
149
143
 
150
- msg['error_message'] = m
151
- msg['error_class'] = exception.class.name
152
- count = if msg['retry_count']
153
- msg['retried_at'] = Time.now.to_f
154
- msg['retry_count'] += 1
144
+ msg["error_message"] = m
145
+ msg["error_class"] = exception.class.name
146
+ count = if msg["retry_count"]
147
+ msg["retried_at"] = Time.now.to_f
148
+ msg["retry_count"] += 1
155
149
  else
156
- msg['failed_at'] = Time.now.to_f
157
- msg['retry_count'] = 0
150
+ msg["failed_at"] = Time.now.to_f
151
+ msg["retry_count"] = 0
158
152
  end
159
153
 
160
- if msg['backtrace'] == true
161
- msg['error_backtrace'] = exception.backtrace
162
- elsif !msg['backtrace']
154
+ if msg["backtrace"] == true
155
+ msg["error_backtrace"] = exception.backtrace
156
+ elsif !msg["backtrace"]
163
157
  # do nothing
164
- elsif msg['backtrace'].to_i != 0
165
- msg['error_backtrace'] = exception.backtrace[0...msg['backtrace'].to_i]
158
+ elsif msg["backtrace"].to_i != 0
159
+ msg["error_backtrace"] = exception.backtrace[0...msg["backtrace"].to_i]
166
160
  end
167
161
 
168
162
  if count < max_retry_attempts
169
163
  delay = delay_for(worker, count, exception)
170
164
  # Logging here can break retries if the logging device raises ENOSPC #3979
171
- #logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
165
+ # logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
172
166
  retry_at = Time.now.to_f + delay
173
167
  payload = Sidekiq.dump_json(msg)
174
168
  Sidekiq.redis do |conn|
175
- conn.zadd('retry', retry_at.to_s, payload)
169
+ conn.zadd("retry", retry_at.to_s, payload)
176
170
  end
177
171
  else
178
172
  # Goodbye dear message, you (re)tried your best I'm sure.
@@ -182,25 +176,23 @@ module Sidekiq
182
176
 
183
177
  def retries_exhausted(worker, msg, exception)
184
178
  begin
185
- block = worker && worker.sidekiq_retries_exhausted_block
186
- block.call(msg, exception) if block
179
+ block = worker&.sidekiq_retries_exhausted_block
180
+ block&.call(msg, exception)
187
181
  rescue => e
188
- handle_exception(e, { context: "Error calling retries_exhausted", job: msg })
182
+ handle_exception(e, {context: "Error calling retries_exhausted", job: msg})
189
183
  end
190
184
 
191
185
  Sidekiq.death_handlers.each do |handler|
192
- begin
193
- handler.call(msg, exception)
194
- rescue => e
195
- handle_exception(e, { context: "Error calling death handler", job: msg })
196
- end
186
+ handler.call(msg, exception)
187
+ rescue => e
188
+ handle_exception(e, {context: "Error calling death handler", job: msg})
197
189
  end
198
190
 
199
- send_to_morgue(msg) unless msg['dead'] == false
191
+ send_to_morgue(msg) unless msg["dead"] == false
200
192
  end
201
193
 
202
194
  def send_to_morgue(msg)
203
- logger.info { "Adding dead #{msg['class']} job #{msg['jid']}" }
195
+ logger.info { "Adding dead #{msg["class"]} job #{msg["jid"]}" }
204
196
  payload = Sidekiq.dump_json(msg)
205
197
  DeadSet.new.kill(payload, notify_failure: false)
206
198
  end
@@ -214,7 +206,7 @@ module Sidekiq
214
206
  end
215
207
 
216
208
  def delay_for(worker, count, exception)
217
- if worker && worker.sidekiq_retry_in_block
209
+ if worker&.sidekiq_retry_in_block
218
210
  custom_retry_in = retry_in(worker, count, exception).to_i
219
211
  return custom_retry_in if custom_retry_in > 0
220
212
  end
@@ -223,16 +215,14 @@ module Sidekiq
223
215
 
224
216
  # delayed_job uses the same basic formula
225
217
  def seconds_to_delay(count)
226
- (count ** 4) + 15 + (rand(30)*(count+1))
218
+ (count**4) + 15 + (rand(30) * (count + 1))
227
219
  end
228
220
 
229
221
  def retry_in(worker, count, exception)
230
- begin
231
- worker.sidekiq_retry_in_block.call(count, exception)
232
- rescue Exception => e
233
- handle_exception(e, { context: "Failure scheduling retry using the defined `sidekiq_retry_in` in #{worker.class.name}, falling back to default" })
234
- nil
235
- end
222
+ worker.sidekiq_retry_in_block.call(count, exception)
223
+ rescue Exception => e
224
+ handle_exception(e, {context: "Failure scheduling retry using the defined `sidekiq_retry_in` in #{worker.class.name}, falling back to default"})
225
+ nil
236
226
  end
237
227
 
238
228
  def exception_caused_by_shutdown?(e, checked_causes = [])
@@ -249,14 +239,11 @@ module Sidekiq
249
239
  # Extract message from exception.
250
240
  # Set a default if the message raises an error
251
241
  def exception_message(exception)
252
- begin
253
- # App code can stuff all sorts of crazy binary data into the error message
254
- # that won't convert to JSON.
255
- exception.message.to_s[0, 10_000]
256
- rescue
257
- "!!! ERROR MESSAGE THREW AN ERROR !!!".dup
258
- end
242
+ # App code can stuff all sorts of crazy binary data into the error message
243
+ # that won't convert to JSON.
244
+ exception.message.to_s[0, 10_000]
245
+ rescue
246
+ +"!!! ERROR MESSAGE THREW AN ERROR !!!"
259
247
  end
260
-
261
248
  end
262
249
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/manager'
3
- require 'sidekiq/fetch'
4
- require 'sidekiq/scheduled'
2
+
3
+ require "sidekiq/manager"
4
+ require "sidekiq/fetch"
5
+ require "sidekiq/scheduled"
5
6
 
6
7
  module Sidekiq
7
8
  # The Launcher is a very simple Actor whose job is to
@@ -11,9 +12,17 @@ module Sidekiq
11
12
  class Launcher
12
13
  include Util
13
14
 
14
- attr_accessor :manager, :poller, :fetcher
15
+ STATS_TTL = 5 * 365 * 24 * 60 * 60 # 5 years
15
16
 
16
- STATS_TTL = 5*365*24*60*60
17
+ PROCTITLES = [
18
+ proc { "sidekiq" },
19
+ proc { Sidekiq::VERSION },
20
+ proc { |me, data| data["tag"] },
21
+ proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data["concurrency"]} busy]" },
22
+ proc { |me, data| "stopping" if me.stopping? },
23
+ ]
24
+
25
+ attr_accessor :manager, :poller, :fetcher
17
26
 
18
27
  def initialize(options)
19
28
  @manager = Sidekiq::Manager.new(options)
@@ -62,10 +71,30 @@ module Sidekiq
62
71
 
63
72
  private unless $TESTING
64
73
 
74
+ def start_heartbeat
75
+ loop do
76
+ heartbeat
77
+ sleep 5
78
+ end
79
+ Sidekiq.logger.info("Heartbeat stopping...")
80
+ end
81
+
82
+ def clear_heartbeat
83
+ # Remove record from Redis since we are shutting down.
84
+ # Note we don't stop the heartbeat thread; if the process
85
+ # doesn't actually exit, it'll reappear in the Web UI.
86
+ Sidekiq.redis do |conn|
87
+ conn.pipelined do
88
+ conn.srem("processes", identity)
89
+ conn.del("#{identity}:workers")
90
+ end
91
+ end
92
+ rescue
93
+ # best effort, ignore network errors
94
+ end
95
+
65
96
  def heartbeat
66
- results = Sidekiq::CLI::PROCTITLES.map {|x| x.(self, to_data) }
67
- results.compact!
68
- $0 = results.join(' ')
97
+ $0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ")
69
98
 
70
99
 
71
100
  end
@@ -73,6 +102,7 @@ module Sidekiq
73
102
  def ❤
74
103
  key = identity
75
104
  fails = procd = 0
105
+
76
106
  begin
77
107
  fails = Processor::FAILURE.reset
78
108
  procd = Processor::PROCESSED.reset
@@ -80,6 +110,7 @@ module Sidekiq
80
110
 
81
111
  workers_key = "#{key}:workers"
82
112
  nowdate = Time.now.utc.strftime("%Y-%m-%d")
113
+
83
114
  Sidekiq.redis do |conn|
84
115
  conn.multi do
85
116
  conn.incrby("stat:processed", procd)
@@ -97,24 +128,27 @@ module Sidekiq
97
128
  conn.expire(workers_key, 60)
98
129
  end
99
130
  end
131
+
100
132
  fails = procd = 0
101
133
 
102
- _, exists, _, _, msg = Sidekiq.redis do |conn|
103
- conn.multi do
104
- conn.sadd('processes', key)
105
- conn.exists?(key)
106
- conn.hmset(key, 'info', to_json, 'busy', curstate.size, 'beat', Time.now.to_f, 'quiet', @done)
134
+ _, exists, _, _, msg = Sidekiq.redis { |conn|
135
+ res = conn.multi {
136
+ conn.sadd("processes", key)
137
+ conn.exists(key)
138
+ conn.hmset(key, "info", to_json, "busy", curstate.size, "beat", Time.now.to_f, "quiet", @done)
107
139
  conn.expire(key, 60)
108
140
  conn.rpop("#{key}-signals")
109
- end
110
- end
141
+ }
142
+
143
+ res
144
+ }
111
145
 
112
146
  # first heartbeat or recovering from an outage and need to reestablish our heartbeat
113
- fire_event(:heartbeat) if !exists
147
+ fire_event(:heartbeat) unless exists
114
148
 
115
149
  return unless msg
116
150
 
117
- ::Process.kill(msg, $$)
151
+ ::Process.kill(msg, $PID)
118
152
  rescue => e
119
153
  # ignore all redis/network issues
120
154
  logger.error("heartbeat: #{e.message}")
@@ -124,25 +158,17 @@ module Sidekiq
124
158
  end
125
159
  end
126
160
 
127
- def start_heartbeat
128
- while true
129
- heartbeat
130
- sleep 5
131
- end
132
- Sidekiq.logger.info("Heartbeat stopping...")
133
- end
134
-
135
161
  def to_data
136
162
  @data ||= begin
137
163
  {
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,
164
+ "hostname" => hostname,
165
+ "started_at" => Time.now.to_f,
166
+ "pid" => $PID,
167
+ "tag" => @options[:tag] || "",
168
+ "concurrency" => @options[:concurrency],
169
+ "queues" => @options[:queues].uniq,
170
+ "labels" => @options[:labels],
171
+ "identity" => identity,
146
172
  }
147
173
  end
148
174
  end
@@ -154,20 +180,5 @@ module Sidekiq
154
180
  Sidekiq.dump_json(to_data)
155
181
  end
156
182
  end
157
-
158
- def clear_heartbeat
159
- # Remove record from Redis since we are shutting down.
160
- # Note we don't stop the heartbeat thread; if the process
161
- # doesn't actually exit, it'll reappear in the Web UI.
162
- Sidekiq.redis do |conn|
163
- conn.pipelined do
164
- conn.srem('processes', identity)
165
- conn.del("#{identity}:workers")
166
- end
167
- end
168
- rescue
169
- # best effort, ignore network errors
170
- end
171
-
172
183
  end
173
184
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "time"
5
+
6
+ module Sidekiq
7
+ class Logger < ::Logger
8
+ def initialize(*args)
9
+ super
10
+
11
+ self.formatter = Sidekiq.log_formatter
12
+ end
13
+
14
+ def with_context(hash)
15
+ ctx.merge!(hash)
16
+ yield
17
+ ensure
18
+ hash.keys.each { |key| ctx.delete(key) }
19
+ end
20
+
21
+ def ctx
22
+ Thread.current[:sidekiq_context] ||= {}
23
+ end
24
+
25
+ module Formatters
26
+ class Base < ::Logger::Formatter
27
+ def tid
28
+ Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
29
+ end
30
+
31
+ def ctx
32
+ Thread.current[:sidekiq_context] ||= {}
33
+ end
34
+
35
+ def format_context
36
+ " " + ctx.compact.map { |k, v| "#{k}=#{v}" }.join(" ") if ctx.any?
37
+ end
38
+ end
39
+
40
+ class Pretty < Base
41
+ def call(severity, time, program_name, message)
42
+ "#{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
43
+ end
44
+ end
45
+
46
+ class WithoutTimestamp < Pretty
47
+ def call(severity, time, program_name, message)
48
+ "pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
49
+ end
50
+ end
51
+
52
+ class JSON < Base
53
+ def call(severity, time, program_name, message)
54
+ hash = {
55
+ ts: time.utc.iso8601(3),
56
+ pid: ::Process.pid,
57
+ tid: tid,
58
+ lvl: severity,
59
+ msg: message,
60
+ }
61
+ c = ctx
62
+ hash["ctx"] = c unless c.empty?
63
+
64
+ Sidekiq.dump_json(hash) << "\n"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/util'
3
- require 'sidekiq/processor'
4
- require 'sidekiq/fetch'
5
- require 'thread'
6
- require 'set'
7
2
 
8
- module Sidekiq
3
+ require "sidekiq/util"
4
+ require "sidekiq/processor"
5
+ require "sidekiq/fetch"
6
+ require "set"
9
7
 
8
+ module Sidekiq
10
9
  ##
11
10
  # The Manager is the central coordination point in Sidekiq, controlling
12
11
  # the lifecycle of the Processors.
@@ -27,7 +26,7 @@ module Sidekiq
27
26
  attr_reader :workers
28
27
  attr_reader :options
29
28
 
30
- def initialize(options={})
29
+ def initialize(options = {})
31
30
  logger.debug { options.inspect }
32
31
  @options = options
33
32
  @count = options[:concurrency] || 10
@@ -132,6 +131,5 @@ module Sidekiq
132
131
  processor.kill
133
132
  end
134
133
  end
135
-
136
134
  end
137
135
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Sidekiq
3
4
  # Middleware is code configured to run before/after
4
5
  # a message is processed. It is patterned after Rack
@@ -106,7 +107,7 @@ module Sidekiq
106
107
  i = entries.index { |entry| entry.klass == newklass }
107
108
  new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
108
109
  i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1
109
- entries.insert(i+1, new_entry)
110
+ entries.insert(i + 1, new_entry)
110
111
  end
111
112
 
112
113
  def exists?(klass)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Simple middleware to save the current locale and restore it when the job executes.
4
5
  # Use it by requiring it in your initializer:
@@ -9,19 +10,16 @@ module Sidekiq::Middleware::I18n
9
10
  # Get the current locale and store it in the message
10
11
  # to be sent to Sidekiq.
11
12
  class Client
12
- def call(worker_class, msg, queue, redis_pool)
13
- msg['locale'] ||= I18n.locale
13
+ def call(_worker, msg, _queue, _redis)
14
+ msg["locale"] ||= I18n.locale
14
15
  yield
15
16
  end
16
17
  end
17
18
 
18
19
  # Pull the msg locale out and set the current thread to use it.
19
20
  class Server
20
- def call(worker, msg, queue)
21
- I18n.locale = msg['locale'] || I18n.default_locale
22
- yield
23
- ensure
24
- I18n.locale = I18n.default_locale
21
+ def call(_worker, msg, _queue, &block)
22
+ I18n.with_locale(msg.fetch("locale", I18n.default_locale), &block)
25
23
  end
26
24
  end
27
25
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Sidekiq
3
4
  module Paginator
4
-
5
- def page(key, pageidx=1, page_size=25, opts=nil)
5
+ def page(key, pageidx = 1, page_size = 25, opts = nil)
6
6
  current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
7
7
  pageidx = current_page - 1
8
8
  total_size = 0
@@ -14,30 +14,29 @@ module Sidekiq
14
14
  type = conn.type(key)
15
15
 
16
16
  case type
17
- when 'zset'
17
+ when "zset"
18
18
  rev = opts && opts[:reverse]
19
- total_size, items = conn.multi do
19
+ total_size, items = conn.multi {
20
20
  conn.zcard(key)
21
21
  if rev
22
- conn.zrevrange(key, starting, ending, :with_scores => true)
22
+ conn.zrevrange(key, starting, ending, with_scores: true)
23
23
  else
24
- conn.zrange(key, starting, ending, :with_scores => true)
24
+ conn.zrange(key, starting, ending, with_scores: true)
25
25
  end
26
- end
26
+ }
27
27
  [current_page, total_size, items]
28
- when 'list'
29
- total_size, items = conn.multi do
28
+ when "list"
29
+ total_size, items = conn.multi {
30
30
  conn.llen(key)
31
31
  conn.lrange(key, starting, ending)
32
- end
32
+ }
33
33
  [current_page, total_size, items]
34
- when 'none'
34
+ when "none"
35
35
  [1, 0, []]
36
36
  else
37
37
  raise "can't page a #{type}"
38
38
  end
39
39
  end
40
40
  end
41
-
42
41
  end
43
42
  end