sidekiq 6.2.1 → 6.4.0

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +79 -1
  3. data/LICENSE +3 -3
  4. data/README.md +3 -3
  5. data/lib/generators/sidekiq/job_generator.rb +57 -0
  6. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  7. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  8. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  9. data/lib/sidekiq/api.rb +86 -60
  10. data/lib/sidekiq/cli.rb +13 -3
  11. data/lib/sidekiq/client.rb +5 -39
  12. data/lib/sidekiq/delay.rb +2 -0
  13. data/lib/sidekiq/extensions/action_mailer.rb +2 -2
  14. data/lib/sidekiq/extensions/active_record.rb +2 -2
  15. data/lib/sidekiq/extensions/class_methods.rb +2 -2
  16. data/lib/sidekiq/extensions/generic_proxy.rb +5 -3
  17. data/lib/sidekiq/fetch.rb +5 -4
  18. data/lib/sidekiq/job.rb +13 -0
  19. data/lib/sidekiq/job_logger.rb +1 -1
  20. data/lib/sidekiq/job_retry.rb +9 -11
  21. data/lib/sidekiq/job_util.rb +65 -0
  22. data/lib/sidekiq/launcher.rb +18 -18
  23. data/lib/sidekiq/manager.rb +7 -9
  24. data/lib/sidekiq/middleware/chain.rb +5 -3
  25. data/lib/sidekiq/middleware/current_attributes.rb +57 -0
  26. data/lib/sidekiq/rails.rb +11 -0
  27. data/lib/sidekiq/redis_connection.rb +4 -6
  28. data/lib/sidekiq/scheduled.rb +51 -16
  29. data/lib/sidekiq/testing.rb +1 -3
  30. data/lib/sidekiq/util.rb +13 -0
  31. data/lib/sidekiq/version.rb +1 -1
  32. data/lib/sidekiq/web/action.rb +1 -1
  33. data/lib/sidekiq/web/application.rb +9 -6
  34. data/lib/sidekiq/web/helpers.rb +9 -21
  35. data/lib/sidekiq/web.rb +4 -3
  36. data/lib/sidekiq/worker.rb +127 -7
  37. data/lib/sidekiq.rb +8 -1
  38. data/sidekiq.gemspec +1 -1
  39. data/web/assets/javascripts/application.js +82 -61
  40. data/web/assets/javascripts/dashboard.js +51 -51
  41. data/web/assets/stylesheets/application-dark.css +28 -45
  42. data/web/assets/stylesheets/application-rtl.css +0 -4
  43. data/web/assets/stylesheets/application.css +23 -237
  44. data/web/locales/ar.yml +8 -2
  45. data/web/locales/en.yml +4 -1
  46. data/web/locales/es.yml +18 -2
  47. data/web/locales/fr.yml +7 -0
  48. data/web/locales/ja.yml +3 -0
  49. data/web/locales/lt.yml +1 -1
  50. data/web/views/_footer.erb +1 -1
  51. data/web/views/_job_info.erb +1 -1
  52. data/web/views/_poll_link.erb +2 -5
  53. data/web/views/_summary.erb +7 -7
  54. data/web/views/busy.erb +5 -5
  55. data/web/views/dashboard.erb +22 -14
  56. data/web/views/dead.erb +1 -1
  57. data/web/views/layout.erb +1 -1
  58. data/web/views/morgue.erb +6 -6
  59. data/web/views/queue.erb +10 -10
  60. data/web/views/queues.erb +3 -3
  61. data/web/views/retries.erb +7 -7
  62. data/web/views/retry.erb +1 -1
  63. data/web/views/scheduled.erb +1 -1
  64. metadata +10 -7
  65. data/lib/generators/sidekiq/worker_generator.rb +0 -57
@@ -13,18 +13,20 @@ module Sidekiq
13
13
  @opts = options
14
14
  end
15
15
 
16
- def method_missing(name, *args)
16
+ def method_missing(name, *args, **kwargs)
17
17
  # Sidekiq has a limitation in that its message must be JSON.
