sidekiq 6.5.1 → 7.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +142 -12
  3. data/README.md +40 -32
  4. data/bin/sidekiq +3 -8
  5. data/bin/sidekiqload +186 -118
  6. data/bin/sidekiqmon +3 -0
  7. data/lib/sidekiq/api.rb +226 -139
  8. data/lib/sidekiq/capsule.rb +127 -0
  9. data/lib/sidekiq/cli.rb +55 -61
  10. data/lib/sidekiq/client.rb +31 -18
  11. data/lib/sidekiq/component.rb +5 -1
  12. data/lib/sidekiq/config.rb +270 -0
  13. data/lib/sidekiq/deploy.rb +62 -0
  14. data/lib/sidekiq/embedded.rb +61 -0
  15. data/lib/sidekiq/fetch.rb +11 -14
  16. data/lib/sidekiq/job.rb +375 -10
  17. data/lib/sidekiq/job_logger.rb +2 -2
  18. data/lib/sidekiq/job_retry.rb +62 -41
  19. data/lib/sidekiq/job_util.rb +48 -14
  20. data/lib/sidekiq/launcher.rb +71 -65
  21. data/lib/sidekiq/logger.rb +1 -26
  22. data/lib/sidekiq/manager.rb +9 -11
  23. data/lib/sidekiq/metrics/query.rb +153 -0
  24. data/lib/sidekiq/metrics/shared.rb +95 -0
  25. data/lib/sidekiq/metrics/tracking.rb +136 -0
  26. data/lib/sidekiq/middleware/chain.rb +84 -48
  27. data/lib/sidekiq/middleware/current_attributes.rb +12 -17
  28. data/lib/sidekiq/monitor.rb +17 -4
  29. data/lib/sidekiq/paginator.rb +9 -1
  30. data/lib/sidekiq/processor.rb +27 -27
  31. data/lib/sidekiq/rails.rb +4 -9
  32. data/lib/sidekiq/redis_client_adapter.rb +8 -47
  33. data/lib/sidekiq/redis_connection.rb +11 -113
  34. data/lib/sidekiq/scheduled.rb +60 -33
  35. data/lib/sidekiq/testing.rb +5 -33
  36. data/lib/sidekiq/transaction_aware_client.rb +4 -5
  37. data/lib/sidekiq/version.rb +2 -1
  38. data/lib/sidekiq/web/action.rb +3 -3
  39. data/lib/sidekiq/web/application.rb +40 -9
  40. data/lib/sidekiq/web/csrf_protection.rb +1 -1
  41. data/lib/sidekiq/web/helpers.rb +32 -18
  42. data/lib/sidekiq/web.rb +7 -14
  43. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  44. data/lib/sidekiq.rb +76 -266
  45. data/sidekiq.gemspec +21 -10
  46. data/web/assets/javascripts/application.js +19 -1
  47. data/web/assets/javascripts/base-charts.js +106 -0
  48. data/web/assets/javascripts/chart.min.js +13 -0
  49. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  50. data/web/assets/javascripts/dashboard-charts.js +166 -0
  51. data/web/assets/javascripts/dashboard.js +3 -240
  52. data/web/assets/javascripts/metrics.js +264 -0
  53. data/web/assets/stylesheets/application-dark.css +4 -0
  54. data/web/assets/stylesheets/application-rtl.css +2 -91
  55. data/web/assets/stylesheets/application.css +65 -297
  56. data/web/locales/ar.yml +70 -70
  57. data/web/locales/cs.yml +62 -62
  58. data/web/locales/da.yml +60 -53
  59. data/web/locales/de.yml +65 -65
  60. data/web/locales/el.yml +43 -24
  61. data/web/locales/en.yml +82 -69
  62. data/web/locales/es.yml +68 -68
  63. data/web/locales/fa.yml +65 -65
  64. data/web/locales/fr.yml +67 -67
  65. data/web/locales/gd.yml +99 -0
  66. data/web/locales/he.yml +65 -64
  67. data/web/locales/hi.yml +59 -59
  68. data/web/locales/it.yml +53 -53
  69. data/web/locales/ja.yml +73 -68
  70. data/web/locales/ko.yml +52 -52
  71. data/web/locales/lt.yml +66 -66
  72. data/web/locales/nb.yml +61 -61
  73. data/web/locales/nl.yml +52 -52
  74. data/web/locales/pl.yml +45 -45
  75. data/web/locales/pt-br.yml +59 -69
  76. data/web/locales/pt.yml +51 -51
  77. data/web/locales/ru.yml +67 -66
  78. data/web/locales/sv.yml +53 -53
  79. data/web/locales/ta.yml +60 -60
  80. data/web/locales/uk.yml +62 -61
  81. data/web/locales/ur.yml +64 -64
  82. data/web/locales/vi.yml +67 -67
  83. data/web/locales/zh-cn.yml +43 -16
  84. data/web/locales/zh-tw.yml +42 -8
  85. data/web/views/_footer.erb +5 -2
  86. data/web/views/_job_info.erb +18 -2
  87. data/web/views/_metrics_period_select.erb +12 -0
  88. data/web/views/_nav.erb +1 -1
  89. data/web/views/_paging.erb +2 -0
  90. data/web/views/_poll_link.erb +1 -1
  91. data/web/views/busy.erb +43 -27
  92. data/web/views/dashboard.erb +36 -4
  93. data/web/views/metrics.erb +82 -0
  94. data/web/views/metrics_for_job.erb +68 -0
  95. data/web/views/morgue.erb +5 -9
  96. data/web/views/queue.erb +15 -15
  97. data/web/views/queues.erb +3 -1
  98. data/web/views/retries.erb +5 -9
  99. data/web/views/scheduled.erb +12 -13
  100. metadata +60 -27
  101. data/lib/sidekiq/.DS_Store +0 -0
  102. data/lib/sidekiq/delay.rb +0 -43
  103. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  104. data/lib/sidekiq/extensions/active_record.rb +0 -43
  105. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  106. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  107. data/lib/sidekiq/worker.rb +0 -367
  108. /data/{LICENSE → LICENSE.txt} +0 -0
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "connection_pool"
4
3
  require "redis_client"
