sidekiq 6.4.2 → 6.5.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.

@@ -66,12 +66,13 @@ module Sidekiq
66
66
 
67
67
  class Skip < Handled; end
68
68
 
69
- include Sidekiq::Util
69
+ include Sidekiq::Component
70
70
 
71
71
  DEFAULT_MAX_RETRY_ATTEMPTS = 25
72
72
 
73
- def initialize(options = {})
74
- @max_retries = Sidekiq.options.merge(options).fetch(:max_retries, DEFAULT_MAX_RETRY_ATTEMPTS)
73
+ def initialize(options)
74
+ @config = options
75
+ @max_retries = @config[:max_retries] || DEFAULT_MAX_RETRY_ATTEMPTS
75
76
  end
76
77
 
77
78
  # The global retry handler requires only the barest of data.
@@ -175,7 +176,7 @@ module Sidekiq
175
176
  # logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
176
177
  retry_at = Time.now.to_f + delay
177
178
  payload = Sidekiq.dump_json(msg)
178
- Sidekiq.redis do |conn|
179
+ redis do |conn|
179
180
  conn.zadd("retry", retry_at.to_s, payload)
180
181
  end
181
182
  else
@@ -194,7 +195,7 @@ module Sidekiq
194
195
 
195
196
  send_to_morgue(msg) unless msg["dead"] == false
196
197
 
197
- Sidekiq.death_handlers.each do |handler|
198
+ config.death_handlers.each do |handler|
198
199
  handler.call(msg, exception)
199
200
  rescue => e
200
201
  handle_exception(e, {context: "Error calling death handler", job: msg})
@@ -4,7 +4,8 @@ require "time"
4
4
  module Sidekiq
5
5
  module JobUtil
6
6
  # These functions encapsulate various job utilities.
7
- # They must be simple and free from side effects.
7
+
8
+ TRANSIENT_ATTRIBUTES = %w[]
8
9
 
9
10
  def validate(item)
10
11
  raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: `#{item}`") unless item.is_a?(Hash) && item.key?("class") && item.key?("args")
@@ -16,13 +17,13 @@ module Sidekiq
16
17
 
17
18
  def verify_json(item)
18
19
  job_class = item["wrapped"] || item["class"]
19
- if Sidekiq.options[:on_complex_arguments] == :raise
20
+ if Sidekiq[:on_complex_arguments] == :raise
20
21
  msg = <<~EOM
21
22
  Job arguments to #{job_class} must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices.
22
23
  To disable this error, remove `Sidekiq.strict_args!` from your initializer.
23
24
  EOM
24
25
  raise(ArgumentError, msg) unless json_safe?(item)
25
- elsif Sidekiq.options[:on_complex_arguments] == :warn
26
+ elsif Sidekiq[:on_complex_arguments] == :warn
26
27
  Sidekiq.logger.warn <<~EOM unless json_safe?(item)
27
28
  Job arguments to #{job_class} do not serialize to JSON safely. This will raise an error in
28
29
  Sidekiq 7.0. See https://github.com/mperham/sidekiq/wiki/Best-Practices or raise an error today
@@ -42,6 +43,9 @@ module Sidekiq
42
43
 
43
44
  raise(ArgumentError, "Job must include a valid queue name") if item["queue"].nil? || item["queue"] == ""
44
45
 
46
+ # remove job attributes which aren't necessary to persist into Redis
47
+ TRANSIENT_ATTRIBUTES.each { |key| item.delete(key) }
48
+
45
49
  item["jid"] ||= SecureRandom.hex(12)
46
50
  item["class"] = item["class"].to_s
47
51
  item["queue"] = item["queue"].to_s
@@ -3,11 +3,12 @@
3
3
  require "sidekiq/manager"
4
4
  require "sidekiq/fetch"
5
5
  require "sidekiq/scheduled"
6
+ require "sidekiq/ring_buffer"
6
7
 
7
8
  module Sidekiq
8
9
  # The Launcher starts the Manager and Poller threads and provides the process heartbeat.
9
10
  class Launcher
10
- include Util
11
+ include Sidekiq::Component
11
12
 
12
13
  STATS_TTL = 5 * 365 * 24 * 60 * 60 # 5 years
13
14
 
@@ -22,11 +23,11 @@ module Sidekiq
22
23
  attr_accessor :manager, :poller, :fetcher
23
24
 
24
25
  def initialize(options)
26
+ @config = options
25
27
  options[:fetch] ||= BasicFetch.new(options)
26
28
  @manager = Sidekiq::Manager.new(options)
