sidekiq 6.1.0 → 7.0.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 (141) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +256 -7
  3. data/LICENSE.txt +9 -0
  4. data/README.md +21 -16
  5. data/bin/sidekiq +4 -9
  6. data/bin/sidekiqload +71 -76
  7. data/bin/sidekiqmon +1 -1
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +352 -229
  13. data/lib/sidekiq/capsule.rb +110 -0
  14. data/lib/sidekiq/cli.rb +109 -89
  15. data/lib/sidekiq/client.rb +75 -86
  16. data/lib/sidekiq/{util.rb → component.rb} +13 -14
  17. data/lib/sidekiq/config.rb +271 -0
  18. data/lib/sidekiq/deploy.rb +62 -0
  19. data/lib/sidekiq/embedded.rb +61 -0
  20. data/lib/sidekiq/fetch.rb +31 -23
  21. data/lib/sidekiq/{worker.rb → job.rb} +162 -28
  22. data/lib/sidekiq/job_logger.rb +17 -29
  23. data/lib/sidekiq/job_retry.rb +80 -60
  24. data/lib/sidekiq/job_util.rb +71 -0
  25. data/lib/sidekiq/launcher.rb +143 -92
  26. data/lib/sidekiq/logger.rb +11 -45
  27. data/lib/sidekiq/manager.rb +40 -41
  28. data/lib/sidekiq/metrics/query.rb +153 -0
  29. data/lib/sidekiq/metrics/shared.rb +95 -0
  30. data/lib/sidekiq/metrics/tracking.rb +134 -0
  31. data/lib/sidekiq/middleware/chain.rb +90 -46
  32. data/lib/sidekiq/middleware/current_attributes.rb +58 -0
  33. data/lib/sidekiq/middleware/i18n.rb +6 -4
  34. data/lib/sidekiq/middleware/modules.rb +21 -0
  35. data/lib/sidekiq/monitor.rb +1 -1
  36. data/lib/sidekiq/paginator.rb +16 -8
  37. data/lib/sidekiq/processor.rb +56 -59
  38. data/lib/sidekiq/rails.rb +17 -5
  39. data/lib/sidekiq/redis_client_adapter.rb +118 -0
  40. data/lib/sidekiq/redis_connection.rb +17 -88
  41. data/lib/sidekiq/ring_buffer.rb +29 -0
  42. data/lib/sidekiq/scheduled.rb +102 -39
  43. data/lib/sidekiq/testing/inline.rb +4 -4
  44. data/lib/sidekiq/testing.rb +42 -71
  45. data/lib/sidekiq/transaction_aware_client.rb +44 -0
  46. data/lib/sidekiq/version.rb +2 -1
  47. data/lib/sidekiq/web/action.rb +3 -3
  48. data/lib/sidekiq/web/application.rb +42 -17
  49. data/lib/sidekiq/web/csrf_protection.rb +33 -6
  50. data/lib/sidekiq/web/helpers.rb +52 -41
  51. data/lib/sidekiq/web/router.rb +4 -1
  52. data/lib/sidekiq/web.rb +26 -81
  53. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  54. data/lib/sidekiq.rb +86 -201
  55. data/sidekiq.gemspec +38 -6
  56. data/web/assets/images/apple-touch-icon.png +0 -0
  57. data/web/assets/javascripts/application.js +113 -65
  58. data/web/assets/javascripts/base-charts.js +106 -0
  59. data/web/assets/javascripts/chart.min.js +13 -0
  60. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  61. data/web/assets/javascripts/dashboard-charts.js +166 -0
  62. data/web/assets/javascripts/dashboard.js +36 -273
  63. data/web/assets/javascripts/metrics.js +236 -0
  64. data/web/assets/stylesheets/application-dark.css +61 -51
  65. data/web/assets/stylesheets/application-rtl.css +2 -95
  66. data/web/assets/stylesheets/application.css +98 -532
  67. data/web/locales/ar.yml +71 -65
  68. data/web/locales/cs.yml +62 -62
  69. data/web/locales/da.yml +52 -52
  70. data/web/locales/de.yml +65 -65
  71. data/web/locales/el.yml +43 -24
  72. data/web/locales/en.yml +83 -67
  73. data/web/locales/es.yml +70 -54
  74. data/web/locales/fa.yml +65 -65
  75. data/web/locales/fr.yml +69 -62
  76. data/web/locales/he.yml +65 -64
  77. data/web/locales/hi.yml +59 -59
  78. data/web/locales/it.yml +53 -53
  79. data/web/locales/ja.yml +72 -66
  80. data/web/locales/ko.yml +52 -52
  81. data/web/locales/lt.yml +66 -66
  82. data/web/locales/nb.yml +61 -61
  83. data/web/locales/nl.yml +52 -52
  84. data/web/locales/pl.yml +45 -45
  85. data/web/locales/pt-br.yml +63 -55
  86. data/web/locales/pt.yml +51 -51
  87. data/web/locales/ru.yml +68 -63
  88. data/web/locales/sv.yml +53 -53
  89. data/web/locales/ta.yml +60 -60
  90. data/web/locales/uk.yml +62 -61
  91. data/web/locales/ur.yml +64 -64
  92. data/web/locales/vi.yml +67 -67
  93. data/web/locales/zh-cn.yml +37 -11
  94. data/web/locales/zh-tw.yml +42 -8
  95. data/web/views/_footer.erb +6 -3
  96. data/web/views/_job_info.erb +1 -1
  97. data/web/views/_nav.erb +1 -1
  98. data/web/views/_poll_link.erb +2 -5
  99. data/web/views/_summary.erb +7 -7
  100. data/web/views/busy.erb +57 -21
  101. data/web/views/dashboard.erb +58 -18
  102. data/web/views/dead.erb +1 -1
  103. data/web/views/layout.erb +2 -1
  104. data/web/views/metrics.erb +80 -0
  105. data/web/views/metrics_for_job.erb +69 -0
  106. data/web/views/morgue.erb +6 -6
  107. data/web/views/queue.erb +15 -11
  108. data/web/views/queues.erb +4 -4
  109. data/web/views/retries.erb +7 -7
  110. data/web/views/retry.erb +1 -1
  111. data/web/views/scheduled.erb +1 -1
  112. metadata +87 -52
  113. data/.circleci/config.yml +0 -71
  114. data/.github/contributing.md +0 -32
  115. data/.github/issue_template.md +0 -11
  116. data/.gitignore +0 -13
  117. data/.standard.yml +0 -20
  118. data/3.0-Upgrade.md +0 -70
  119. data/4.0-Upgrade.md +0 -53
  120. data/5.0-Upgrade.md +0 -56
  121. data/6.0-Upgrade.md +0 -72
  122. data/COMM-LICENSE +0 -97
  123. data/Ent-2.0-Upgrade.md +0 -37
  124. data/Ent-Changes.md +0 -269
  125. data/Gemfile +0 -24
  126. data/Gemfile.lock +0 -208
  127. data/LICENSE +0 -9
  128. data/Pro-2.0-Upgrade.md +0 -138
  129. data/Pro-3.0-Upgrade.md +0 -44
  130. data/Pro-4.0-Upgrade.md +0 -35
  131. data/Pro-5.0-Upgrade.md +0 -25
  132. data/Pro-Changes.md +0 -790
  133. data/Rakefile +0 -10
  134. data/code_of_conduct.md +0 -50
  135. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  136. data/lib/sidekiq/delay.rb +0 -41
  137. data/lib/sidekiq/exception_handler.rb +0 -27
  138. data/lib/sidekiq/extensions/action_mailer.rb +0 -47
  139. data/lib/sidekiq/extensions/active_record.rb +0 -43
  140. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  141. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