5
4
  require "redis_client/decorator"
6
- require "uri"
7
5
 
8
6
  module Sidekiq
9
7
  class RedisClientAdapter
@@ -11,37 +9,20 @@ module Sidekiq
11
9
  CommandError = RedisClient::CommandError
12
10
 
13
11
  module CompatMethods
12
+ # TODO Deprecate and remove this
14
13
  def info
15
14
  @client.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
16
15
  end
17
16
 
17
+ # TODO Deprecate and remove this
18
18
  def evalsha(sha, keys, argv)
19
19
  @client.call("EVALSHA", sha, keys.size, *keys, *argv)
20
20
  end
21
21
 
22
- def brpoplpush(*args)
23
- @client.blocking_call(false, "BRPOPLPUSH", *args)
24
- end
25
-
26
- def brpop(*args)
27
- @client.blocking_call(false, "BRPOP", *args)
28
- end
29
-
30
- def set(*args)
31
- @client.call("SET", *args) { |r| r == "OK" }
32
- end
33
- ruby2_keywords :set if respond_to?(:ruby2_keywords, true)
34
-
35
- def sismember(*args)
36
- @client.call("SISMEMBER", *args) { |c| c > 0 }
37
- end
38
-
39
- def exists?(key)
40
- @client.call("EXISTS", key) { |c| c > 0 }
41
- end
42
-
43
22
  private
44
23
 
24
+ # this allows us to use methods like `conn.hmset(...)` instead of having to use
25
+ # redis-client's native `conn.call("hmset", ...)`
45
26
  def method_missing(*args, &block)
46
27
  @client.call(*args, *block)
47
28
  end