18
18
  # JSON can't round trip real Ruby objects so we use YAML to
19
19
  # serialize the objects to a String. The YAML will be converted
20
20
  # to JSON and then deserialized on the other side back into a
21
21
  # Ruby object.
22
- obj = [@target, name, args]
22
+ obj = [@target, name, args, kwargs]
23
23
  marshalled = ::YAML.dump(obj)
24
24
  if marshalled.size > SIZE_LIMIT
25
25
  ::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" }
26
26
  end
27
- @performable.client_push({"class" => @performable, "args" => [marshalled]}.merge(@opts))
27
+ @performable.client_push({"class" => @performable,
28
+ "args" => [marshalled],
29
+ "display_class" => "#{@target}.#{name}"}.merge(@opts))
28
30
  end
29
31
  end
30
32
  end
data/lib/sidekiq/fetch.rb CHANGED
@@ -40,7 +40,7 @@ module Sidekiq
40
40
  # 4825 Sidekiq Pro with all queues paused will return an
41
41
  # empty set of queues with a trailing TIMEOUT value.
42
42
  if qs.size <= 1
43
- sleep(2)
43
+ sleep(TIMEOUT)
44
44
  return nil
45
45
  end
46
46
 
@@ -79,9 +79,10 @@ module Sidekiq
79
79
  if @strictly_ordered_queues
80
80
  @queues
81
81
  else
82
- queues = @queues.shuffle!.uniq
83
- queues << TIMEOUT
84
- queues
82
+ permute = @queues.shuffle
83
+ permute.uniq!
84
+ permute << TIMEOUT
85
+ permute
85
86
  end
86
87
  end
87
88
  end
@@ -0,0 +1,13 @@
1
+ require "sidekiq/worker"
2
+
3
+ module Sidekiq
4
+ # Sidekiq::Job is a new alias for Sidekiq::Worker as of Sidekiq 6.3.0.
5
+ # Use `include Sidekiq::Job` rather than `include Sidekiq::Worker`.
6
+ #
7
+ # The term "worker" is too generic and overly confusing, used in several
8
+ # different contexts meaning different things. Many people call a Sidekiq
9
+ # process a "worker". Some people call the thread that executes jobs a
10
+ # "worker". This change brings Sidekiq closer to ActiveJob where your job
11
+ # classes extend ApplicationJob.
12
+ Job = Worker
13
+ end
@@ -38,7 +38,7 @@ module Sidekiq
38
38
  # If we're using a wrapper class, like ActiveJob, use the "wrapped"
39
39
  # attribute to expose the underlying thing.
40
40
  h = {
41
- class: job_hash["wrapped"] || job_hash["class"],
41
+ class: job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"],
42
42
  jid: job_hash["jid"]
43
43
  }
44
44
  h[:bid] = job_hash["bid"] if job_hash["bid"]
@@ -34,9 +34,10 @@ module Sidekiq
34
34
  # The job will be retried this number of times before giving up. (If simply
35
35
  # 'true', Sidekiq retries 25 times)
36
36
  #
37
- # We'll add a bit more data to the job to support retries:
37
+ # Relevant options for job retries:
38
38
  #
39
- # * 'queue' - the queue to use
39
+ # * 'queue' - the queue for the initial job
40
+ # * 'retry_queue' - if job retries should be pushed to a different (e.g. lower priority) queue
40
41
  # * 'retry_count' - number of times we've retried so far.
41
42
  # * 'error_message' - the message from the exception
42
43
  # * 'error_class' - the exception class
@@ -52,11 +53,12 @@ module Sidekiq
52
53
  #
53
54
  # Sidekiq.options[:max_retries] = 7
54
55
  #
55
- # or limit the number of retries for a particular worker with:
56
+ # or limit the number of retries for a particular worker and send retries to
57
+ # a low priority queue with:
56
58
  #
57
59
  # class MyWorker