@@ -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)
@@ -101,20 +92,20 @@ module Sidekiq
101
92
  ##
102
93
  # The Queues class is only for testing the fake queue implementation.
103
94
  # There are 2 data structures involved in tandem. This is due to the
104
- # Rspec syntax of change(QueueWorker.jobs, :size). It keeps a reference
95
+ # Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
105
96
  # to the array. Because the array was dervied from a filter of the total
106
97
  # jobs enqueued, it appeared as though the array didn't change.
107
98
  #
108
99
  # To solve this, we'll keep 2 hashes containing the jobs. One with keys based
109
- # on the queue, and another with keys of the worker names, so the array for
110
- # QueueWorker.jobs is a straight reference to a real array.
100
+ # on the queue, and another with keys of the job type, so the array for
101
+ # HardJob.jobs is a straight reference to a real array.
111
102
  #
112
103
  # Queue-based hash:
113
104
  #
114
105
  # {
115
106
  # "default"=>[
116
107
  # {
117
- # "class"=>"TestTesting::QueueWorker",
108
+ # "class"=>"TestTesting::HardJob",
118
109
  # "args"=>[1, 2],
119
110
  # "retry"=>true,
120
111
  # "queue"=>"default",
@@ -124,12 +115,12 @@ module Sidekiq
124
115
  # ]