@@ -55,25 +36,8 @@ module Sidekiq
55
36
  CompatClient = RedisClient::Decorator.create(CompatMethods)
56
37
 
57
38
  class CompatClient
58
- %i[scan sscan zscan hscan].each do |method|
59
- alias_method :"#{method}_each", method
60
- undef_method method
61
- end
62
-
63
- def disconnect!
64
- @client.close
65
- end
66
-
67
- def connection
68
- {id: @client.id}
69
- end
70
-
71
- def redis
72
- self
73
- end
74
-
75
- def _client
76
- @client
39
+ def config
40
+ @client.config
77
41
  end
78
42
 
79
43
  def message
@@ -118,9 +82,8 @@ module Sidekiq
118
82
  opts = options.dup
119
83
 
120
84
  if opts[:namespace]
121
- Sidekiq.logger.error("Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature isn't supported by redis-client. " \
122
- "Either use the redis adapter or remove the namespace.")
123
- Kernel.exit(-127)
85
+ raise ArgumentError, "Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature isn't supported by redis-client. " \
86
+ "Either use the redis adapter or remove the namespace."
124
87
  end
125
88
 
126
89
  opts.delete(:size)
@@ -150,5 +113,3 @@ module Sidekiq
150
113
  end
151
114
  end
152
115
  end
153
-
154
- Sidekiq::RedisConnection.adapter = Sidekiq::RedisClientAdapter
@@ -1,130 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "connection_pool"
4
- require "redis"
5
4
  require "uri"
5
+ require "sidekiq/redis_client_adapter"
6
6
 
7
7
  module Sidekiq
8
8
  module RedisConnection
9
- class RedisAdapter
10
- BaseError = Redis::BaseError
11
- CommandError = Redis::CommandError
12
-
13
- def initialize(options)
14
- warn("Usage of the 'redis' gem within Sidekiq itself is deprecated, Sidekiq 7.0 will only use the new, simpler 'redis-client' gem", caller) if ENV["SIDEKIQ_REDIS_CLIENT"] == "1"
15
- @options = options
16
- end
17
-
18
- def new_client
19
- namespace = @options[:namespace]
20
-
21
- client = Redis.new client_opts(@options)
22
- if namespace
23
- begin
24
- require "redis/namespace"
25
- Redis::Namespace.new(namespace, redis: client)
26
- rescue LoadError
27
- Sidekiq.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \
28
- "Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.")
29
- exit(-127)
30
- end
31
- else
32
- client
33
- end
34
- end
35
-
36
- private
37
-
38
- def client_opts(options)
39
- opts = options.dup
40
- if opts[:namespace]
41
- opts.delete(:namespace)
42
- end
43
-
44
- if opts[:network_timeout]
45
- opts[:timeout] = opts[:network_timeout]
46
- opts.delete(:network_timeout)
47
- end
48
-
49
- opts[:driver] ||= Redis::Connection.drivers.last || "ruby"
50
-
51
- # Issue #3303, redis-rb will silently retry an operation.
52
- # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
53
- # is performed twice but I believe this is much, much rarer
54
- # than the reconnect silently fixing a problem; we keep it
55
- # on by default.
56
- opts[:reconnect_attempts] ||= 1
57
-
58
- opts
59
- end
60
- end
61
-
62
- @adapter = RedisAdapter
63
-
64
9
  class << self
65
- attr_reader :adapter
66
-
67
- # RedisConnection.adapter = :redis
68
- # RedisConnection.adapter = :redis_client
69
- def adapter=(adapter)
70
- raise "no" if adapter == self
71
- result = case adapter
72
- when :redis
73
- RedisAdapter
74
- when Class
75
- adapter
76
- else
77
- require "sidekiq/#{adapter}_adapter"
78
- nil
79
- end
80
- @adapter = result if result
81
- end
82
-
83
10
  def create(options = {})
84
11
  symbolized_options = options.transform_keys(&:to_sym)