58
60
  # include Sidekiq::Worker
59
- # sidekiq_options :retry => 10
61
+ # sidekiq_options retry: 10, retry_queue: 'low'
60
62
  # end
61
63
  #
62
64
  class JobRetry
@@ -214,16 +216,12 @@ module Sidekiq
214
216
  end
215
217
 
216
218
  def delay_for(worker, count, exception)
219
+ jitter = rand(10) * (count + 1)
217
220
  if worker&.sidekiq_retry_in_block
218
221
  custom_retry_in = retry_in(worker, count, exception).to_i
219
- return custom_retry_in if custom_retry_in > 0
222
+ return custom_retry_in + jitter if custom_retry_in > 0
220
223
  end
221
- seconds_to_delay(count)
222
- end
223
-
224
- # delayed_job uses the same basic formula
225
- def seconds_to_delay(count)
226
- (count**4) + 15 + (rand(30) * (count + 1))
224
+ (count**4) + 15 + jitter
227
225
  end
228
226
 
229
227
  def retry_in(worker, count, exception)
@@ -0,0 +1,65 @@
1
+ require "securerandom"
2
+ require "time"
3
+
4
+ module Sidekiq
5
+ module JobUtil
6
+ # These functions encapsulate various job utilities.
7
+ # They must be simple and free from side effects.
8
+
9
+ def validate(item)
10
+ raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: `#{item}`") unless item.is_a?(Hash) && item.key?("class") && item.key?("args")
11
+ raise(ArgumentError, "Job args must be an Array: `#{item}`") unless item["args"].is_a?(Array)
12
+ raise(ArgumentError, "Job class must be either a Class or String representation of the class name: `#{item}`") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
13
+ raise(ArgumentError, "Job 'at' must be a Numeric timestamp: `#{item}`") if item.key?("at") && !item["at"].is_a?(Numeric)
14
+ raise(ArgumentError, "Job tags must be an Array: `#{item}`") if item["tags"] && !item["tags"].is_a?(Array)
15
+
16
+ if Sidekiq.options[:on_complex_arguments] == :raise
17
+ msg = <<~EOM
18
+ Job arguments to #{item["class"]} must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices.
19
+ To disable this error, remove `Sidekiq.strict_args!` from your initializer.
20
+ EOM
21
+ raise(ArgumentError, msg) unless json_safe?(item)
22
+ elsif Sidekiq.options[:on_complex_arguments] == :warn
23
+ Sidekiq.logger.warn <<~EOM unless json_safe?(item)
24
+ Job arguments to #{item["class"]} do not serialize to JSON safely. This will raise an error in
25
+ Sidekiq 7.0. See https://github.com/mperham/sidekiq/wiki/Best-Practices or raise an error today
26
+ by calling `Sidekiq.strict_args!` during Sidekiq initialization.
27
+ EOM
28
+ end
29
+ end
30
+
31
+ def normalize_item(item)
32
+ validate(item)
33
+
34
+ # merge in the default sidekiq_options for the item's class and/or wrapped element
35
+ # this allows ActiveJobs to control sidekiq_options too.
36
+ defaults = normalized_hash(item["class"])
37
+ defaults = defaults.merge(item["wrapped"].get_sidekiq_options) if item["wrapped"].respond_to?(:get_sidekiq_options)
38
+ item = defaults.merge(item)
39
+
40
+ raise(ArgumentError, "Job must include a valid queue name") if item["queue"].nil? || item["queue"] == ""
41
+
42
+ item["class"] = item["class"].to_s
43
+ item["queue"] = item["queue"].to_s
44
+ item["jid"] ||= SecureRandom.hex(12)
45
+ item["created_at"] ||= Time.now.to_f
46
+
47
+ item
48
+ end
49
+
50
+ def normalized_hash(item_class)
51
+ if item_class.is_a?(Class)
52
+ raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") unless item_class.respond_to?(:get_sidekiq_options)
53
+ item_class.get_sidekiq_options
54
+ else
55
+ Sidekiq.default_worker_options
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def json_safe?(item)
62
+ JSON.parse(JSON.dump(item["args"])) == item["args"]
63
+ end
64
+ end
65
+ end
@@ -69,10 +69,12 @@ module Sidekiq
69
69
 