125
116
  # }
126
117
  #
127
- # Worker-based hash:
118
+ # Job-based hash:
128
119
  #
129
120
  # {
130
- # "TestTesting::QueueWorker"=>[
121
+ # "TestTesting::HardJob"=>[
131
122
  # {
132
- # "class"=>"TestTesting::QueueWorker",
123
+ # "class"=>"TestTesting::HardJob",
133
124
  # "args"=>[1, 2],
134
125
  # "retry"=>true,
135
126
  # "queue"=>"default",
@@ -144,14 +135,14 @@ module Sidekiq
144
135
  # require 'sidekiq/testing'
145
136
  #
146
137
  # assert_equal 0, Sidekiq::Queues["default"].size
147
- # HardWorker.perform_async(:something)
138
+ # HardJob.perform_async(:something)
148
139
  # assert_equal 1, Sidekiq::Queues["default"].size
149
140
  # assert_equal :something, Sidekiq::Queues["default"].first['args'][0]
150
141
  #
151
- # You can also clear all workers' jobs:
142
+ # You can also clear all jobs:
152
143
  #
153
144
  # assert_equal 0, Sidekiq::Queues["default"].size
154
- # HardWorker.perform_async(:something)
145
+ # HardJob.perform_async(:something)
155
146
  # Sidekiq::Queues.clear_all
156
147
  # assert_equal 0, Sidekiq::Queues["default"].size
157
148
  #
@@ -170,35 +161,36 @@ module Sidekiq
170
161
 
171
162
  def push(queue, klass, job)
172
163
  jobs_by_queue[queue] << job
173
- jobs_by_worker[klass] << job
164
+ jobs_by_class[klass] << job
174
165
  end
175
166
 
176
167
  def jobs_by_queue
177
168
  @jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] }
178
169
  end
179
170
 
180
- def jobs_by_worker
181
- @jobs_by_worker ||= Hash.new { |hash, key| hash[key] = [] }
171
+ def jobs_by_class
172
+ @jobs_by_class ||= Hash.new { |hash, key| hash[key] = [] }
182
173
  end
174
+ alias_method :jobs_by_worker, :jobs_by_class
183
175
 
184
176
  def delete_for(jid, queue, klass)
185
177
  jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid }
186
- jobs_by_worker[klass].delete_if { |job| job["jid"] == jid }
178
+ jobs_by_class[klass].delete_if { |job| job["jid"] == jid }
187
179
  end
188
180
 
189
181
  def clear_for(queue, klass)
190
- jobs_by_queue[queue].clear
191
- jobs_by_worker[klass].clear
182
+ jobs_by_queue[queue.to_s].clear
183
+ jobs_by_class[klass].clear
192
184
  end
193
185
 
194
186
  def clear_all
195
187
  jobs_by_queue.clear
196
- jobs_by_worker.clear
188
+ jobs_by_class.clear
197
189
  end
198
190
  end
199
191
  end
200
192
 
201
- module Worker
193
+ module Job
202
194
  ##
203
195
  # The Sidekiq testing infrastructure overrides perform_async
204
196
  # so that it does not actually touch the network. Instead it
@@ -212,43 +204,27 @@ module Sidekiq
212
204
  #
213
205
  # require 'sidekiq/testing'
214
206
  #
215
- # assert_equal 0, HardWorker.jobs.size
216
- # HardWorker.perform_async(:something)
217
- # assert_equal 1, HardWorker.jobs.size
218
- # assert_equal :something, HardWorker.jobs[0]['args'][0]
219
- #
220
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
221
- # MyMailer.delay.send_welcome_email('foo@example.com')
222
- # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
207
+ # assert_equal 0, HardJob.jobs.size
208
+ # HardJob.perform_async(:something)
209
+ # assert_equal 1, HardJob.jobs.size
210
+ # assert_equal :something, HardJob.jobs[0]['args'][0]
223
211
  #
224
- # You can also clear and drain all workers' jobs:
212
+ # You can also clear and drain all job types:
225
213
  #