27
- @poller = Sidekiq::Scheduled::Poller.new
29
+ @poller = Sidekiq::Scheduled::Poller.new(options)
28
30
  @done = false
29
- @options = options
30
31
  end
31
32
 
32
33
  def run
@@ -45,7 +46,7 @@ module Sidekiq
45
46
 
46
47
  # Shuts down this Sidekiq instance. Waits up to the deadline for all jobs to complete.
47
48
  def stop
48
- deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @options[:timeout]
49
+ deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @config[:timeout]
49
50
 
50
51
  @done = true
51
52
  @manager.quiet
@@ -55,8 +56,8 @@ module Sidekiq
55
56
 
56
57
  # Requeue everything in case there was a thread which fetched a job while the process was stopped.
57
58
  # This call is a no-op in Sidekiq but necessary for Sidekiq Pro.
58
- strategy = @options[:fetch]
59
- strategy.bulk_requeue([], @options)
59
+ strategy = @config[:fetch]
60
+ strategy.bulk_requeue([], @config)
60
61
 
61
62
  clear_heartbeat
62
63
  end
@@ -74,14 +75,14 @@ module Sidekiq
74
75
  heartbeat
75
76
  sleep BEAT_PAUSE
76
77
  end
77
- Sidekiq.logger.info("Heartbeat stopping...")
78
+ logger.info("Heartbeat stopping...")
78
79
  end
79
80
 
80
81
  def clear_heartbeat
81
82
  # Remove record from Redis since we are shutting down.
82
83
  # Note we don't stop the heartbeat thread; if the process
83
84
  # doesn't actually exit, it'll reappear in the Web UI.
84
- Sidekiq.redis do |conn|
85
+ redis do |conn|
85
86
  conn.pipelined do |pipeline|
86
87
  pipeline.srem("processes", identity)
87
88
  pipeline.unlink("#{identity}:work")
@@ -134,7 +135,7 @@ module Sidekiq
134
135
 
135
136
  nowdate = Time.now.utc.strftime("%Y-%m-%d")
136
137
 
137
- Sidekiq.redis do |conn|
138
+ redis do |conn|
138
139
  conn.multi do |transaction|
139
140
  transaction.incrby("stat:processed", procd)
140
141
  transaction.incrby("stat:processed:#{nowdate}", procd)
@@ -161,7 +162,7 @@ module Sidekiq
161
162
  fails = procd = 0
162
163
  kb = memory_usage(::Process.pid)
163
164
 