70
70
  private unless $TESTING
71
71
 
72
+ BEAT_PAUSE = 5
73
+
72
74
  def start_heartbeat
73
75
  loop do
74
76
  heartbeat
75
- sleep 5
77
+ sleep BEAT_PAUSE
76
78
  end
77
79
  Sidekiq.logger.info("Heartbeat stopping...")
78
80
  end
@@ -211,6 +213,8 @@ module Sidekiq
211
213
  Your Redis network connection is performing extremely poorly.
212
214
  Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
213
215
  Ensure Redis is running in the same AZ or datacenter as Sidekiq.
216
+ If these values are close to 100,000, that means your Sidekiq process may be
217
+ CPU overloaded; see https://github.com/mperham/sidekiq/discussions/5039
214
218
  EOM
215
219
  RTT_READINGS.reset
216
220
  end
@@ -238,26 +242,22 @@ module Sidekiq
238
242
  end
239
243
 
240
244
  def to_data
241
- @data ||= begin
242
- {
243
- "hostname" => hostname,
244
- "started_at" => Time.now.to_f,
245
- "pid" => ::Process.pid,
246
- "tag" => @options[:tag] || "",
247
- "concurrency" => @options[:concurrency],
248
- "queues" => @options[:queues].uniq,
249
- "labels" => @options[:labels],
250
- "identity" => identity
251
- }
252
- end
245
+ @data ||= {
246
+ "hostname" => hostname,
247
+ "started_at" => Time.now.to_f,
248
+ "pid" => ::Process.pid,
249
+ "tag" => @options[:tag] || "",
250
+ "concurrency" => @options[:concurrency],
251
+ "queues" => @options[:queues].uniq,
252
+ "labels" => @options[:labels],
253
+ "identity" => identity
254
+ }
253
255
  end
254
256
 
255
257
  def to_json
256
- @json ||= begin
257
- # this data changes infrequently so dump it to a string
258
- # now so we don't need to dump it every heartbeat.
259
- Sidekiq.dump_json(to_data)
260
- end
258
+ # this data changes infrequently so dump it to a string
259
+ # now so we don't need to dump it every heartbeat.
260
+ @json ||= Sidekiq.dump_json(to_data)
261
261
  end
262
262
  end
263
263
  end
@@ -55,9 +55,6 @@ module Sidekiq
55
55
  fire_event(:quiet, reverse: true)
56
56
  end
57
57
 
58
- # hack for quicker development / testing environment #2774
59
- PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5
60
-
61
58
  def stop(deadline)
62
59
  quiet
63
60
  fire_event(:shutdown, reverse: true)
@@ -69,12 +66,7 @@ module Sidekiq
69
66
  return if @workers.empty?
70
67
 
71
68
  logger.info { "Pausing to allow workers to finish..." }
72
- remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
73
- while remaining > PAUSE_TIME
74
- return if @workers.empty?
75
- sleep PAUSE_TIME
76
- remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
77
- end
69
+ wait_for(deadline) { @workers.empty? }
78
70
  return if @workers.empty?
79
71
 
80
72
  hard_shutdown
@@ -130,6 +122,12 @@ module Sidekiq
130
122
  cleanup.each do |processor|
131
123
  processor.kill
132
124
  end
125
+
126
+ # when this method returns, we immediately call `exit` which may not give
127
+ # the remaining threads time to run `ensure` blocks, etc. We pause here up
128
+ # to 3 seconds to give threads a minimal amount of time to run `ensure` blocks.
129
+ deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + 3
130
+ wait_for(deadline) { @workers.empty? }
133
131
  end
134
132
  end
135
133
  end
@@ -90,12 +90,12 @@ module Sidekiq
90
90
  end