226
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
227
- # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
228
- #
229
- # MyMailer.delay.send_welcome_email('foo@example.com')
230
- # MyModel.delay.do_something_hard
231
- #
232
- # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
233
- # assert_equal 1, Sidekiq::Extensions::DelayedModel.jobs.size
234
- #
235
- # Sidekiq::Worker.clear_all # or .drain_all
236
- #
237
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
238
- # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
214
+ # Sidekiq::Job.clear_all # or .drain_all
239
215
  #
240
216
  # This can be useful to make sure jobs don't linger between tests:
241
217
  #
242
218
  # RSpec.configure do |config|
243
219
  # config.before(:each) do
244
- # Sidekiq::Worker.clear_all
220
+ # Sidekiq::Job.clear_all
245
221
  # end
246
222
  # end
247
223
  #
248
224
  # or for acceptance testing, i.e. with cucumber:
249
225
  #
250
226
  # AfterStep do
251
- # Sidekiq::Worker.drain_all
227
+ # Sidekiq::Job.drain_all
252
228
  # end
253
229
  #
254
230
  # When I sign up as "foo@example.com"
@@ -262,7 +238,7 @@ module Sidekiq
262
238
 
263
239
  # Jobs queued for this worker
264
240
  def jobs
265
- Queues.jobs_by_worker[to_s]
241
+ Queues.jobs_by_class[to_s]
266
242
  end
267
243
 
268
244
  # Clear all jobs for this worker
@@ -288,11 +264,11 @@ module Sidekiq
288
264
  end
289
265
 
290
266
  def process_job(job)
291
- worker = new
292
- worker.jid = job["jid"]
293
- worker.bid = job["bid"] if worker.respond_to?(:bid=)
294
- Sidekiq::Testing.server_middleware.invoke(worker, job, job["queue"]) do
295
- execute_job(worker, job["args"])
267
+ inst = new
268
+ inst.jid = job["jid"]
269
+ inst.bid = job["bid"] if inst.respond_to?(:bid=)
270
+ Sidekiq::Testing.server_middleware.invoke(inst, job, job["queue"]) do
271
+ execute_job(inst, job["args"])
296
272
  end
297
273
  end
298
274
 
@@ -306,18 +282,18 @@ module Sidekiq
306
282
  Queues.jobs_by_queue.values.flatten
307
283
  end
308
284
 
309
- # Clear all queued jobs across all workers
285
+ # Clear all queued jobs
310
286
  def clear_all
311
287
  Queues.clear_all
312
288
  end
313
289
 
314
- # Drain all queued jobs across all workers
290
+ # Drain (execute) all queued jobs
315
291
  def drain_all
316
292
  while jobs.any?
317
- worker_classes = jobs.map { |job| job["class"] }.uniq
293
+ job_classes = jobs.map { |job| job["class"] }.uniq
318
294
 
319
- worker_classes.each do |worker_class|
320
- Sidekiq::Testing.constantize(worker_class).drain
295
+ job_classes.each do |job_class|
296
+ Object.const_get(job_class).drain
321
297
  end
322
298
  end
323
299
  end
@@ -328,17 +304,12 @@ module Sidekiq
328
304
  def jobs_for(klass)
329
305
  jobs.select do |job|
330
306
  marshalled = job["args"][0]
331
- marshalled.index(klass.to_s) && YAML.load(marshalled)[0] == klass
307
+ marshalled.index(klass.to_s) && YAML.safe_load(marshalled)[0] == klass
332
308
  end
333
309
  end
334
310
  end
335
-
336
- Sidekiq::Extensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedMailer)
337
- Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel)
338
311
  end
339
312
 
340
313
  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("**************************************************")