12
+ symbolized_options[:url] ||= determine_redis_provider
85
13
 
86
- if !symbolized_options[:url] && (u = determine_redis_provider)
87
- symbolized_options[:url] = u
88
- end
89
-
90
- size = if symbolized_options[:size]
91
- symbolized_options[:size]
92
- elsif Sidekiq.server?
93
- # Give ourselves plenty of connections. pool is lazy
94
- # so we won't create them until we need them.
95
- Sidekiq[:concurrency] + 5
96
- elsif ENV["RAILS_MAX_THREADS"]
97
- Integer(ENV["RAILS_MAX_THREADS"])
98
- else
99
- 5
100
- end
101
-
102
- verify_sizing(size, Sidekiq[:concurrency]) if Sidekiq.server?
14
+ logger = symbolized_options.delete(:logger)
15
+ logger&.info { "Sidekiq #{Sidekiq::VERSION} connecting to Redis with options #{scrub(symbolized_options)}" }
103
16
 
104
- pool_timeout = symbolized_options[:pool_timeout] || 1
105
- log_info(symbolized_options)
17
+ size = symbolized_options.delete(:size) || 5
18
+ pool_timeout = symbolized_options.delete(:pool_timeout) || 1
19
+ pool_name = symbolized_options.delete(:pool_name)
106
20
 
107
- redis_config = adapter.new(symbolized_options)
108
- ConnectionPool.new(timeout: pool_timeout, size: size) do
21
+ redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
22
+ ConnectionPool.new(timeout: pool_timeout, size: size, name: pool_name) do
109
23
  redis_config.new_client
110
24
  end
111
25
  end
112
26
 
113
27
  private
114
28
 
115
- # Sidekiq needs many concurrent Redis connections.
116
- #
117
- # We need a connection for each Processor.
118
- # We need a connection for Pro's real-time change listener
119
- # We need a connection to various features to call Redis every few seconds:
120
- # - the process heartbeat.
121
- # - enterprise's leader election
122
- # - enterprise's cron support
123
- def verify_sizing(size, concurrency)
124
- raise ArgumentError, "Your Redis connection pool is too small for Sidekiq. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size < (concurrency + 2)
125
- end
126
-
127
- def log_info(options)
29
+ def scrub(options)
128
30
  redacted = "REDACTED"
129
31
 
130
32
  # Deep clone so we can muck with these options all we want and exclude
@@ -142,11 +44,7 @@ module Sidekiq
142
44
  scrubbed_options[:sentinels]&.each do |sentinel|
143
45
  sentinel[:password] = redacted if sentinel[:password]
144
46
  end
145
- if Sidekiq.server?
146
- Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with #{adapter.name} options #{scrubbed_options}")
147
- else
148
- Sidekiq.logger.debug("#{Sidekiq::NAME} client with #{adapter.name} options #{scrubbed_options}")
149
- end
47
+ scrubbed_options
150
48
  end
151
49
 
152
50
  def determine_redis_provider
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq"
4
- require "sidekiq/api"
5
4
  require "sidekiq/component"
6
5
 
7
6
  module Sidekiq
@@ -9,6 +8,8 @@ module Sidekiq
9
8
  SETS = %w[retry schedule]
10
9
 
11
10
  class Enq
11
+ include Sidekiq::Component
12
+
12
13
  LUA_ZPOPBYSCORE = <<~LUA
13
14
  local key, now = KEYS[1], ARGV[1]
14
15
  local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1)
@@ -18,7 +19,9 @@ module Sidekiq
18
19
  end
19
20
  LUA
20
21
 
21
- def initialize
22
+ def initialize(container)
23
+ @config = container
24
+ @client = Sidekiq::Client.new(config: container)
22
25
  @done = false
23
26
  @lua_zpopbyscore_sha = nil
24
27
  end
@@ -26,15 +29,15 @@ module Sidekiq
26
29
  def enqueue_jobs(sorted_sets = SETS)
27
30
  # A job's "score" in Redis is the time at which it should be processed.
28
31
  # Just check Redis for the set of jobs with a timestamp before now.
29
- Sidekiq.redis do |conn|
32
+ redis do |conn|
30
33
  sorted_sets.each do |sorted_set|
31
34
  # Get next item in the queue with score (time to execute) <= now.
32
35
  # We need to go through the list one at a time to reduce the risk of something
33
36
  # going wrong between the time jobs are popped from the scheduled queue and when
34
37
  # they are pushed onto a work queue and losing the jobs.
35
38
  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}" }
39
+ @client.push(Sidekiq.load_json(job))
40
+ logger.debug { "enqueued #{sorted_set}: #{job}" }
38
41
  end
39
42
  end
40
43
  end
@@ -48,12 +51,11 @@ module Sidekiq
48
51
 
49
52
  def zpopbyscore(conn, keys: nil, argv: nil)
50
53
  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)
54
+ @lua_zpopbyscore_sha = conn.script(:load, LUA_ZPOPBYSCORE)
53
55
  end
54
56
 
55
- conn.evalsha(@lua_zpopbyscore_sha, keys, argv)
56
- rescue RedisConnection.adapter::CommandError => e
57
+ conn.call("EVALSHA", @lua_zpopbyscore_sha, keys.size, *keys, *argv)
58
+ rescue RedisClient::CommandError => e
57
59
  raise unless e.message.start_with?("NOSCRIPT")
58
60
 
59
61
  @lua_zpopbyscore_sha = nil
@@ -71,9 +73,9 @@ module Sidekiq
71
73
 
72
74
  INITIAL_WAIT = 10
73
75
 
74
- def initialize(options)
75
- @config = options
76
- @enq = (options[:scheduled_enq] || Sidekiq::Scheduled::Enq).new
76
+ def initialize(config)
77
+ @config = config
78
+ @enq = (config[:scheduled_enq] || Sidekiq::Scheduled::Enq).new(config)
77
79
  @sleeper = ConnectionPool::TimedStack.new
78
80
  @done = false
79
81
  @thread = nil
@@ -83,14 +85,10 @@ module Sidekiq
83
85
  # Shut down this instance, will pause until the thread is dead.
84
86
  def terminate
85
87
  @done = true
86
- @enq.terminate if @enq.respond_to?(:terminate)
88
+ @enq.terminate
87
89
 
88
- if @thread
89
- t = @thread
90
- @thread = nil
91
- @sleeper << 0
92
- t.value
93
- end
90
+ @sleeper << 0
91
+ @thread&.value
94
92
  end
95
93
 
96
94
  def start
@@ -148,13 +146,16 @@ module Sidekiq
148
146
  # As we run more processes, the scheduling interval average will approach an even spread
149
147
  # between 0 and poll interval so we don't need this artifical boost.
150
148
  #
151
- if process_count < 10
149
+ count = process_count
150
+ interval = poll_interval_average(count)
151
+
152
+ if count < 10
152
153
  # For small clusters, calculate a random interval that is ±50% the desired average.
153
- poll_interval_average * rand + poll_interval_average.to_f / 2
154
+ interval * rand + interval.to_f / 2
154
155
  else
155
156
  # With 10+ processes, we should have enough randomness to get decent polling
156
157
  # across the entire timespan
157
- poll_interval_average * rand
158
+ interval * rand
158
159
  end
159
160
  end
160
161
 
@@ -171,31 +172,52 @@ module Sidekiq
171
172
  # the same time: the thundering herd problem.
172
173
  #
173
174
  # We only do this if poll_interval_average is unset (the default).
174
- def poll_interval_average
175
- @config[:poll_interval_average] ||= scaled_poll_interval
175
+ def poll_interval_average(count)
176
+ @config[:poll_interval_average] || scaled_poll_interval(count)
176
177
  end
177
178
 
178
179
  # Calculates an average poll interval based on the number of known Sidekiq processes.
179
180
  # This minimizes a single point of failure by dispersing check-ins but without taxing
180
181
  # Redis if you run many Sidekiq processes.
181
- def scaled_poll_interval
182
+ def scaled_poll_interval(process_count)
182
183
  process_count * @config[:average_scheduled_poll_interval]
183
184
  end
184
185
 
185
186
  def process_count
186
- # The work buried within Sidekiq::ProcessSet#cleanup can be
187
- # expensive at scale. Cut it down by 90% with this counter.
188
- # NB: This method is only called by the scheduler thread so we
189
- # don't need to worry about the thread safety of +=.
190
- pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
187
+ pcount = Sidekiq.redis { |conn| conn.scard("processes") }
191
188
  pcount = 1 if pcount == 0
192
- @count_calls += 1
193
189
  pcount
194
190
  end
195
191
 
192
+ # A copy of Sidekiq::ProcessSet#cleanup because server
193
+ # should never depend on sidekiq/api.
194
+ def cleanup
195
+ # dont run cleanup more than once per minute
196
+ return 0 unless redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) }
197
+
198
+ count = 0
199
+ redis do |conn|
200
+ procs = conn.sscan("processes").to_a
201
+ heartbeats = conn.pipelined { |pipeline|
202
+ procs.each do |key|
203
+ pipeline.hget(key, "info")
204
+ end
205
+ }
206
+
207
+ # the hash named key has an expiry of 60 seconds.
208
+ # if it's not found, that means the process has not reported
209
+ # in to Redis and probably died.
210
+ to_prune = procs.select.with_index { |proc, i|
211
+ heartbeats[i].nil?
212
+ }
213
+ count = conn.srem("processes", to_prune) unless to_prune.empty?
214
+ end
215
+ count
216
+ end
217
+
196
218
  def initial_wait
197
- # Have all processes sleep between 5-15 seconds. 10 seconds
198
- # to give time for the heartbeat to register (if the poll interval is going to be calculated by the number
219
+ # Have all processes sleep between 5-15 seconds. 10 seconds to give time for
220
+ # the heartbeat to register (if the poll interval is going to be calculated by the number
199
221
  # of workers), and 5 random seconds to ensure they don't all hit Redis at the same time.
200
222
  total = 0
201
223
  total += INITIAL_WAIT unless @config[:poll_interval_average]
@@ -203,6 +225,11 @@ module Sidekiq
203
225
 
204
226
  @sleeper.pop(total)
205
227
  rescue Timeout::Error
228
+ ensure
229
+ # periodically clean out the `processes` set in Redis which can collect
230
+ # references to dead processes over time. The process count affects how
231
+ # often we scan for scheduled jobs.
232
+ cleanup
206
233
  end
207
234
  end
208
235
  end
@@ -51,19 +51,10 @@ module Sidekiq
51
51
  end
52
52
 
53
53
  def server_middleware
54
- @server_chain ||= Middleware::Chain.new
54
+ @server_chain ||= Middleware::Chain.new(Sidekiq.default_configuration)
55
55
  yield @server_chain if block_given?
56
56
  @server_chain
57
57
  end
58
-
59
- def constantize(str)
60
- names = str.split("::")
61
- names.shift if names.empty? || names.first.empty?
62
-
63
- names.inject(Object) do |constant, name|
64
- constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
65
- end
66
- end
67
58
  end
68
59
  end
69
60
 
@@ -83,7 +74,7 @@ module Sidekiq
83
74
  true
84
75
  elsif Sidekiq::Testing.inline?
85
76
  payloads.each do |job|
86
- klass = Sidekiq::Testing.constantize(job["class"])
77
+ klass = Object.const_get(job["class"])
87
78
  job["id"] ||= SecureRandom.hex(12)
88
79
  job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
89
80
  klass.process_job(job_hash)
@@ -218,25 +209,9 @@ module Sidekiq
218
209
  # assert_equal 1, HardJob.jobs.size
219
210
  # assert_equal :something, HardJob.jobs[0]['args'][0]
220
211
  #
221
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
222
- # MyMailer.delay.send_welcome_email('foo@example.com')
223
- # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
224
- #
225
212
  # You can also clear and drain all job types:
226
213
  #
227
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
228
- # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
229
- #
230
- # MyMailer.delay.send_welcome_email('foo@example.com')
231
- # MyModel.delay.do_something_hard
232
- #
233
- # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
234
- # assert_equal 1, Sidekiq::Extensions::DelayedModel.jobs.size
235
- #
236
- # Sidekiq::Worker.clear_all # or .drain_all
237
- #
238
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
239
- # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
214
+ # Sidekiq::Job.clear_all # or .drain_all
240
215
  #
241
216
  # This can be useful to make sure jobs don't linger between tests:
242
217
  #
@@ -318,7 +293,7 @@ module Sidekiq
318
293
  job_classes = jobs.map { |job| job["class"] }.uniq
319
294
 
320
295
  job_classes.each do |job_class|
321
- Sidekiq::Testing.constantize(job_class).drain
296
+ Object.const_get(job_class).drain
322
297
  end
323
298
  end
324
299
  end
@@ -329,13 +304,10 @@ module Sidekiq
329
304
  def jobs_for(klass)
330
305
  jobs.select do |job|
331
306
  marshalled = job["args"][0]
332
- marshalled.index(klass.to_s) && YAML.load(marshalled)[0] == klass
307
+ marshalled.index(klass.to_s) && YAML.safe_load(marshalled)[0] == klass
333
308
  end
334
309
  end
335
310
  end
336
-
337
- Sidekiq::Extensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedMailer)
338
- Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel)
339
311
  end
340
312
 
341
313
  if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
@@ -5,8 +5,8 @@ require "sidekiq/client"
5
5
 
6
6
  module Sidekiq
7
7
  class TransactionAwareClient
8
- def initialize(redis_pool)
9
- @redis_client = Client.new(redis_pool)
8
+ def initialize(pool: nil, config: nil)
9
+ @redis_client = Client.new(pool: pool, config: config)
10
10
  end
11
11
 
12
12
  def push(item)
@@ -34,11 +34,10 @@ module Sidekiq
34
34
  begin
35
35
  require "after_commit_everywhere"
36
36
  rescue LoadError