91
91
 
92
92
  def add(klass, *args)
93
- remove(klass) if exists?(klass)
93
+ remove(klass)
94
94
  entries << Entry.new(klass, *args)
95
95
  end
96
96
 
97
97
  def prepend(klass, *args)
98
- remove(klass) if exists?(klass)
98
+ remove(klass)
99
99
  entries.insert(0, Entry.new(klass, *args))
100
100
  end
101
101
 
@@ -132,7 +132,7 @@ module Sidekiq
132
132
  def invoke(*args)
133
133
  return yield if empty?
134
134
 
135
- chain = retrieve.dup
135
+ chain = retrieve
136
136
  traverse_chain = proc do
137
137
  if chain.empty?
138
138
  yield
@@ -144,6 +144,8 @@ module Sidekiq
144
144
  end
145
145
  end
146
146
 
147
+ private
148
+
147
149
  class Entry
148
150
  attr_reader :klass
149
151
 
@@ -0,0 +1,57 @@
1
+ require "active_support/current_attributes"
2
+
3
+ module Sidekiq
4
+ ##
5
+ # Automatically save and load any current attributes in the execution context
6
+ # so context attributes "flow" from Rails actions into any associated jobs.
7
+ # This can be useful for multi-tenancy, i18n locale, timezone, any implicit
8
+ # per-request attribute. See +ActiveSupport::CurrentAttributes+.
9
+ #
10
+ # @example
11
+ #
12
+ # # in your initializer
13
+ # require "sidekiq/middleware/current_attributes"
14
+ # Sidekiq::CurrentAttributes.persist(Myapp::Current)
15
+ #
16
+ module CurrentAttributes
17
+ class Save
18
+ def initialize(cattr)
19
+ @klass = cattr
20
+ end
21
+
22
+ def call(_, job, _, _)
23
+ attrs = @klass.attributes
24
+ if job.has_key?("cattr")
25
+ job["cattr"].merge!(attrs)
26
+ else
27
+ job["cattr"] = attrs
28
+ end
29
+ yield
30
+ end
31
+ end
32
+
33
+ class Load
34
+ def initialize(cattr)
35
+ @klass = cattr
36
+ end
37
+
38
+ def call(_, job, _, &block)
39
+ if job.has_key?("cattr")
40
+ @klass.set(job["cattr"], &block)
41
+ else
42
+ yield
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.persist(klass)
48
+ Sidekiq.configure_client do |config|
49
+ config.client_middleware.add Save, klass
50
+ end
51
+ Sidekiq.configure_server do |config|
52
+ config.client_middleware.add Save, klass
53
+ config.server_middleware.add Load, klass
54
+ end
55
+ end
56
+ end
57
+ end
data/lib/sidekiq/rails.rb CHANGED
@@ -37,6 +37,17 @@ module Sidekiq
37
37
  end
38
38
  end
39
39
 
40
+ initializer "sidekiq.rails_logger" do
41
+ Sidekiq.configure_server do |_|
42
+ # This is the integration code necessary so that if code uses `Rails.logger.info "Hello"`,
43
+ # it will appear in the Sidekiq console with all of the job context. See #5021 and
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))
47
+ end
48
+ end
49
+ end
50
+
40
51
  # This hook happens after all initializers are run, just before returning
41
52
  # from config/environment.rb back to sidekiq/cli.rb.
42
53
  #
@@ -94,12 +94,10 @@ module Sidekiq
94
94
  def log_info(options)
95
95
  redacted = "REDACTED"
96
96
 
97
- # deep clone so we can muck with these options all we want
98
- #
99
- # exclude SSL params from dump-and-load because some information isn't
100
- # safely dumpable in current Rubies
101
- keys = options.keys
102
- keys.delete(:ssl_params)
97
+ # Deep clone so we can muck with these options all we want and exclude
98
+ # params from dump-and-load that may contain objects that Marshal is
99
+ # unable to safely dump.
100
+ keys = options.keys - [:logger, :ssl_params]
103
101
  scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
