sidekiq 8.0.9 → 8.1.0

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +29 -0
  3. data/README.md +15 -0
  4. data/bin/lint-herb +13 -0
  5. data/lib/sidekiq/api.rb +17 -7
  6. data/lib/sidekiq/cli.rb +16 -4
  7. data/lib/sidekiq/config.rb +4 -4
  8. data/lib/sidekiq/job.rb +2 -0
  9. data/lib/sidekiq/job_retry.rb +7 -3
  10. data/lib/sidekiq/launcher.rb +8 -8
  11. data/lib/sidekiq/redis_connection.rb +2 -2
  12. data/lib/sidekiq/scheduled.rb +7 -5
  13. data/lib/sidekiq/version.rb +1 -1
  14. data/lib/sidekiq/web/action.rb +1 -1
  15. data/lib/sidekiq/web/config.rb +3 -6
  16. data/lib/sidekiq/web/helpers.rb +3 -3
  17. data/lib/sidekiq/web.rb +23 -4
  18. data/sidekiq.gemspec +5 -5
  19. data/web/assets/javascripts/application.js +1 -1
  20. data/web/assets/stylesheets/style.css +0 -2
  21. data/web/locales/fr.yml +1 -1
  22. data/web/views/{filtering.erb → filtering.html.erb} +2 -2
  23. data/web/views/{metrics.erb → metrics.html.erb} +3 -2
  24. data/web/views/{morgue.erb → morgue.html.erb} +1 -1
  25. data/web/views/{retries.erb → retries.html.erb} +1 -1
  26. data/web/views/{scheduled.erb → scheduled.html.erb} +1 -1
  27. metadata +34 -34
  28. data/lib/sidekiq/web/csrf_protection.rb +0 -183
  29. /data/web/views/{_footer.erb → _footer.html.erb} +0 -0
  30. /data/web/views/{_job_info.erb → _job_info.html.erb} +0 -0
  31. /data/web/views/{_metrics_period_select.erb → _metrics_period_select.html.erb} +0 -0
  32. /data/web/views/{_nav.erb → _nav.html.erb} +0 -0
  33. /data/web/views/{_paging.erb → _paging.html.erb} +0 -0
  34. /data/web/views/{_poll_link.erb → _poll_link.html.erb} +0 -0
  35. /data/web/views/{_summary.erb → _summary.html.erb} +0 -0
  36. /data/web/views/{busy.erb → busy.html.erb} +0 -0
  37. /data/web/views/{dashboard.erb → dashboard.html.erb} +0 -0
  38. /data/web/views/{dead.erb → dead.html.erb} +0 -0
  39. /data/web/views/{layout.erb → layout.html.erb} +0 -0
  40. /data/web/views/{metrics_for_job.erb → metrics_for_job.html.erb} +0 -0
  41. /data/web/views/{profiles.erb → profiles.html.erb} +0 -0
  42. /data/web/views/{queue.erb → queue.html.erb} +0 -0
  43. /data/web/views/{queues.erb → queues.html.erb} +0 -0
  44. /data/web/views/{retry.erb → retry.html.erb} +0 -0
  45. /data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef51643693879cd574d6edd54ef41443defc4825c855d5eed0e07d42e35aa1cd
4
- data.tar.gz: 2d9acb8d89326185356ee9818ca543e8a6992bac0a00262e06aa9394052978c1
3
+ metadata.gz: 63658318932ac6b7045211590b07f84ed9e1e6398f908ace790a0df20d5ac1f0
4
+ data.tar.gz: 0c3f5e69ada7529bdac7392acb1c56ef2a19b8ed13210be8c36aac19858c6df4
5
5
  SHA512:
6
- metadata.gz: 761f705abeb6b5deb31591d07d9d9ea0d79d5d6f74367f622842994aa0a57866bd3af9add1089ce2b29d8dd4a9cd110d839036504a1ae0830ac59dbca186ff26
7
- data.tar.gz: 5df2e3b47cb7ed56a51ff90b1a8d23ea96f74e9fd64a9116d249a266c7a074ef2a4bac09d66202c66a4f631c6f15bcae167df8ae733f43020f0b53886ae3e564
6
+ metadata.gz: 9a5e95a2faccbbf42f8e651c4882c9495ad72b0500bb8fe77fa2a93fb4aa6f8cb96fc820621cd1910fb4b00accf35d75a99d548b3cc8ccdd41a18b5620a9ad73
7
+ data.tar.gz: 1bc66490ee07c548c71f680fb280e876efa7eeb8c6f234277df73211ce05c5ae29f5c9bc13064972da945568d1df6634252a6c22a7c5298bdd436b7266246c53
data/Changes.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  [Sidekiq Changes](https://github.com/sidekiq/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/sidekiq/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/sidekiq/sidekiq/blob/main/Ent-Changes.md)
4
4
 
5
+ 8.1.0
6
+ ----------
7
+
8
+ - `retry_for` and `retry` are now mutually exclusive [#6878, Saidbek]
9
+ - `perform_inline` now enforces `strict_args!` [#6718, Saidbek]
10
+ - Integrate Herb linting for ERB templates [#6760, Saidbek]
11
+ - Remove CSRF code, use `Sec-Fetch-Site` header [#6874, deve1212]
12
+ - Allow custom Web UI `assets_path` for CDN purposes [#6865, stanhu]
13
+ - Upgrade to connection_pool 3.0
14
+ - Allow idle connection reaping after N seconds.
15
+ You can activate this **beta** feature like below.
16
+ Feedback requested: is this feature stable and useful for you in production?
17
+ This feature may or may not be enabled by default in Sidekiq 9.0.
18
+ ```ruby
19
+ Sidekiq.configure_server do |cfg|
20
+ cfg.reap_idle_redis_connections(60)
21
+ end
22
+ ```
23
+
24
+ 8.0.10
25
+ ----------
26
+
27
+ - Add confirm dialog for Delete All buttons in Web UI [#6853]
28
+ - Adjust scheduler to run closer to poll average [#6866]
29
+ - Forward compatibility changes for connection_pool 3.0.0
30
+ - Backwards compatibility fix for <8.0.9 process data in Redis [#6870]
31
+ - Backtrace dump can now be triggered with the INFO signal, since Puma uses the
32
+ same signal [#6857]
33
+
5
34
  8.0.9
6
35
  ----------
7
36
 
data/README.md CHANGED
@@ -97,6 +97,21 @@ Contributing
97
97
 
98
98
  See [the contributing guidelines](https://github.com/sidekiq/sidekiq/blob/main/.github/contributing.md).
99
99
 
100
+ ### ERB Linting with HERB
101
+
102
+ This project uses [HERB](https://herb-tools.dev/) for ERB file linting and formatting. All ERB files have been renamed to use the `.html.erb` extension for better tooling support.
103
+
104
+ **Local Development:**
105
+ ```bash
106
+ # Run HERB linting
107
+ bundle exec rake lint:herb
108
+ # or
109
+ bin/lint-herb
110
+ ```
111
+
112
+ **CI Integration:**
113
+ HERB linting is automatically run in CI to ensure all ERB files are properly formatted and free of parse errors.
114
+
100
115
  License
101
116
  -----------------
102
117
 
data/bin/lint-herb ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # HERB Linting Script
5
+ # Run this script to lint all ERB files in the project
6
+ # Usage: bin/lint-herb
7
+
8
+ require "bundler/setup"
9
+
10
+ puts "🔍 Running HERB linting on ERB files..."
11
+ puts
12
+
13
+ exec("bundle exec herb analyze web/views -n --no-log-file")
data/lib/sidekiq/api.rb CHANGED
@@ -1091,19 +1091,29 @@ module Sidekiq
1091
1091
 
1092
1092
  # deprecated, use capsules below
1093
1093
  def queues
1094
- capsules.values.flat_map { |x| x["weights"].keys }.uniq
1094
+ # Backwards compatibility with <8.0.8
1095
+ if !self["capsules"]
1096
+ self["queues"]
1097
+ else
1098
+ capsules.values.flat_map { |x| x["weights"].keys }.uniq
1099
+ end
1095
1100
  end
1096
1101
 
1097
1102
  # deprecated, use capsules below
1098
1103
  def weights
1099
- hash = {}
1100
- capsules.values.each do |cap|
1101
- # Note: will lose data if two capsules are processing the same named queue
1102
- cap["weights"].each_pair do |queue, weight|
1103
- hash[queue] = weight
1104
+ # Backwards compatibility with <8.0.8
1105
+ if !self["capsules"]
1106
+ self["weights"]
1107
+ else
1108
+ hash = {}
1109
+ capsules.values.each do |cap|
1110
+ # Note: will lose data if two capsules are processing the same named queue
1111
+ cap["weights"].each_pair do |queue, weight|
1112
+ hash[queue] = weight
1113
+ end
1104
1114
  end
1115
+ hash
1105
1116
  end
1106
- hash
1107
1117
  end
1108
1118
 
1109
1119
  def capsules
data/lib/sidekiq/cli.rb CHANGED
@@ -49,7 +49,7 @@ module Sidekiq # :nodoc:
49
49
  logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app?
50
50
 
51
51
  self_read, self_write = IO.pipe
52
- sigs = %w[INT TERM TTIN TSTP]
52
+ sigs = %w[INT TERM INFO TTIN TSTP]
53
53
  # USR1 and USR2 don't work on the JVM
54
54
  sigs << "USR2" if Sidekiq.pro? && !jruby?
55
55
  sigs.each do |sig|
@@ -201,7 +201,19 @@ module Sidekiq # :nodoc:
201
201
  cli.logger.info "Received TSTP, no longer accepting new work"
202
202
  cli.launcher.quiet
203
203
  },
204
+ # deprecated, use INFO
204
205
  "TTIN" => ->(cli) {
206
+ cli.logger.error { "DEPRECATED: Please use the INFO signal for backtraces, support for TTIN will be removed in Sidekiq 9.0." }
207
+ Thread.list.each do |thread|
208
+ cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
209
+ if thread.backtrace
210
+ cli.logger.warn thread.backtrace.join("\n")
211
+ else
212
+ cli.logger.warn "<no backtrace available>"
213
+ end
214
+ end
215
+ },
216
+ "INFO" => ->(cli) {
205
217
  Thread.list.each do |thread|
206
218
  cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
207
219
  if thread.backtrace
@@ -212,12 +224,12 @@ module Sidekiq # :nodoc:
212
224
  end
213
225
  }
214
226
  }
215
- UNHANDLED_SIGNAL_HANDLER = ->(cli) { cli.logger.info "No signal handler registered, ignoring" }
216
- SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER
217
227
 
218
228
  def handle_signal(sig)
219
229
  logger.debug "Got #{sig} signal"
220
- SIGNAL_HANDLERS[sig].call(self)
230
+ hndlr = SIGNAL_HANDLERS[sig]
231
+ hndlr ? hndlr.call(self) :
232
+ logger.warn("No #{sig} signal handler registered, ignoring")
221
233
  end
222
234
 
223
235
  private
@@ -36,7 +36,8 @@ module Sidekiq
36
36
  dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months
37
37
  reloader: proc { |&block| block.call },
38
38
  backtrace_cleaner: ->(backtrace) { backtrace },
39
- logged_job_attributes: ["bid", "tags"]
39
+ logged_job_attributes: ["bid", "tags"],
40
+ redis_idle_timeout: nil
40
41
  }
41
42
 
42
43
  ERROR_HANDLER = ->(ex, ctx, cfg = Sidekiq.default_configuration) {
@@ -145,10 +146,9 @@ module Sidekiq
145
146
  @redis_config = @redis_config.merge(hash)
146
147
  end
147
148
 
148
- def reap_idle_redis_connections(timeout = nil)
149
- self[:reap_connections] = timeout
149
+ def reap_idle_redis_connections(timeout = 60)
150
+ self[:redis_idle_timeout] = timeout
150
151
  end
151
- alias_method :reap, :reap_idle_redis_connections
152
152
 
153
153
  def redis_pool
154
154
  Thread.current[:sidekiq_redis_pool] || Thread.current[:sidekiq_capsule]&.redis_pool || local_redis_pool
data/lib/sidekiq/job.rb CHANGED
@@ -226,6 +226,8 @@ module Sidekiq
226
226
  end
227
227
  return nil unless result
228
228
 
229
+ verify_json(item)
230
+
229
231
  # round-trip the payload via JSON
230
232
  msg = Sidekiq.load_json(Sidekiq.dump_json(item))
231
233
 
@@ -178,10 +178,14 @@ module Sidekiq
178
178
  msg["error_backtrace"] = compress_backtrace(lines)
179
179
  end
180
180
 
181
- return retries_exhausted(jobinst, msg, exception) if count >= max_retry_attempts
182
-
181
+ # retry_for and retry are mutually exclusive - if retry_for is set,
182
+ # we exclusively use duration-based retry logic and ignore count-based logic
183
183
  rf = msg["retry_for"]
184
- return retries_exhausted(jobinst, msg, exception) if rf && (time_for(msg["failed_at"]) + rf) < Time.now
184
+ if rf
185
+ return retries_exhausted(jobinst, msg, exception) if (time_for(msg["failed_at"]) + rf) < Time.now
186
+ elsif count >= max_retry_attempts
187
+ return retries_exhausted(jobinst, msg, exception)
188
+ end
185
189
 
186
190
  strategy, delay = delay_for(jobinst, count, exception, msg)
187
191
  case strategy
@@ -142,10 +142,10 @@ module Sidekiq
142
142
  key = identity
143
143
  fails = procd = 0
144
144
 
145
- idle = config[:reap_connections]
146
- if idle
147
- config.capsules.each_value { |cap| cap.local_redis_pool.reap(idle, &:close) }
148
- config.local_redis_pool.reap(idle, &:close)
145
+ idle_timeout = config[:redis_idle_timeout]
146
+ if idle_timeout
147
+ config.capsules.each_value { |cap| cap.local_redis_pool.reap(idle_seconds: idle_timeout, &:close) }
148
+ config.local_redis_pool.reap(idle_seconds: idle_timeout, &:close)
149
149
  end
150
150
 
151
151
  begin
@@ -220,11 +220,11 @@ module Sidekiq
220
220
  # Log a warning if it's a disaster.
221
221
  if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
222
222
  logger.warn <<~EOM
223
- Your Redis network connection is performing extremely poorly.
223
+ Your Redis network connection appears to be performing poorly.
224
224
  Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
225
- Ensure Redis is running in the same AZ or datacenter as Sidekiq.
226
- If these values are close to 100,000, that means your Sidekiq process may be
227
- CPU-saturated; reduce your concurrency and/or see https://github.com/sidekiq/sidekiq/discussions/5039
225
+ Ensure Redis is running in the same AZ or datacenter as Sidekiq and that
226
+ your Sidekiq process is not CPU-saturated; reduce your concurrency and/or
227
+ see https://github.com/sidekiq/sidekiq/discussions/5039
228
228
  EOM
229
229
  RTT_READINGS.reset
230
230
  end
@@ -23,7 +23,7 @@ module Sidekiq
23
23
 
24
24
  size = symbolized_options.delete(:size) || 5
25
25
  pool_timeout = symbolized_options.delete(:pool_timeout) || 1
26
- pool_name = symbolized_options.delete(:pool_name)
26
+ symbolized_options.delete(:pool_name)
27
27
 
28
28
  # Default timeout in redis-client is 1 second, which can be too aggressive
29
29
  # if the Sidekiq process is CPU-bound. With 10-15 threads and a thread quantum of 100ms,
@@ -33,7 +33,7 @@ module Sidekiq
33
33
  symbolized_options[:timeout] ||= 3
34
34
 
35
35
  redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
36
- ConnectionPool.new(timeout: pool_timeout, size: size, name: pool_name) do
36
+ ConnectionPool.new(timeout: pool_timeout, size: size) do
37
37
  redis_config.new_client
38
38
  end
39
39
  end
@@ -72,6 +72,7 @@ module Sidekiq
72
72
  include Sidekiq::Component
73
73
 
74
74
  INITIAL_WAIT = 10
75
+ attr_accessor :rnd
75
76
 
76
77
  def initialize(config)
77
78
  @config = config
@@ -80,6 +81,7 @@ module Sidekiq
80
81
  @done = false
81
82
  @thread = nil
82
83
  @count_calls = 0
84
+ @rnd = Random.new
83
85
  end
84
86
 
85
87
  # Shut down this instance, will pause until the thread is dead.
@@ -115,9 +117,9 @@ module Sidekiq
115
117
  private
116
118
 
117
119
  def wait
118
- @sleeper.pop(random_poll_interval)
120
+ @sleeper.pop(timeout: random_poll_interval)
119
121
  rescue Timeout::Error
120
- # expected
122
+ # TODO move to exception: false
121
123
  rescue => ex
122
124
  # if poll_interval_average hasn't been calculated yet, we can
123
125
  # raise an error trying to reach Redis.
@@ -151,11 +153,11 @@ module Sidekiq
151
153
 
152
154
  if count < 10
153
155
  # For small clusters, calculate a random interval that is ±50% the desired average.
154
- interval * rand + interval.to_f / 2
156
+ interval * @rnd.rand + interval.to_f / 2
155
157
  else
156
158
  # With 10+ processes, we should have enough randomness to get decent polling
157
159
  # across the entire timespan
158
- interval * rand
160
+ interval * @rnd.rand * 2
159
161
  end
160
162
  end
161
163
 
@@ -223,7 +225,7 @@ module Sidekiq
223
225
  total += INITIAL_WAIT unless @config[:poll_interval_average]
224
226
  total += (5 * rand)
225
227
 
226
- @sleeper.pop(total)
228
+ @sleeper.pop(timeout: total)
227
229
  rescue Timeout::Error
228
230
  ensure
229
231
  # periodically clean out the `processes` set in Redis which can collect
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "8.0.9"
4
+ VERSION = "8.1.0"
5
5
  MAJOR = 8
6
6
 
7
7
  def self.gem_version
@@ -117,7 +117,7 @@ module Sidekiq
117
117
  if content.is_a? Symbol
118
118
  unless respond_to?(:"_erb_#{content}")
119
119
  views = options[:views] || Web.views
120
- filename = "#{views}/#{content}.erb"
120
+ filename = "#{views}/#{content}.html.erb"
121
121
  src = ERB.new(File.read(filename)).src
122
122
 
123
123
  # Need to use lineno less by 1 because erb generates a
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sidekiq/web/csrf_protection"
4
-
5
3
  module Sidekiq
6
4
  class Web
7
5
  ##
@@ -24,10 +22,7 @@ module Sidekiq
24
22
  # and very difficult for us to vendor or provide ourselves. If you are worried
25
23
  # about data security and wish to self-host, you can change these URLs.
26
24
  profile_view_url: "https://profiler.firefox.com/public/%s",
27
- profile_store_url: "https://api.profiler.firefox.com/compressed-store",
28
- # Will be false in Sidekiq 9.0.
29
- # CSRF is unnecessary if you are using SameSite=(Strict|Lax) cookies.
30
- csrf: true
25
+ profile_store_url: "https://api.profiler.firefox.com/compressed-store"
31
26
  }
32
27
 
33
28
  ##
@@ -54,11 +49,13 @@ module Sidekiq
54
49
 
55
50
  # Adds the "Back to App" link in the header
56
51
  attr_accessor :app_url
52
+ attr_accessor :assets_path
57
53
 
58
54
  def initialize
59
55
  @options = OPTIONS.dup
60
56
  @locales = LOCALES
61
57
  @views = VIEWS
58
+ @assets_path = ASSETS
62
59
  @tabs = DEFAULT_TABS.dup
63
60
  @middlewares = []
64
61
  @custom_job_info_rows = []
@@ -122,8 +122,8 @@ module Sidekiq
122
122
  resultset
123
123
  end
124
124
 
125
- def filtering(which)
126
- erb(:filtering, locals: {which: which})
125
+ def filtering(which, placeholder_key: "AnyJobContent", label_key: "Filter")
126
+ erb(:filtering, locals: {which:, placeholder_key:, label_key:})
127
127
  end
128
128
 
129
129
  def filter_link(jid, within = "retries")
@@ -329,7 +329,7 @@ module Sidekiq
329
329
  end
330
330
 
331
331
  def csrf_tag
332
- "<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
332
+ ""
333
333
  end
334
334
 
335
335
  def csp_nonce
data/lib/sidekiq/web.rb CHANGED
@@ -13,7 +13,7 @@ module Sidekiq
13
13
  ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
14
14
  VIEWS = "#{ROOT}/views"
15
15
  LOCALES = ["#{ROOT}/locales"]
16
- LAYOUT = "#{VIEWS}/layout.erb"
16
+ LAYOUT = "#{VIEWS}/layout.html.erb"
17
17
  ASSETS = "#{ROOT}/assets"
18
18
 
19
19
  DEFAULT_TABS = {
@@ -42,6 +42,12 @@ module Sidekiq
42
42
  @@config.app_url = url
43
43
  end
44
44
 
45
+ def assets_path=(path)
46
+ @@config.assets_path = path
47
+ end
48
+
49
+ def assets_path = @@config.assets_path
50
+
45
51
  def tabs = @@config.tabs
46
52
 
47
53
  def locales = @@config.locales
@@ -87,13 +93,27 @@ module Sidekiq
87
93
  env[:web_config] = Sidekiq::Web.configure
88
94
  env[:csp_nonce] = SecureRandom.hex(8)
89
95
  env[:redis_pool] = self.class.redis_pool
90
- app.call(env)
96
+ safe_request?(env) ? app.call(env) : deny(env)
91
97
  end
92
98
 
93
99
  def app
94
100
  @app ||= build(@@config)
95
101
  end
96
102
 
103
+ def safe_methods?(env)
104
+ %w[GET HEAD OPTIONS TRACE].include? env["REQUEST_METHOD"]
105
+ end
106
+
107
+ def safe_request?(env)
108
+ return true if safe_methods?(env)
109
+ env["HTTP_SEC_FETCH_SITE"] == "same-origin"
110
+ end
111
+
112
+ def deny(env)
113
+ Sidekiq.logger.warn "attack prevented by #{self.class}"
114
+ [403, {Rack::CONTENT_TYPE => "text/plain"}, ["Forbidden"]]
115
+ end
116
+
97
117
  private
98
118
 
99
119
  def build(cfg)
@@ -105,11 +125,10 @@ module Sidekiq
105
125
 
106
126
  ::Rack::Builder.new do
107
127
  use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
108
- root: ASSETS,
128
+ root: cfg.assets_path,
109
129
  cascade: true,
110
130
  header_rules: rules
111
131
  m.each { |middleware, block| use(*middleware, &block) }
112
- use CsrfProtection if cfg[:csrf]
113
132
  run Sidekiq::Web::Application.new(self.class)
114
133
  end
115
134
  end
data/sidekiq.gemspec CHANGED
@@ -23,9 +23,9 @@ Gem::Specification.new do |gem|
23
23
  "rubygems_mfa_required" => "true"
24
24
  }
25
25
 
26
- gem.add_dependency "redis-client", ">= 0.23.2"
27
- gem.add_dependency "connection_pool", ">= 2.5.0"
28
- gem.add_dependency "rack", ">= 3.1.0"
29
- gem.add_dependency "json", ">= 2.9.0"
30
- gem.add_dependency "logger", ">= 1.6.2"
26
+ gem.add_dependency "redis-client", ">= 0.26.0"
27
+ gem.add_dependency "connection_pool", ">= 3.0.0"
28
+ gem.add_dependency "rack", ">= 3.2.0"
29
+ gem.add_dependency "json", ">= 2.16.0"
30
+ gem.add_dependency "logger", ">= 1.7.0"
31
31
  end
@@ -190,7 +190,7 @@ function updateProgressBars() {
190
190
  function handleConfirmDialog (event) {
191
191
  const target = event.target
192
192
 
193
- if (target.localName !== "input") { return }
193
+ if (target.localName !== "input" && target.localName !== "button" ) { return }
194
194
  const confirmMessage = target.dataset.confirm
195
195
 
196
196
  if (confirmMessage === undefined) { return }
@@ -448,9 +448,7 @@ canvas + .table_container {
448
448
  }
449
449
 
450
450
  .buttons-row {
451
- margin-top: var(--space-3x);
452
451
  display: flex;
453
- flex-wrap: wrap;
454
452
  gap: 8px;
455
453
  }
456
454
 
data/web/locales/fr.yml CHANGED
@@ -8,7 +8,7 @@ fr:
8
8
  AreYouSureDeleteJob: Êtes-vous certain de vouloir supprimer cette tâche ?
9
9
  AreYouSureDeleteQueue: Êtes-vous certain de vouloir supprimer la queue %{queue} ?
10
10
  Arguments: Arguments
11
- Back to App: Retour à l'application
11
+ BackToApp: Retour à l'application
12
12
  Busy: En cours
13
13
  Class: Classe
14
14
  Connections: Connexions
@@ -1,6 +1,6 @@
1
1
  <div class="filter">
2
2
  <form role="search" method="get" class="form-inline" action="<%= root_path %><%= which %>">
3
- <label for="substr"><%= t('Filter') %></label>
4
- <input class="form-control" type="search" name="substr" value="<%= h url_params("substr") %>" placeholder="<%= t('AnyJobContent') %>" autocomplete="off">
3
+ <label for="substr"><%= t(label_key) %></label>
4
+ <input class="form-control" type="search" name="substr" value="<%= h url_params("substr") %>" placeholder="<%= t(placeholder_key) %>" autocomplete="off">
5
5
  </form>
6
6
  </div>
@@ -58,8 +58,9 @@
58
58
  id="<%= id %>"
59
59
  class="metrics-swatch"
60
60
  value="<%= kls %>"
61
- <%= visible_kls.include?(kls) ? 'checked' : '' %>
62
- >
61
+ <% if visible_kls.include?(kls) %>
62
+ checked
63
+ <% end %>>
63
64
  <code><a href="<%= root_path %>metrics/<%= kls %>?period=<%= @period %>"><%= kls %></a></code>
64
65
  </div>
65
66
  </td>
@@ -59,7 +59,7 @@
59
59
  <input class="btn btn-primary pull-left flip" type="submit" name="retry" value="<%= t('RetryNow') %>">
60
60
  <input class="btn btn-danger pull-left flip" type="submit" name="delete" value="<%= t('Delete') %>">
61
61
  <button class="btn btn-primary bulk-action-buttons bulk-lead-button" type="submit" name="action" value="retry_all" formaction="<%= "#{root_path}morgue/all/retry" %>"><%= t('RetryAll') %></button>
62
- <button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}morgue/all/delete" %>"><%= t('DeleteAll') %></button>
62
+ <button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}morgue/all/delete" %>" data-confirm="<%= t('AreYouSure') %>"><%= t('DeleteAll') %></button>
63
63
  </div>
64
64
  </form>
65
65
 
@@ -63,7 +63,7 @@
63
63
  <input class="btn btn-danger" type="submit" name="kill" value="<%= t('Kill') %>">
64
64
  <!-- Retry all -->
65
65
  <button class="btn btn-primary bulk-action-buttons bulk-lead-button" type="submit" name="action" value="retry_all" formaction="<%= "#{root_path}retries/all/retry" %>"><%= t('RetryAll') %></button>
66
- <button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}retries/all/delete" %>"><%= t('DeleteAll') %></button>
66
+ <button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}retries/all/delete" %>" data-confirm="<%= t('AreYouSure') %>"><%= t('DeleteAll') %></button>
67
67
  <button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="kill_all" formaction="<%= "#{root_path}retries/all/kill" %>"><%= t('KillAll') %></button>
68
68
  </div>
69
69
  </form>
@@ -54,7 +54,7 @@
54
54
  <div class="buttons-row">
55
55
  <input class="btn btn-danger" type="submit" name="delete" value="<%= t('Delete') %>">
56
56
  <input class="btn btn-danger" type="submit" name="add_to_queue" value="<%= t('AddToQueue') %>">
57
- <button class="btn btn-danger bulk-action-buttons bulk-lead-button" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}scheduled/all/delete" %>"><%= t('DeleteAll') %></button>
57
+ <button class="btn btn-danger bulk-action-buttons bulk-lead-button" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}scheduled/all/delete" %>" data-confirm="<%= t('AreYouSure') %>"><%= t('DeleteAll') %></button>
58
58
  <button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="add_all_to_queue" formaction="<%= "#{root_path}scheduled/all/add_to_queue" %>"><%= t('AddAllToQueue') %></button>
59
59
  </div>
60
60
  </form>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.9
4
+ version: 8.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
@@ -15,70 +15,70 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 0.23.2
18
+ version: 0.26.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 0.23.2
25
+ version: 0.26.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: connection_pool
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 2.5.0
32
+ version: 3.0.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 2.5.0
39
+ version: 3.0.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: rack
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 3.1.0
46
+ version: 3.2.0
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 3.1.0
53
+ version: 3.2.0
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: json
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 2.9.0
60
+ version: 2.16.0
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
- version: 2.9.0
67
+ version: 2.16.0
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: logger
70
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: 1.6.2
74
+ version: 1.7.0
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - ">="
80
80
  - !ruby/object:Gem::Version
81
- version: 1.6.2
81
+ version: 1.7.0
82
82
  description: Simple, efficient background processing for Ruby.
83
83
  email:
84
84
  - info@contribsys.com
@@ -91,6 +91,7 @@ files:
91
91
  - Changes.md
92
92
  - LICENSE.txt
93
93
  - README.md
94
+ - bin/lint-herb
94
95
  - bin/multi_queue_bench
95
96
  - bin/sidekiq
96
97
  - bin/sidekiqload
@@ -151,7 +152,6 @@ files:
151
152
  - lib/sidekiq/web/action.rb
152
153
  - lib/sidekiq/web/application.rb
153
154
  - lib/sidekiq/web/config.rb
154
- - lib/sidekiq/web/csrf_protection.rb
155
155
  - lib/sidekiq/web/helpers.rb
156
156
  - lib/sidekiq/web/router.rb
157
157
  - lib/sidekiq/worker_compatibility_alias.rb
@@ -199,28 +199,28 @@ files:
199
199
  - web/locales/vi.yml
200
200
  - web/locales/zh-CN.yml
201
201
  - web/locales/zh-TW.yml
202
- - web/views/_footer.erb
203
- - web/views/_job_info.erb
204
- - web/views/_metrics_period_select.erb
205
- - web/views/_nav.erb
206
- - web/views/_paging.erb
207
- - web/views/_poll_link.erb
208
- - web/views/_summary.erb
209
- - web/views/busy.erb
210
- - web/views/dashboard.erb
211
- - web/views/dead.erb
212
- - web/views/filtering.erb
213
- - web/views/layout.erb
214
- - web/views/metrics.erb
215
- - web/views/metrics_for_job.erb
216
- - web/views/morgue.erb
217
- - web/views/profiles.erb
218
- - web/views/queue.erb
219
- - web/views/queues.erb
220
- - web/views/retries.erb
221
- - web/views/retry.erb
222
- - web/views/scheduled.erb
223
- - web/views/scheduled_job_info.erb
202
+ - web/views/_footer.html.erb
203
+ - web/views/_job_info.html.erb
204
+ - web/views/_metrics_period_select.html.erb
205
+ - web/views/_nav.html.erb
206
+ - web/views/_paging.html.erb
207
+ - web/views/_poll_link.html.erb
208
+ - web/views/_summary.html.erb
209
+ - web/views/busy.html.erb
210
+ - web/views/dashboard.html.erb
211
+ - web/views/dead.html.erb
212
+ - web/views/filtering.html.erb
213
+ - web/views/layout.html.erb
214
+ - web/views/metrics.html.erb
215
+ - web/views/metrics_for_job.html.erb
216
+ - web/views/morgue.html.erb
217
+ - web/views/profiles.html.erb
218
+ - web/views/queue.html.erb
219
+ - web/views/queues.html.erb
220
+ - web/views/retries.html.erb
221
+ - web/views/retry.html.erb
222
+ - web/views/scheduled.html.erb
223
+ - web/views/scheduled_job_info.html.erb
224
224
  homepage: https://sidekiq.org
225
225
  licenses:
226
226
  - LGPL-3.0
@@ -1,183 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # this file originally based on authenticity_token.rb from the sinatra/rack-protection project
4
- #
5
- # The MIT License (MIT)
6
- #
7
- # Copyright (c) 2011-2017 Konstantin Haase
8
- # Copyright (c) 2015-2017 Zachary Scott
9
- #
10
- # Permission is hereby granted, free of charge, to any person obtaining
11
- # a copy of this software and associated documentation files (the
12
- # 'Software'), to deal in the Software without restriction, including
13
- # without limitation the rights to use, copy, modify, merge, publish,
14
- # distribute, sublicense, and/or sell copies of the Software, and to
15
- # permit persons to whom the Software is furnished to do so, subject to
16
- # the following conditions:
17
- #
18
- # The above copyright notice and this permission notice shall be
19
- # included in all copies or substantial portions of the Software.
20
- #
21
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
- # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
- # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
- # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
- # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
-
29
- require "securerandom"
30
- require "rack/request"
31
-
32
- module Sidekiq
33
- class Web
34
- class CsrfProtection
35
- def initialize(app, options = nil)
36
- @app = app
37
- end
38
-
39
- def call(env)
40
- accept?(env) ? admit(env) : deny(env)
41
- end
42
-
43
- private
44
-
45
- def admit(env)
46
- # On each successful request, we create a fresh masked token
47
- # which will be used in any forms rendered for this request.
48
- s = session(env)
49
- s[:csrf] ||= SecureRandom.base64(TOKEN_LENGTH)
50
- env[:csrf_token] = mask_token(s[:csrf])
51
- @app.call(env)
52
- end
53
-
54
- def safe?(env)
55
- %w[GET HEAD OPTIONS TRACE].include? env["REQUEST_METHOD"]
56
- end
57
-
58
- def logger(env)
59
- @logger ||= env["rack.logger"] || ::Logger.new(env["rack.errors"])
60
- end
61
-
62
- def deny(env)
63
- logger(env).warn "attack prevented by #{self.class}"
64
- [403, {Rack::CONTENT_TYPE => "text/plain"}, ["Forbidden"]]
65
- end
66
-
67
- def session(env)
68
- env["rack.session"] || fail(<<~EOM)
69
- Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app,
70
- make sure you mount Sidekiq::Web *inside* your application routes:
71
-
72
-
73
- Rails.application.routes.draw do
74
- mount Sidekiq::Web => "/sidekiq"
75
- ....
76
- end
77
-
78
-
79
- If this is a Rails app in API mode, you need to enable sessions.
80
-
81
- https://guides.rubyonrails.org/api_app.html#using-session-middlewares
82
-
83
- If this is a bare Rack app, use a session middleware before Sidekiq::Web:
84
-
85
- # first, use IRB to create a shared secret key for sessions and commit it
86
- require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
87
-
88
- # now use the secret with a session cookie middleware
89
- use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
90
- run Sidekiq::Web
91
-
92
- EOM
93
- end
94
-
95
- def accept?(env)
96
- return true if safe?(env)
97
-
98
- giventoken = ::Rack::Request.new(env).params["authenticity_token"]
99
- valid_token?(env, giventoken)
100
- end
101
-
102
- TOKEN_LENGTH = 32
103
-
104
- # Checks that the token given to us as a parameter matches
105
- # the token stored in the session.
106
- def valid_token?(env, giventoken)
107
- return false if giventoken.nil? || giventoken.empty?
108
-
109
- begin
110
- token = decode_token(giventoken)
111
- rescue ArgumentError # client input is invalid
112
- return false
113
- end
114
-
115
- sess = session(env)
116
- localtoken = sess[:csrf]
117
-
118
- # Checks that Rack::Session::Cookie actually contains the csrf token
119
- return false if localtoken.nil?
120
-
121
- # Rotate the session token after every use
122
- sess[:csrf] = SecureRandom.base64(TOKEN_LENGTH)
123
-
124
- # See if it's actually a masked token or not. We should be able
125
- # to handle any unmasked tokens that we've issued without error.
126
-
127
- if unmasked_token?(token)
128
- compare_with_real_token token, localtoken
129
- elsif masked_token?(token)
130
- unmasked = unmask_token(token)
131
- compare_with_real_token unmasked, localtoken
132
- else
133
- false # Token is malformed
134
- end
135
- end
136
-
137
- # Creates a masked version of the authenticity token that varies
138
- # on each request. The masking is used to mitigate SSL attacks
139
- # like BREACH.
140
- def mask_token(token)
141
- token = decode_token(token)
142
- one_time_pad = SecureRandom.random_bytes(token.length)
143
- encrypted_token = xor_byte_strings(one_time_pad, token)
144
- masked_token = one_time_pad + encrypted_token
145
- encode_token(masked_token)
146
- end
147
-
148
- # Essentially the inverse of +mask_token+.
149
- def unmask_token(masked_token)
150
- # Split the token into the one-time pad and the encrypted
151
- # value and decrypt it
152
- token_length = masked_token.length / 2
153
- one_time_pad = masked_token[0...token_length]
154
- encrypted_token = masked_token[token_length..]
155
- xor_byte_strings(one_time_pad, encrypted_token)
156
- end
157
-
158
- def unmasked_token?(token)
159
- token.length == TOKEN_LENGTH
160
- end
161
-
162
- def masked_token?(token)
163
- token.length == TOKEN_LENGTH * 2
164
- end
165
-
166
- def compare_with_real_token(token, local)
167
- ::Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s)
168
- end
169
-
170
- def encode_token(token)
171
- [token].pack("m0").tr("+/", "-_")
172
- end
173
-
174
- def decode_token(token)
175
- token.tr("-_", "+/").unpack1("m0")
176
- end
177
-
178
- def xor_byte_strings(s1, s2)
179
- s1.bytes.zip(s2.bytes).map { |(c1, c2)| c1 ^ c2 }.pack("c*")
180
- end
181
- end
182
- end
183
- end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes