sidekiq 8.0.4 → 8.0.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70244790b6f984dfb4da9922e5971f3072479bce2f484ca3ab3982108906e6d0
4
- data.tar.gz: 77b5d8e952603bebfe6e703b0f92df3cf4b78be6caf7e46fb46e5376c02c45c7
3
+ metadata.gz: b6411cfbee23ece1d53e775bf2726c85d236469d655eedfc8bc808277693fa1b
4
+ data.tar.gz: eef531b98d9f9e6fd5dc2fe2d2d59ac4a033134417be773b453dbf159f125156
5
5
  SHA512:
6
- metadata.gz: 5c347f189e4ed255a1de061afa5c08bf73b3d77f83d853669e09c9b7e06f430958d1db4e3aa9a6ae3a9abad5802c3872f050ae43b2db014105633c45be4cfaa0
7
- data.tar.gz: 0fca2e77cfd25ab7ccc6be20edeb8a0c09056c8bb0bde03de5ff415a5ca15f55668e1c662a2f91c1dfe8ed074ae5d106d95b1d3a04e32fa1d86606aa35c06645
6
+ metadata.gz: 8cd9e7e77116f9a9d07ac4cf9f805ffd58d6d81fe53fa52cf5c293fb33ae6bdaff6fce351527bf87df056225794016157949c6dcb9f1d3ddc37169c105cc15ce
7
+ data.tar.gz: f1b0fceebc6a94e2d4ca441fe834f3e82a1d0702da0cd70d14cf8fb0eb2f9490d6b1842e0e5c34e4a5f4e6a13a193806920826bd8b6f816297db471fc7464313
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.0.7
6
+ ----------
7
+
8
+ - The `:discard` option for `sidekiq_retries_exhausted` and `sidekiq_retry_in`
9
+ now calls death handlers, otherwise it could break other Sidekiq
10
+ functionality. [#6741]
11
+ - Provide a Plain log formatter which does not colorize output [#6778]
12
+ - Job iteration now exposes `current_object` for easy access within the `around_iteration` callback [#6774]
13
+ - Fix JS race condition which could skip confirmation dialogs when Live Polling [#6768]
14
+ - Fix edge case which could lose CurrentAttributes [#6767]
15
+ - Update UK locale [#6776]
16
+
17
+ 8.0.6
18
+ ----------
19
+
20
+ - Adjust transactional client to use ActiveRecord 7.2's support for
21
+ `after_all_transactions_commit` when available. [#6765, rewritten]
22
+ - Fix Rails 7.0 and 7.1 compatibility [#6746, mlarraz]
23
+ - Flush metrics at `:exit` [#6764]
24
+
25
+ 8.0.5
26
+ ----------
27
+
28
+ - Add `stopping?` method to AJ adapter for compatibility with the new AJ::Continuations feature [#6732]
29
+ - Further improvements to Rails boot compatibility [#6710]
30
+ - Add ability to disable CSRF middleware. SameSite cookies prevent
31
+ CSRF in a cleaner manner and are default in most browsers now.
32
+ CSRF code will be removed in Sidekiq 9.0. [#6739]
33
+
5
34
  8.0.4
6
35
  ----------
7
36
 
@@ -17,7 +17,7 @@ begin
17
17
  end
18
18
  end
19
19
 
20
- unless ActiveJob::Base.respond_to?(:sidekiq_options)
20
+ ActiveSupport.on_load(:active_job) do
21
21
  # By including the Options module, we allow AJs to directly control sidekiq features
22
22
  # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
23
23
  # AJ retries don't show up in the Sidekiq UI Retries tab, don't save any error data, can't be
@@ -29,7 +29,7 @@ begin
29
29
  # def perform
30
30
  # end
31
31
  # end
32
- ActiveJob::Base.include Sidekiq::Job::Options
32
+ include Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
33
33
  end
34
34
 
35
35
  # Patch the ActiveJob module
@@ -43,7 +43,15 @@ begin
43
43
  # To use Sidekiq set the queue_adapter config to +:sidekiq+.
44
44
  #
45
45
  # Rails.application.config.active_job.queue_adapter = :sidekiq
46
- class SidekiqAdapter
46
+ parent = const_defined?(:AbstractAdapter) ? AbstractAdapter : Object
47
+ class SidekiqAdapter < parent
48
+ @@stopping = false
49
+
50
+ callback = -> { @@stopping = true }
51
+
52
+ Sidekiq.configure_client { |config| config.on(:quiet, &callback) }
53
+ Sidekiq.configure_server { |config| config.on(:quiet, &callback) }
54
+
47
55
  # Defines whether enqueuing should happen implicitly to after commit when called
48
56
  # from inside a transaction.
49
57
  # @api private
@@ -99,10 +107,12 @@ begin
99
107
  enqueued_count
100
108
  end
101
109
 
110
+ # @api private
111
+ def stopping? = !!@@stopping
112
+
102
113
  # Defines a class alias for backwards compatibility with enqueued Active Job jobs.
103
114
  # @api private
104
- class JobWrapper < Sidekiq::ActiveJob::Wrapper
105
- end
115
+ JobWrapper = Sidekiq::ActiveJob::Wrapper
106
116
  end
107
117
  end
108
118
  end
@@ -32,8 +32,14 @@ module Sidekiq
32
32
  @_runtime = 0
33
33
  @_args = nil
34
34
  @_cancelled = nil
35
+ @current_object = nil
35
36
  end
36
37
 
38
+ # Access to the current object while iterating.
39
+ # This value is not reset so the latest element is
40
+ # explicitly available to cleanup/complete callbacks.
41
+ attr_reader :current_object
42
+
37
43
  def arguments
38
44
  @_args
39
45
  end
@@ -203,6 +209,7 @@ module Sidekiq
203
209
  enumerator.each do |object, cursor|
204
210
  found_record = true
205
211
  @_cursor = cursor
212
+ @current_object = object
206
213
 
207
214
  is_interrupted = interrupted?
208
215
  if ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - state_flushed_at >= STATE_FLUSH_INTERVAL || is_interrupted
@@ -27,7 +27,7 @@ module Sidekiq
27
27
  # attribute to expose the underlying thing.
28
28
  h = {
29
29
  jid: job_hash["jid"],
30
- class: job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
30
+ class: job_hash["wrapped"] || job_hash["class"]
31
31
  }
32
32
  h[:bid] = job_hash["bid"] if job_hash.has_key?("bid")
33
33
  h[:tags] = job_hash["tags"] if job_hash.has_key?("tags")
@@ -186,7 +186,7 @@ module Sidekiq
186
186
  strategy, delay = delay_for(jobinst, count, exception, msg)
187
187
  case strategy
188
188
  when :discard
189
- return # poof!
189
+ return run_death_handlers(msg, exception)
190
190
  when :kill
191
191
  return retries_exhausted(jobinst, msg, exception)
192
192
  end
@@ -255,13 +255,16 @@ module Sidekiq
255
255
  handle_exception(e, {context: "Error calling retries_exhausted", job: msg})
256
256
  end
257
257
 
258
- return if rv == :discard # poof!
259
- send_to_morgue(msg) unless msg["dead"] == false
258
+ to_morgue = !(msg["dead"] == false || rv == :discard)
259
+ send_to_morgue(msg) if to_morgue
260
+ run_death_handlers(msg, exception)
261
+ end
260
262
 
263
+ def run_death_handlers(job, exception)
261
264
  @capsule.config.death_handlers.each do |handler|
262
- handler.call(msg, exception)
265
+ handler.call(job, exception)
263
266
  rescue => e
264
- handle_exception(e, {context: "Error calling death handler", job: msg})
267
+ handle_exception(e, {context: "Error calling death handler", job: job})
265
268
  end
266
269
  end
267
270
 
@@ -24,14 +24,15 @@ module Sidekiq
24
24
 
25
25
  class Logger < ::Logger
26
26
  module Formatters
27
- COLORS = {
28
- "DEBUG" => "\e[1;32mDEBUG\e[0m", # green
29
- "INFO" => "\e[1;34mINFO \e[0m", # blue
30
- "WARN" => "\e[1;33mWARN \e[0m", # yellow
31
- "ERROR" => "\e[1;31mERROR\e[0m", # red
32
- "FATAL" => "\e[1;35mFATAL\e[0m" # pink
33
- }
34
27
  class Base < ::Logger::Formatter
28
+ COLORS = {
29
+ "DEBUG" => "\e[1;32mDEBUG\e[0m", # green
30
+ "INFO" => "\e[1;34mINFO \e[0m", # blue
31
+ "WARN" => "\e[1;33mWARN \e[0m", # yellow
32
+ "ERROR" => "\e[1;31mERROR\e[0m", # red
33
+ "FATAL" => "\e[1;35mFATAL\e[0m" # pink
34
+ }
35
+
35
36
  def tid
36
37
  Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
37
38
  end
@@ -50,13 +51,19 @@ module Sidekiq
50
51
 
51
52
  class Pretty < Base
52
53
  def call(severity, time, program_name, message)
53
- "#{Formatters::COLORS[severity]} #{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
54
+ "#{COLORS[severity]} #{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
55
+ end
56
+ end
57
+
58
+ class Plain < Base
59
+ def call(severity, time, program_name, message)
60
+ "#{severity} #{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
54
61
  end
55
62
  end
56
63
 
57
64
  class WithoutTimestamp < Pretty
58
65
  def call(severity, time, program_name, message)
59
- "#{Formatters::COLORS[severity]} pid=#{::Process.pid} tid=#{tid} #{format_context}: #{message}\n"
66
+ "#{COLORS[severity]} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
60
67
  end
61
68
  end
62
69
 
@@ -147,4 +147,7 @@ Sidekiq.configure_server do |config|
147
147
  config.on(:beat) do
148
148
  exec.flush
149
149
  end
150
+ config.on(:exit) do
151
+ exec.flush
152
+ end
150
153
  end
@@ -50,7 +50,7 @@ module Sidekiq
50
50
  @cattrs = cattrs
51
51
  end
52
52
 
53
- def call(_, job, _, &block)
53
+ def call(_, job, *, &block)
54
54
  klass_attrs = {}
55
55
 
56
56
  @cattrs.each do |(key, strklass)|
@@ -93,6 +93,7 @@ module Sidekiq
93
93
  def persist(klass_or_array, config = Sidekiq.default_configuration)
94
94
  cattrs = build_cattrs_hash(klass_or_array)
95
95
 
96
+ config.client_middleware.prepend Load, cattrs
96
97
  config.client_middleware.add Save, cattrs
97
98
  config.server_middleware.prepend Load, cattrs
98
99
  end
data/lib/sidekiq/rails.rb CHANGED
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sidekiq/job"
4
- require_relative "../active_job/queue_adapters/sidekiq_adapter"
5
-
6
3
  module Sidekiq
7
4
  begin
8
5
  gem "railties", ">= 7.0"
9
6
  require "rails"
7
+ require "sidekiq/job"
8
+ require_relative "../active_job/queue_adapters/sidekiq_adapter"
10
9
 
11
10
  class Rails < ::Rails::Engine
12
11
  class Reloader
@@ -83,7 +83,7 @@ module Sidekiq
83
83
  class EmptyQueueError < RuntimeError; end
84
84
 
85
85
  module TestingClient
86
- def atomic_push(conn, payloads)
86
+ private def atomic_push(conn, payloads)
87
87
  if Sidekiq::Testing.fake?
88
88
  payloads.each do |job|
89
89
  job = Sidekiq.load_json(Sidekiq.dump_json(job))
@@ -7,6 +7,12 @@ module Sidekiq
7
7
  class TransactionAwareClient
8
8
  def initialize(pool: nil, config: nil)
9
9
  @redis_client = Client.new(pool: pool, config: config)
10
+ @transaction_backend =
11
+ if ActiveRecord.version >= Gem::Version.new("7.2")
12
+ ActiveRecord.method(:after_all_transactions_commit)
13
+ else
14
+ AfterCommitEverywhere.method(:after_commit)
15
+ end
10
16
  end
11
17
 
12
18
  def batching?
@@ -20,7 +26,7 @@ module Sidekiq
20
26
  # pre-allocate the JID so we can return it immediately and
21
27
  # save it to the database as part of the transaction.
22
28
  item["jid"] ||= SecureRandom.hex(12)
23
- AfterCommitEverywhere.after_commit { @redis_client.push(item) }
29
+ @transaction_backend.call { @redis_client.push(item) }
24
30
  item["jid"]
25
31
  end
26
32
 
@@ -38,10 +44,12 @@ end
38
44
  # Use `Sidekiq.transactional_push!` in your sidekiq.rb initializer
39
45
  module Sidekiq
40
46
  def self.transactional_push!
41
- begin
42
- require "after_commit_everywhere"
43
- rescue LoadError
44
- raise %q(You need to add `gem "after_commit_everywhere"` to your Gemfile to use Sidekiq's transactional client)
47
+ if ActiveRecord.version < Gem::Version.new("7.2")
48
+ begin
49
+ require "after_commit_everywhere"
50
+ rescue LoadError
51
+ raise %q(You need ActiveRecord >= 7.2 or to add `gem "after_commit_everywhere"` to your Gemfile to use Sidekiq's transactional client)
52
+ end
45
53
  end
46
54
 
47
55
  Sidekiq.default_job_options["client_class"] = Sidekiq::TransactionAwareClient
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "8.0.4"
4
+ VERSION = "8.0.7"
5
5
  MAJOR = 8
6
6
 
7
7
  def self.gem_version
@@ -67,7 +67,31 @@ module Sidekiq
67
67
  end
68
68
 
69
69
  def session
70
- env["rack.session"]
70
+ env["rack.session"] || fail(<<~EOM)
71
+ Sidekiq::Web needs a valid Rack session. If this is a Rails app, make
72
+ sure you mount Sidekiq::Web *inside* your application routes:
73
+
74
+
75
+ Rails.application.routes.draw do
76
+ mount Sidekiq::Web => "/sidekiq"
77
+ ....
78
+ end
79
+
80
+
81
+ If this is a Rails app in API mode, you need to enable sessions.
82
+
83
+ https://guides.rubyonrails.org/api_app.html#using-session-middlewares
84
+
85
+ If this is a bare Rack app, use a session middleware before Sidekiq::Web:
86
+
87
+ # first, use IRB to create a shared secret key for sessions and commit it
88
+ require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
89
+
90
+ # now use the secret with a session cookie middleware
91
+ use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
92
+ run Sidekiq::Web
93
+
94
+ EOM
71
95
  end
72
96
 
73
97
  def logger
@@ -24,7 +24,10 @@ module Sidekiq
24
24
  # and very difficult for us to vendor or provide ourselves. If you are worried
25
25
  # about data security and wish to self-host, you can change these URLs.
26
26
  profile_view_url: "https://profiler.firefox.com/public/%s",
27
- profile_store_url: "https://api.profiler.firefox.com/compressed-store"
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
28
31
  }
29
32
 
30
33
  ##
data/lib/sidekiq/web.rb CHANGED
@@ -109,7 +109,7 @@ module Sidekiq
109
109
  cascade: true,
110
110
  header_rules: rules
111
111
  m.each { |middleware, block| use(*middleware, &block) }
112
- use CsrfProtection unless $TESTING # rubocop:disable Style/GlobalVars
112
+ use CsrfProtection if cfg[:csrf]
113
113
  run Sidekiq::Web::Application.new(self.class)
114
114
  end
115
115
  end
File without changes
File without changes
@@ -18,15 +18,6 @@ function addListeners() {
18
18
  })
19
19
  });
20
20
 
21
- document.querySelectorAll("input[data-confirm]").forEach(node => {
22
- node.addEventListener("click", event => {
23
- if (!window.confirm(node.getAttribute("data-confirm"))) {
24
- event.preventDefault();
25
- event.stopPropagation();
26
- }
27
- })
28
- })
29
-
30
21
  document.querySelectorAll("[data-toggle]").forEach(node => {
31
22
  node.addEventListener("click", addDataToggleListeners)
32
23
  })
@@ -178,4 +169,20 @@ function updateLocale(event) {
178
169
 
179
170
  function updateProgressBars() {
180
171
  document.querySelectorAll('.progress-bar').forEach(bar => { bar.style.width = bar.dataset.width + "%"})
181
- }
172
+ }
173
+
174
+ function handleConfirmDialog (event) {
175
+ const target = event.target
176
+
177
+ if (target.localName !== "input") { return }
178
+ if (!target.hasAttribute("data-confirm")) { return }
179
+
180
+ const confirmMessage = target.getAttribute("data-confirm")
181
+
182
+ if (!window.confirm(confirmMessage)) {
183
+ event.preventDefault()
184
+ event.stopPropagation()
185
+ }
186
+ }
187
+
188
+ document.addEventListener("click", handleConfirmDialog)
@@ -567,6 +567,7 @@ body > footer .nav {
567
567
  --color-border: oklch(25% 0.01 256);
568
568
  --color-input-border: oklch(31% 0.01 256);
569
569
  --color-selected: oklch(27% 0.01 256);
570
+ --color-selected-text: oklch(55% 0.11 45);
570
571
  --color-table-bg-alt: oklch(24% 0.01 256);
571
572
  --color-shadow: oklch(9% 0.01 256 / 10%);
572
573
  --color-text: oklch(75% 0.01 256);
@@ -615,6 +616,10 @@ body > footer .nav {
615
616
  .label-info { background: var(--color-info); }
616
617
  .label-danger { background: var(--color-danger); }
617
618
  .label-warning { background: var(--color-warning); }
619
+
620
+ td.box::selection {
621
+ background-color: var(--color-selected-text);
622
+ }
618
623
  }
619
624
 
620
625
  @media (max-width: 800px) { :root { --font-size: 14px; } }
@@ -758,4 +763,4 @@ body > footer .nav {
758
763
  text-align: center;
759
764
  padding: 20px;
760
765
  background: var(--color-success);
761
- }
766
+ }
data/web/locales/uk.yml CHANGED
@@ -14,8 +14,8 @@ uk:
14
14
  CreatedAt: Створено
15
15
  CurrentMessagesInQueue: Поточні задачі у черзі <span class='title'>%{queue}</span>
16
16
  Dashboard: Панель керування
17
- Dead: Вбитих
18
- DeadJobs: Вбиті задачі
17
+ Dead: Зупинених
18
+ DeadJobs: Зупинені задачі
19
19
  Delete: Видалити
20
20
  DeleteAll: Видалити усі
21
21
  Deploy: Деплой
@@ -33,8 +33,8 @@ uk:
33
33
  History: Історія
34
34
  Job: Задача
35
35
  Jobs: Задачі
36
- Kill: Вбити
37
- KillAll: Вбити все
36
+ Kill: Зупинити
37
+ KillAll: Зупинити все
38
38
  LastRetry: Остання спроба
39
39
  Latency: Затримка
40
40
  LivePoll: Постійне опитування
@@ -42,7 +42,7 @@ uk:
42
42
  Name: Назва
43
43
  Namespace: Простір імен
44
44
  NextRetry: Наступна спроба
45
- NoDeadJobsFound: Вбитих задач не знайдено
45
+ NoDeadJobsFound: Зупинених задач не знайдено
46
46
  NoRetriesFound: Спроб не знайдено
47
47
  NoScheduledFound: Запланованих задач не знайдено
48
48
  NotYetEnqueued: Ще не в черзі
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.4
4
+ version: 8.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: redis-client
@@ -244,7 +244,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
244
244
  - !ruby/object:Gem::Version
245
245
  version: '0'
246
246
  requirements: []
247
- rubygems_version: 3.6.2
247
+ rubygems_version: 3.6.9
248
248
  specification_version: 4
249
249
  summary: Simple, efficient background processing for Ruby
250
250
  test_files: []