104
102
  if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
105
103
  uri.password = redacted
@@ -9,29 +9,56 @@ module Sidekiq
9
9
  SETS = %w[retry schedule]
10
10
 
11
11
  class Enq
12
- def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = SETS)
12
+ LUA_ZPOPBYSCORE = <<~LUA
13
+ local key, now = KEYS[1], ARGV[1]
14
+ local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1)
15
+ if jobs[1] then
16
+ redis.call("zrem", key, jobs[1])
17
+ return jobs[1]
18
+ end
19
+ LUA
20
+
21
+ def initialize
22
+ @done = false
23
+ @lua_zpopbyscore_sha = nil
24
+ end
25
+
26
+ def enqueue_jobs(sorted_sets = SETS)
13
27
  # A job's "score" in Redis is the time at which it should be processed.
14
28
  # Just check Redis for the set of jobs with a timestamp before now.
15
29
  Sidekiq.redis do |conn|
16
30
  sorted_sets.each do |sorted_set|
17
- # Get next items in the queue with scores (time to execute) <= now.
18
- until (jobs = conn.zrangebyscore(sorted_set, "-inf", now, limit: [0, 100])).empty?
19
- # We need to go through the list one at a time to reduce the risk of something
20
- # going wrong between the time jobs are popped from the scheduled queue and when
21
- # they are pushed onto a work queue and losing the jobs.
22
- jobs.each do |job|
23
- # Pop item off the queue and add it to the work queue. If the job can't be popped from
24
- # the queue, it's because another process already popped it so we can move on to the
25
- # next one.
26
- if conn.zrem(sorted_set, job)
27
- Sidekiq::Client.push(Sidekiq.load_json(job))
28
- Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
29
- end
30
- end
31
+ # Get next item in the queue with score (time to execute) <= now.
32
+ # We need to go through the list one at a time to reduce the risk of something
33
+ # going wrong between the time jobs are popped from the scheduled queue and when
34
+ # they are pushed onto a work queue and losing the jobs.
35
+ while !@done && (job = zpopbyscore(conn, keys: [sorted_set], argv: [Time.now.to_f.to_s]))
36
+ Sidekiq::Client.push(Sidekiq.load_json(job))
37
+ Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
31
38
  end
32
39
  end
33
40
  end
34
41
  end
42
+
43
+ def terminate
44
+ @done = true
45
+ end
46
+
47
+ private
48
+
49
+ def zpopbyscore(conn, keys: nil, argv: nil)
50
+ if @lua_zpopbyscore_sha.nil?
51
+ raw_conn = conn.respond_to?(:redis) ? conn.redis : conn
52
+ @lua_zpopbyscore_sha = raw_conn.script(:load, LUA_ZPOPBYSCORE)
53
+ end
54
+
55
+ conn.evalsha(@lua_zpopbyscore_sha, keys: keys, argv: argv)
56
+ rescue Redis::CommandError => e
57
+ raise unless e.message.start_with?("NOSCRIPT")
58
+
59
+ @lua_zpopbyscore_sha = nil
60
+ retry
61
+ end
35
62
  end
36
63
 
37
64
  ##
@@ -49,11 +76,14 @@ module Sidekiq
49
76
  @sleeper = ConnectionPool::TimedStack.new
50
77
  @done = false
51
78
  @thread = nil
79
+ @count_calls = 0
52
80
  end
53
81
 
54
82
  # Shut down this instance, will pause until the thread is dead.
55
83
  def terminate
56
84
  @done = true
85
+ @enq.terminate if @enq.respond_to?(:terminate)
86
+
57
87
  if @thread
58
88
  t = @thread
59
89
  @thread = nil
@@ -152,8 +182,13 @@ module Sidekiq
152
182
  end
153
183
 
154
184
  def process_count