37
- Sidekiq.logger.error("You need to add after_commit_everywhere to your Gemfile to use Sidekiq's transactional client")
38
- raise
37
+ raise %q(You need to add `gem "after_commit_everywhere"` to your Gemfile to use Sidekiq's transactional client)
39
38
  end
40
39
 
41
- default_job_options["client_class"] = Sidekiq::TransactionAwareClient
40
+ Sidekiq.default_job_options["client_class"] = Sidekiq::TransactionAwareClient
42
41
  Sidekiq::JobUtil::TRANSIENT_ATTRIBUTES << "client_class"
43
42
  true
44
43
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "6.5.1"
4
+ VERSION = "7.0.9"
5
+ MAJOR = 7
5
6
  end
@@ -15,11 +15,11 @@ module Sidekiq
15
15
  end
16
16
 
17
17
  def halt(res)
18
- throw :halt, [res, {"Content-Type" => "text/plain"}, [res.to_s]]
18
+ throw :halt, [res, {"content-type" => "text/plain"}, [res.to_s]]
19
19
  end
20
20
 
21
21
  def redirect(location)
22
- throw :halt, [302, {"Location" => "#{request.base_url}#{location}"}, []]
22
+ throw :halt, [302, {"location" => "#{request.base_url}#{location}"}, []]
23
23
  end
24
24
 
25
25
  def params
@@ -68,7 +68,7 @@ module Sidekiq
68
68
  end
69
69
 
70
70
  def json(payload)
71
- [200, {"Content-Type" => "application/json", "Cache-Control" => "private, no-store"}, [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)
@@ -20,6 +20,12 @@ module Sidekiq
20
20
  "worker-src 'self'",
21
21
  "base-uri 'self'"
22
22
  ].join("; ").freeze
23
+ METRICS_PERIODS = {
24
+ "1h" => 60,
25
+ "2h" => 120,
26
+ "4h" => 240,
27
+ "8h" => 480
28
+ }
23
29
 
24
30
  def initialize(klass)
25
31
  @klass = klass
@@ -60,17 +66,42 @@ module Sidekiq
60
66
  erb(:dashboard)
61
67
  end
62
68
 
69
+ get "/metrics" do
70
+ q = Sidekiq::Metrics::Query.new
71
+ @period = h((params[:period] || "")[0..1])
72
+ @periods = METRICS_PERIODS
73
+ minutes = @periods.fetch(@period, @periods.values.first)
74
+ @query_result = q.top_jobs(minutes: minutes)
75
+ erb(:metrics)
76
+ end
77
+
78
+ get "/metrics/:name" do
79
+ @name = route_params[:name]
80
+ @period = h((params[:period] || "")[0..1])
81
+ q = Sidekiq::Metrics::Query.new
82
+ @periods = METRICS_PERIODS
83
+ minutes = @periods.fetch(@period, @periods.values.first)
84
+ @query_result = q.for_job(@name, minutes: minutes)
85
+ erb(:metrics_for_job)
86
+ end
87
+
63
88
  get "/busy" do
89
+ @count = (params["count"] || 100).to_i
90
+ (@current_page, @total_size, @workset) = page_items(workset, params["page"], @count)
91
+
64
92
  erb(:busy)
65
93
  end
66
94
 
67
95
  post "/busy" do
68
96
  if params["identity"]
69
- p = Sidekiq::Process.new("identity" => params["identity"])
70
- p.quiet! if params["quiet"]
71
- p.stop! if params["stop"]
97
+ pro = Sidekiq::ProcessSet[params["identity"]]
98
+
99
+ pro.quiet! if params["quiet"]
100
+ pro.stop! if params["stop"]
72
101
  else
73
102
  processes.each do |pro|
103
+ next if pro.embedded?
104
+
74
105
  pro.quiet! if params["quiet"]
75
106
  pro.stop! if params["stop"]
76
107
  end
@@ -294,12 +325,12 @@ module Sidekiq
294
325
  end
295
326
 
296
327
  get "/stats/queues" do
297
- json Sidekiq::Stats::Queues.new.lengths
328
+ json Sidekiq::Stats.new.queues
298
329
  end
299
330
 
300
331
  def call(env)
301
332
  action = self.class.match(env)
302
- return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action
333
+ return [404, {"content-type" => "text/plain", "x-cascade" => "pass"}, ["Not Found"]] unless action
303
334
 
304
335
  app = @klass
305
336
  resp = catch(:halt) do
@@ -316,10 +347,10 @@ module Sidekiq
316
347
  else
317
348
  # rendered content goes here
318
349
  headers = {
319
- "Content-Type" => "text/html",
320
- "Cache-Control" => "private, no-store",
321
- "Content-Language" => action.locale,
322
- "Content-Security-Policy" => CSP_HEADER
350
+ "content-type" => "text/html",
351
+ "cache-control" => "private, no-store",
352
+ "content-language" => action.locale,
353
+ "content-security-policy" => CSP_HEADER
323
354
  }
324
355
  # we'll let Rack calculate Content-Length for us.
325
356
  [200, headers, [resp]]
@@ -152,7 +152,7 @@ module Sidekiq
152
152
  # value and decrypt it
153
153
  token_length = masked_token.length / 2
154
154
  one_time_pad = masked_token[0...token_length]
155
- encrypted_token = masked_token[token_length..-1]
155
+ encrypted_token = masked_token[token_length..]
156
156
  xor_byte_strings(one_time_pad, encrypted_token)
157
157
  end
158
158