164
- _, exists, _, _, msg = Sidekiq.redis { |conn|
165
+ _, exists, _, _, msg = redis { |conn|
165
166
  conn.multi { |transaction|
166
167
  transaction.sadd("processes", key)
167
168
  transaction.exists?(key)
@@ -169,7 +170,7 @@ module Sidekiq
169
170
  "busy", curstate.size,
170
171
  "beat", Time.now.to_f,
171
172
  "rtt_us", rtt,
172
- "quiet", @done,
173
+ "quiet", @done.to_s,
173
174
  "rss", kb)
174
175
  transaction.expire(key, 60)
175
176
  transaction.rpop("#{key}-signals")
@@ -199,7 +200,7 @@ module Sidekiq
199
200
 
200
201
  def check_rtt
201
202
  a = b = 0
202
- Sidekiq.redis do |x|
203
+ redis do |x|
203
204
  a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
204
205
  x.ping
205
206
  b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
@@ -210,7 +211,7 @@ module Sidekiq
210
211
  # Workable is < 10,000µs
211
212
  # Log a warning if it's a disaster.
212
213
  if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
213
- Sidekiq.logger.warn <<~EOM
214
+ logger.warn <<~EOM
214
215
  Your Redis network connection is performing extremely poorly.
215
216
  Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
216
217
  Ensure Redis is running in the same AZ or datacenter as Sidekiq.
@@ -247,10 +248,10 @@ module Sidekiq
247
248
  "hostname" => hostname,
248
249
  "started_at" => Time.now.to_f,
249
250
  "pid" => ::Process.pid,
250
- "tag" => @options[:tag] || "",
251
- "concurrency" => @options[:concurrency],
252
- "queues" => @options[:queues].uniq,
253
- "labels" => @options[:labels],
251
+ "tag" => @config[:tag] || "",
252
+ "concurrency" => @config[:concurrency],
253
+ "queues" => @config[:queues].uniq,
254
+ "labels" => @config[:labels],
254
255
  "identity" => identity
255
256
  }
256
257
  end
@@ -18,7 +18,7 @@ module Sidekiq
18
18
  end
19
19
 
20
20
  def self.add(k, v)
21
- Thread.current[:sidekiq_context][k] = v
21
+ current[k] = v
22
22
  end
23
23
  end
24
24
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sidekiq/util"
4
3
  require "sidekiq/processor"
5
4
  require "sidekiq/fetch"
6
5
  require "set"
@@ -21,29 +20,26 @@ module Sidekiq
21
20
  # the shutdown process. The other tasks are performed by other threads.
22
21
  #
23
22
  class Manager
24
- include Util
23
+ include Sidekiq::Component
25
24
 
26
25
  attr_reader :workers
27
- attr_reader :options
28
26
 
29
27
  def initialize(options = {})
28
+ @config = options
30
29
  logger.debug { options.inspect }
31
- @options = options
32
30
  @count = options[:concurrency] || 10
33
31
  raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1
34
32
 
35
33
  @done = false
36
34
  @workers = Set.new
37
35
  @count.times do
38
- @workers << Processor.new(self, options)
36
+ @workers << Processor.new(@config, &method(:processor_result))
39
37
  end
40
38
  @plock = Mutex.new
41
39
  end
42
40
 
43
41
  def start
44
- @workers.each do |x|
45
- x.start
46
- end
42
+ @workers.each(&:start)
47
43
  end
48
44
 
49
45
  def quiet
@@ -51,7 +47,7 @@ module Sidekiq
51
47
  @done = true
52
48
 
53
49
  logger.info { "Terminating quiet threads" }
54
- @workers.each { |x| x.terminate }
50
+ @workers.each(&:terminate)
55
51
  fire_event(:quiet, reverse: true)
56
52
  end
57
53
 
@@ -72,17 +68,11 @@ module Sidekiq
72
68
  hard_shutdown
73
69
  end
74
70
 
75
- def processor_stopped(processor)
76
- @plock.synchronize do
77
- @workers.delete(processor)
78
- end
79
- end
80
-
81
- def processor_died(processor, reason)
71
+ def processor_result(processor, reason = nil)
82
72
  @plock.synchronize do
83
73
  @workers.delete(processor)
84
74
  unless @done
85
- p = Processor.new(self, options)
75
+ p = Processor.new(@config, &method(:processor_result))
86
76
  @workers << p
87
77
  p.start
88
78
  end
@@ -107,7 +97,7 @@ module Sidekiq
107
97
  jobs = cleanup.map { |p| p.job }.compact
108
98
 
109
99
  logger.warn { "Terminating #{cleanup.size} busy threads" }
110
- logger.warn { "Jobs still in progress #{jobs.inspect}" }
100
+ logger.debug { "Jobs still in progress #{jobs.inspect}" }
111
101
 
112
102
  # Re-enqueue unfinished jobs
113
103
  # NOTE: You may notice that we may push a job back to redis before
@@ -115,8 +105,8 @@ module Sidekiq
115
105
  # contract says that jobs are run AT LEAST once. Process termination
116
106
  # is delayed until we're certain the jobs are back in Redis because
117
107
  # it is worse to lose a job than to run it twice.
118
- strategy = @options[:fetch]
119
- strategy.bulk_requeue(jobs, @options)
108
+ strategy = @config[:fetch]
109
+ strategy.bulk_requeue(jobs, @config)
120
110
  end
121
111
 
122
112
  cleanup.each do |processor|
@@ -129,5 +119,18 @@ module Sidekiq
129
119
  deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + 3
130
120
  wait_for(deadline) { @workers.empty? }
131
121
  end
122
+
123
+ # hack for quicker development / testing environment #2774
124
+ PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5
125
+
126
+ # Wait for the orblock to be true or the deadline passed.
127
+ def wait_for(deadline, &condblock)
128
+ remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
129
+ while remaining > PAUSE_TIME
130
+ return if condblock.call
131
+ sleep PAUSE_TIME
132
+ remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
133
+ end
134
+ end
132
135
  end
133
136
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "sidekiq/middleware/modules"
4
+
3
5
  module Sidekiq
4
6
  # Middleware is code configured to run before/after
5
7
  # a message is processed. It is patterned after Rack
@@ -44,10 +46,12 @@ module Sidekiq
44
46
  # This is an example of a minimal server middleware:
45
47
  #
46
48
  # class MyServerHook
49
+ # include Sidekiq::ServerMiddleware
47
50
  # def call(job_instance, msg, queue)
48
- # puts "Before job"
51
+ # logger.info "Before job"
52
+ # redis {|conn| conn.get("foo") } # do something in Redis
49
53
  # yield
50
- # puts "After job"
54
+ # logger.info "After job"
51
55
  # end
52
56
  # end
53
57
  #
@@ -56,10 +60,11 @@ module Sidekiq
56
60
  # to Redis:
57
61
  #
58
62
  # class MyClientHook
63
+ # include Sidekiq::ClientMiddleware
59
64
  # def call(job_class, msg, queue, redis_pool)
60
- # puts "Before push"
65
+ # logger.info "Before push"
61
66
  # result = yield
62
- # puts "After push"
67
+ # logger.info "After push"
63
68
  # result
64
69
  # end
65
70
  # end
@@ -76,7 +81,8 @@ module Sidekiq
76
81
  entries.each(&block)
77
82
  end
78
83
 
79
- def initialize
84
+ def initialize(config = nil)
85
+ @config = config
80
86
  @entries = nil
81
87
  yield self if block_given?
82
88
  end
@@ -91,24 +97,24 @@ module Sidekiq
91
97
 
92
98
  def add(klass, *args)
93
99
  remove(klass)
94
- entries << Entry.new(klass, *args)
100
+ entries << Entry.new(@config, klass, *args)
95
101
  end
96
102
 
97
103
  def prepend(klass, *args)
98
104
  remove(klass)
99
- entries.insert(0, Entry.new(klass, *args))
105
+ entries.insert(0, Entry.new(@config, klass, *args))
100
106
  end
101
107
 
102
108
  def insert_before(oldklass, newklass, *args)
103
109
  i = entries.index { |entry| entry.klass == newklass }
104
- new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
110
+ new_entry = i.nil? ? Entry.new(@config, newklass, *args) : entries.delete_at(i)
105
111
  i = entries.index { |entry| entry.klass == oldklass } || 0
106
112
  entries.insert(i, new_entry)
107
113
  end
108
114
 
109
115
  def insert_after(oldklass, newklass, *args)
110
116
  i = entries.index { |entry| entry.klass == newklass }
111
- new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
117
+ new_entry = i.nil? ? Entry.new(@config, newklass, *args) : entries.delete_at(i)
112
118
  i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1
113
119
  entries.insert(i + 1, new_entry)
114
120
  end
@@ -149,13 +155,16 @@ module Sidekiq
149
155
  class Entry
150
156
  attr_reader :klass
151
157
 
152
- def initialize(klass, *args)
158
+ def initialize(config, klass, *args)
159
+ @config = config
153
160
  @klass = klass
154
161
  @args = args
155
162
  end
156
163
 
157
164
  def make_new
158
- @klass.new(*@args)
165
+ x = @klass.new(*@args)
166
+ x.config = @config if @config && x.respond_to?(:config=)
167
+ x
159
168
  end
160
169
  end
161
170
  end
@@ -15,6 +15,8 @@ module Sidekiq
15
15
  #
16
16
  module CurrentAttributes
17
17
  class Save
18
+ include Sidekiq::ClientMiddleware
19
+
18
20
  def initialize(cattr)
19
21
  @klass = cattr
20
22
  end
@@ -31,6 +33,8 @@ module Sidekiq
31
33
  end
32
34
 
33
35
  class Load
36
+ include Sidekiq::ServerMiddleware
37
+
34
38
  def initialize(cattr)
35
39
  @klass = cattr
36
40
  end
@@ -10,6 +10,7 @@ module Sidekiq::Middleware::I18n
10
10
  # Get the current locale and store it in the message
11
11
  # to be sent to Sidekiq.
12
12
  class Client
13
+ include Sidekiq::ClientMiddleware
13
14
  def call(_jobclass, job, _queue, _redis)
14
15
  job["locale"] ||= I18n.locale
15
16
  yield
@@ -18,6 +19,7 @@ module Sidekiq::Middleware::I18n
18
19
 
19
20
  # Pull the msg locale out and set the current thread to use it.
20
21
  class Server
22
+ include Sidekiq::ServerMiddleware
21
23
  def call(_jobclass, job, _queue, &block)
22
24
  I18n.with_locale(job.fetch("locale", I18n.default_locale), &block)
23
25
  end
@@ -0,0 +1,21 @@
1
+ module Sidekiq
2
+ # Server-side middleware must import this Module in order
3
+ # to get access to server resources during `call`.
4
+ module ServerMiddleware
5
+ attr_accessor :config
6
+ def redis_pool
7
+ config.redis_pool
8
+ end
9
+
10
+ def logger
11
+ config.logger
12
+ end
13
+
14
+ def redis(&block)
15
+ config.redis(&block)
16
+ end
17
+ end
18
+
19
+ # no difference for now
20
+ ClientMiddleware = ServerMiddleware
21
+ end
@@ -19,9 +19,9 @@ module Sidekiq
19
19
  total_size, items = conn.multi { |transaction|
20
20
  transaction.zcard(key)
21
21
  if rev
22
- transaction.zrevrange(key, starting, ending, with_scores: true)
22
+ transaction.zrevrange(key, starting, ending, withscores: true)
23
23
  else
24
- transaction.zrange(key, starting, ending, with_scores: true)
24
+ transaction.zrange(key, starting, ending, withscores: true)
25
25
  end
26
26
  }
27
27
  [current_page, total_size, items]
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sidekiq/util"
4
3
  require "sidekiq/fetch"
5
4
  require "sidekiq/job_logger"
6
5
  require "sidekiq/job_retry"
@@ -15,29 +14,30 @@ module Sidekiq
15
14
  # b. run the middleware chain
16
15
  # c. call #perform
17
16
  #
18
- # A Processor can exit due to shutdown (processor_stopped)
19
- # or due to an error during job execution (processor_died)
17
+ # A Processor can exit due to shutdown or due to
18
+ # an error during job execution.
20
19
  #
21
20
  # If an error occurs in the job execution, the
22
21
  # Processor calls the Manager to create a new one
23
22
  # to replace itself and exits.
24
23
  #
25
24
  class Processor
26
- include Util
25
+ include Sidekiq::Component
27
26
 
28
27
  attr_reader :thread
29
28
  attr_reader :job
30
29
 
31
- def initialize(mgr, options)
32
- @mgr = mgr
30
+ def initialize(options, &block)
31
+ @callback = block
33
32
  @down = false
34
33
  @done = false
35
34
  @job = nil
36
35
  @thread = nil
36
+ @config = options
37
37
  @strategy = options[:fetch]
38
38
  @reloader = options[:reloader] || proc { |&block| block.call }
39
39
  @job_logger = (options[:job_logger] || Sidekiq::JobLogger).new
40
- @retrier = Sidekiq::JobRetry.new
40
+ @retrier = Sidekiq::JobRetry.new(options)
41
41
  end
42
42
 
43
43
  def terminate(wait = false)
@@ -66,14 +66,14 @@ module Sidekiq
66
66
 
67
67
  def run
68
68
  process_one until @done
69
- @mgr.processor_stopped(self)
69
+ @callback.call(self)
70
70
  rescue Sidekiq::Shutdown
71
- @mgr.processor_stopped(self)
71
+ @callback.call(self)
72
72
  rescue Exception => ex
73
- @mgr.processor_died(self, ex)
73
+ @callback.call(self, ex)
74
74
  end
75
75
 
76
- def process_one
76
+ def process_one(&block)
77
77
  @job = fetch
78
78
  process(@job) if @job
79
79
  @job = nil
@@ -160,7 +160,7 @@ module Sidekiq
160
160
  ack = false
161
161
  begin
162
162
  dispatch(job_hash, queue, jobstr) do |inst|
163
- Sidekiq.server_middleware.invoke(inst, job_hash, queue) do
163
+ @config.server_middleware.invoke(inst, job_hash, queue) do
164
164
  execute_job(inst, job_hash["args"])
165
165
  end
166
166
  end
data/lib/sidekiq/rails.rb CHANGED
@@ -38,12 +38,12 @@ module Sidekiq
38
38
  end
39
39
 
40
40
  initializer "sidekiq.rails_logger" do
41
- Sidekiq.configure_server do |_|
41
+ Sidekiq.configure_server do |config|
42
42
  # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
43
43
  # it will appear in the Sidekiq console with all of the job context. See #5021 and
44
44
  # https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
45
- unless ::Rails.logger == ::Sidekiq.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
46
- ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(::Sidekiq.logger))
45
+ unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
46
+ ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
47
47
  end
48
48
  end
49
49
  end
@@ -60,8 +60,8 @@ module Sidekiq
60
60
  #
61
61
  # None of this matters on the client-side, only within the Sidekiq process itself.
62
62
  config.after_initialize do
63
- Sidekiq.configure_server do |_|
64
- Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
63
+ Sidekiq.configure_server do |config|
64
+ config[:reloader] = Sidekiq::Rails::Reloader.new
65
65
  end
66
66
  end
67
67
  end