155
- pcount = Sidekiq::ProcessSet.new.size
185
+ # The work buried within Sidekiq::ProcessSet#cleanup can be
186
+ # expensive at scale. Cut it down by 90% with this counter.
187
+ # NB: This method is only called by the scheduler thread so we
188
+ # don't need to worry about the thread safety of +=.
189
+ pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
156
190
  pcount = 1 if pcount == 0
191
+ @count_calls += 1
157
192
  pcount
158
193
  end
159
194
 
@@ -338,7 +338,5 @@ module Sidekiq
338
338
  end
339
339
 
340
340
  if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
341
- puts("**************************************************")
342
- puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
343
- puts("**************************************************")
341
+ warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
344
342
  end
data/lib/sidekiq/util.rb CHANGED
@@ -39,6 +39,19 @@ module Sidekiq
39
39
  module Util
40
40
  include ExceptionHandler
41
41
 
42
+ # hack for quicker development / testing environment #2774
43
+ PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5
44
+
45
+ # Wait for the orblock to be true or the deadline passed.
46
+ def wait_for(deadline, &condblock)
47
+ remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
48
+ while remaining > PAUSE_TIME
49
+ return if condblock.call
50
+ sleep PAUSE_TIME
51
+ remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
52
+ end
53
+ end
54
+
42
55
  def watchdog(last_words)
43
56
  yield
44
57
  rescue Exception => ex
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "6.2.1"
4
+ VERSION = "6.4.0"
5
5
  end
@@ -68,7 +68,7 @@ module Sidekiq
68
68
  end
69
69
 
70
70
  def json(payload)
71
- [200, {"Content-Type" => "application/json", "Cache-Control" => "no-cache"}, [Sidekiq.dump_json(payload)]]
71
+ [200, {"Content-Type" => "application/json", "Cache-Control" => "private, no-store"}, [Sidekiq.dump_json(payload)]]
72
72
  end
73
73
 
74
74
  def initialize(env, block)
@@ -50,7 +50,10 @@ module Sidekiq
50
50
 
51
51
  get "/" do
52
52
  @redis_info = redis_info.select { |k, v| REDIS_KEYS.include? k }
53
- stats_history = Sidekiq::Stats::History.new((params["days"] || 30).to_i)
53
+ days = (params["days"] || 30).to_i
54
+ return halt(401) if days < 1 || days > 180
55
+
56
+ stats_history = Sidekiq::Stats::History.new(days)
54
57
  @processed_history = stats_history.processed
55
58
  @failed_history = stats_history.failed
56
59
 
@@ -91,8 +94,8 @@ module Sidekiq
91
94
 
92
95
  @count = (params["count"] || 25).to_i
93
96
  @queue = Sidekiq::Queue.new(@name)
94
- (@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
95
- @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
97
+ (@current_page, @total_size, @jobs) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
98
+ @jobs = @jobs.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
96
99
 
97
100
  erb(:queue)
98
101
  end
@@ -113,7 +116,7 @@ module Sidekiq
113
116
 
114
117
  post "/queues/:name/delete" do
115
118
  name = route_params[:name]
116
- Sidekiq::Job.new(params["key_val"], name).delete
119
+ Sidekiq::JobRecord.new(params["key_val"], name).delete
117
120
 
118
121
  redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
119
122
  end
@@ -299,7 +302,7 @@ module Sidekiq
299
302
  return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action
300
303
 
301
304
  app = @klass
302
- resp = catch(:halt) do # rubocop:disable Standard/SemanticBlocks
305
+ resp = catch(:halt) do
303
306
  self.class.run_befores(app, action)
304
307
  action.instance_exec env, &action.block
305
308
  ensure
@@ -314,7 +317,7 @@ module Sidekiq
314
317
  # rendered content goes here
315
318
  headers = {
316
319
  "Content-Type" => "text/html",
317
- "Cache-Control" => "no-cache",
320
+ "Cache-Control" => "private, no-store",
318
321
  "Content-Language" => action.locale,
319
322
  "Content-Security-Policy" => CSP_HEADER
320
323
  }