314
+ warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
344
315
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "sidekiq/client"
5
+
6
+ module Sidekiq
7
+ class TransactionAwareClient
8
+ def initialize(pool: nil, config: nil)
9
+ @redis_client = Client.new(pool: pool, config: config)
10
+ end
11
+
12
+ def push(item)
13
+ # pre-allocate the JID so we can return it immediately and
14
+ # save it to the database as part of the transaction.
15
+ item["jid"] ||= SecureRandom.hex(12)
16
+ AfterCommitEverywhere.after_commit { @redis_client.push(item) }
17
+ item["jid"]
18
+ end
19
+
20
+ ##
21
+ # We don't provide transactionality for push_bulk because we don't want
22
+ # to hold potentially hundreds of thousands of job records in memory due to
23
+ # a long running enqueue process.
24
+ def push_bulk(items)
25
+ @redis_client.push_bulk(items)
26
+ end
27
+ end
28
+ end
29
+
30
+ ##
31
+ # Use `Sidekiq.transactional_push!` in your sidekiq.rb initializer
32
+ module Sidekiq
33
+ def self.transactional_push!
34
+ begin
35
+ require "after_commit_everywhere"
36
+ rescue LoadError
37
+ raise %q(You need to add `gem "after_commit_everywhere"` to your Gemfile to use Sidekiq's transactional client)
38
+ end
39
+
40
+ Sidekiq.default_job_options["client_class"] = Sidekiq::TransactionAwareClient
41
+ Sidekiq::JobUtil::TRANSIENT_ATTRIBUTES << "client_class"
42
+ true
43
+ end
44
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "6.1.0"
4
+ VERSION = "7.0.0"
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
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" => "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)
@@ -4,7 +4,6 @@ module Sidekiq
4
4
  class WebApplication
5
5
  extend WebRouter
6
6
 
7
- CONTENT_LENGTH = "Content-Length"
8
7
  REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
9
8
  CSP_HEADER = [
10
9
  "default-src 'self' https: http:",
@@ -42,16 +41,42 @@ module Sidekiq
42
41
  # nothing, backwards compatibility
43
42
  end
44
43
 
44
+ head "/" do
45
+ # HEAD / is the cheapest heartbeat possible,
46
+ # it hits Redis to ensure connectivity
47
+ Sidekiq.redis { |c| c.llen("queue:default") }
48
+ ""
49
+ end
50
+
45
51
  get "/" do
46
52
  @redis_info = redis_info.select { |k, v| REDIS_KEYS.include? k }
47
- 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)
48
57
  @processed_history = stats_history.processed
49
58
  @failed_history = stats_history.failed
50
59
 
51
60
  erb(:dashboard)
52
61
  end
53
62
 
63
+ get "/metrics" do
64
+ q = Sidekiq::Metrics::Query.new
65
+ @query_result = q.top_jobs
66
+ erb(:metrics)
67
+ end
68
+
69
+ get "/metrics/:name" do
70
+ @name = route_params[:name]
71
+ q = Sidekiq::Metrics::Query.new
72
+ @query_result = q.for_job(@name)
73
+ erb(:metrics_for_job)
74
+ end
75
+
54
76
  get "/busy" do
77
+ @count = (params["count"] || 100).to_i
78
+ (@current_page, @total_size, @workset) = page_items(workset, params["page"], @count)
79
+
55
80
  erb(:busy)
56
81
  end
57
82
 
@@ -76,15 +101,17 @@ module Sidekiq
76
101
  erb(:queues)
77
102
  end
78
103
 
104
+ QUEUE_NAME = /\A[a-z_:.\-0-9]+\z/i
105
+
79
106
  get "/queues/:name" do
80
107
  @name = route_params[:name]
81
108
 
82
- halt(404) unless @name
109
+ halt(404) if !@name || @name !~ QUEUE_NAME
83
110
 
84
111
  @count = (params["count"] || 25).to_i
85
112
  @queue = Sidekiq::Queue.new(@name)
86
- (@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
87
- @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
113
+ (@current_page, @total_size, @jobs) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
114
+ @jobs = @jobs.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
88
115
 
89
116
  erb(:queue)
90
117
  end
@@ -105,7 +132,7 @@ module Sidekiq
105
132
 
106
133
  post "/queues/:name/delete" do
107
134
  name = route_params[:name]
108
- Sidekiq::Job.new(params["key_val"], name).delete
135
+ Sidekiq::JobRecord.new(params["key_val"], name).delete
109
136
 
110
137
  redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
111
138
  end
@@ -283,42 +310,40 @@ module Sidekiq
283
310
  end
284
311
 
285
312
  get "/stats/queues" do
286
- json Sidekiq::Stats::Queues.new.lengths
313
+ json Sidekiq::Stats.new.queues
287
314
  end
288
315
 
289
316
  def call(env)
290
317
  action = self.class.match(env)
291
- return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action
318
+ return [404, {"content-type" => "text/plain", "x-cascade" => "pass"}, ["Not Found"]] unless action
292
319
 
293
320
  app = @klass
294
- resp = catch(:halt) do # rubocop:disable Standard/SemanticBlocks
321
+ resp = catch(:halt) do
295
322
  self.class.run_befores(app, action)
296
323
  action.instance_exec env, &action.block
297
324
  ensure
298
325
  self.class.run_afters(app, action)
299
326
  end
300
327
 
301
- resp = case resp
328
+ case resp
302
329
  when Array
303
330
  # redirects go here
304
331
  resp
305
332
  else
306
333
  # rendered content goes here
307
334
  headers = {
308
- "Content-Type" => "text/html",
309
- "Cache-Control" => "no-cache",
310
- "Content-Language" => action.locale,
311
- "Content-Security-Policy" => CSP_HEADER
335
+ "content-type" => "text/html",
336
+ "cache-control" => "private, no-store",
337
+ "content-language" => action.locale,
338
+ "content-security-policy" => CSP_HEADER
312
339
  }
313
340
  # we'll let Rack calculate Content-Length for us.
314
341
  [200, headers, [resp]]
315
342
  end
316
-
317
- resp
318
343
  end
319
344
 
320
345
  def self.helpers(mod = nil, &block)
321
- if block_given?
346
+ if block
322
347
  WebAction.class_eval(&block)
323
348
  else
324
349
  WebAction.send(:include, mod)
@@ -66,13 +66,37 @@ module Sidekiq
66
66
  end
67
67
 
68
68
  def session(env)
69
- env["rack.session"] || fail("you need to set up a session middleware *before* #{self.class}")
69
+ env["rack.session"] || fail(<<~EOM)
70
+ Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app,
71
+ make sure you mount Sidekiq::Web *inside* your application routes:
72
+
73
+
74
+ Rails.application.routes.draw do
75
+ mount Sidekiq::Web => "/sidekiq"
76
+ ....
77
+ end
78
+
79
+
80
+ If this is a Rails app in API mode, you need to enable sessions.
81
+
82
+ https://guides.rubyonrails.org/api_app.html#using-session-middlewares
83
+
84
+ If this is a bare Rack app, use a session middleware before Sidekiq::Web:
85
+
86
+ # first, use IRB to create a shared secret key for sessions and commit it
87
+ require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
88
+
89
+ # now use the secret with a session cookie middleware
90
+ use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
91
+ run Sidekiq::Web
92
+
93
+ EOM
70
94
  end
71
95
 
72
96
  def accept?(env)
73
97
  return true if safe?(env)
74
98
 
75
- giventoken = Rack::Request.new(env).params["authenticity_token"]
99
+ giventoken = ::Rack::Request.new(env).params["authenticity_token"]
76
100
  valid_token?(env, giventoken)
77
101
  end
78
102
 
@@ -92,6 +116,9 @@ module Sidekiq
92
116
  sess = session(env)
93
117
  localtoken = sess[:csrf]
94
118
 
119
+ # Checks that Rack::Session::Cookie actualy contains the csrf toekn
120
+ return false if localtoken.nil?
121
+
95
122
  # Rotate the session token after every use
96
123
  sess[:csrf] = SecureRandom.base64(TOKEN_LENGTH)
97
124
 
@@ -116,7 +143,7 @@ module Sidekiq
116
143
  one_time_pad = SecureRandom.random_bytes(token.length)
117
144
  encrypted_token = xor_byte_strings(one_time_pad, token)
118
145
  masked_token = one_time_pad + encrypted_token
119
- Base64.strict_encode64(masked_token)
146
+ Base64.urlsafe_encode64(masked_token)
120
147
  end
121
148
 
122
149
  # Essentially the inverse of +mask_token+.
@@ -125,7 +152,7 @@ module Sidekiq
125
152
  # value and decrypt it
126
153
  token_length = masked_token.length / 2
127
154
  one_time_pad = masked_token[0...token_length]
128
- encrypted_token = masked_token[token_length..-1]
155
+ encrypted_token = masked_token[token_length..]
129
156
  xor_byte_strings(one_time_pad, encrypted_token)
130
157
  end
131
158
 
@@ -138,11 +165,11 @@ module Sidekiq
138
165
  end
139
166
 
140
167
  def compare_with_real_token(token, local)
141
- Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s)
168
+ ::Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s)
142
169
  end
143
170
 
144
171
  def decode_token(token)
145
- Base64.strict_decode64(token)
172
+ Base64.urlsafe_decode64(token)
146
173
  end
147
174
 
148
175
  def xor_byte_strings(s1, s2)