sidekiq 7.1.4 → 8.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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +333 -0
  3. data/README.md +16 -13
  4. data/bin/multi_queue_bench +271 -0
  5. data/bin/sidekiqload +31 -22
  6. data/bin/webload +69 -0
  7. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +121 -0
  8. data/lib/generators/sidekiq/job_generator.rb +2 -0
  9. data/lib/generators/sidekiq/templates/job.rb.erb +1 -1
  10. data/lib/sidekiq/api.rb +260 -67
  11. data/lib/sidekiq/capsule.rb +17 -8
  12. data/lib/sidekiq/cli.rb +19 -20
  13. data/lib/sidekiq/client.rb +48 -15
  14. data/lib/sidekiq/component.rb +64 -3
  15. data/lib/sidekiq/config.rb +60 -18
  16. data/lib/sidekiq/deploy.rb +4 -2
  17. data/lib/sidekiq/embedded.rb +4 -1
  18. data/lib/sidekiq/fetch.rb +2 -1
  19. data/lib/sidekiq/iterable_job.rb +56 -0
  20. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  21. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  22. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  23. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  24. data/lib/sidekiq/job/iterable.rb +322 -0
  25. data/lib/sidekiq/job.rb +16 -5
  26. data/lib/sidekiq/job_logger.rb +15 -12
  27. data/lib/sidekiq/job_retry.rb +41 -13
  28. data/lib/sidekiq/job_util.rb +7 -1
  29. data/lib/sidekiq/launcher.rb +23 -11
  30. data/lib/sidekiq/loader.rb +57 -0
  31. data/lib/sidekiq/logger.rb +25 -69
  32. data/lib/sidekiq/manager.rb +0 -1
  33. data/lib/sidekiq/metrics/query.rb +76 -45
  34. data/lib/sidekiq/metrics/shared.rb +23 -9
  35. data/lib/sidekiq/metrics/tracking.rb +32 -15
  36. data/lib/sidekiq/middleware/current_attributes.rb +39 -14
  37. data/lib/sidekiq/middleware/i18n.rb +2 -0
  38. data/lib/sidekiq/middleware/modules.rb +2 -0
  39. data/lib/sidekiq/monitor.rb +6 -9
  40. data/lib/sidekiq/paginator.rb +16 -3
  41. data/lib/sidekiq/processor.rb +37 -20
  42. data/lib/sidekiq/profiler.rb +73 -0
  43. data/lib/sidekiq/rails.rb +47 -57
  44. data/lib/sidekiq/redis_client_adapter.rb +25 -8
  45. data/lib/sidekiq/redis_connection.rb +49 -9
  46. data/lib/sidekiq/ring_buffer.rb +3 -0
  47. data/lib/sidekiq/scheduled.rb +2 -2
  48. data/lib/sidekiq/systemd.rb +2 -0
  49. data/lib/sidekiq/testing.rb +34 -15
  50. data/lib/sidekiq/transaction_aware_client.rb +20 -5
  51. data/lib/sidekiq/version.rb +6 -2
  52. data/lib/sidekiq/web/action.rb +149 -64
  53. data/lib/sidekiq/web/application.rb +367 -297
  54. data/lib/sidekiq/web/config.rb +120 -0
  55. data/lib/sidekiq/web/csrf_protection.rb +8 -5
  56. data/lib/sidekiq/web/helpers.rb +146 -64
  57. data/lib/sidekiq/web/router.rb +61 -74
  58. data/lib/sidekiq/web.rb +53 -106
  59. data/lib/sidekiq.rb +11 -4
  60. data/sidekiq.gemspec +6 -5
  61. data/web/assets/images/logo.png +0 -0
  62. data/web/assets/images/status.png +0 -0
  63. data/web/assets/javascripts/application.js +66 -24
  64. data/web/assets/javascripts/base-charts.js +30 -16
  65. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  66. data/web/assets/javascripts/dashboard-charts.js +37 -11
  67. data/web/assets/javascripts/dashboard.js +15 -11
  68. data/web/assets/javascripts/metrics.js +50 -34
  69. data/web/assets/stylesheets/style.css +776 -0
  70. data/web/locales/ar.yml +2 -0
  71. data/web/locales/cs.yml +2 -0
  72. data/web/locales/da.yml +2 -0
  73. data/web/locales/de.yml +2 -0
  74. data/web/locales/el.yml +2 -0
  75. data/web/locales/en.yml +12 -1
  76. data/web/locales/es.yml +25 -2
  77. data/web/locales/fa.yml +2 -0
  78. data/web/locales/fr.yml +2 -1
  79. data/web/locales/gd.yml +2 -1
  80. data/web/locales/he.yml +2 -0
  81. data/web/locales/hi.yml +2 -0
  82. data/web/locales/it.yml +41 -1
  83. data/web/locales/ja.yml +2 -1
  84. data/web/locales/ko.yml +2 -0
  85. data/web/locales/lt.yml +2 -0
  86. data/web/locales/nb.yml +2 -0
  87. data/web/locales/nl.yml +2 -0
  88. data/web/locales/pl.yml +2 -0
  89. data/web/locales/{pt-br.yml → pt-BR.yml} +4 -3
  90. data/web/locales/pt.yml +2 -0
  91. data/web/locales/ru.yml +2 -0
  92. data/web/locales/sv.yml +2 -0
  93. data/web/locales/ta.yml +2 -0
  94. data/web/locales/tr.yml +102 -0
  95. data/web/locales/uk.yml +29 -4
  96. data/web/locales/ur.yml +2 -0
  97. data/web/locales/vi.yml +2 -0
  98. data/web/locales/{zh-cn.yml → zh-CN.yml} +86 -74
  99. data/web/locales/{zh-tw.yml → zh-TW.yml} +3 -2
  100. data/web/views/_footer.erb +31 -22
  101. data/web/views/_job_info.erb +91 -89
  102. data/web/views/_metrics_period_select.erb +13 -10
  103. data/web/views/_nav.erb +14 -21
  104. data/web/views/_paging.erb +22 -21
  105. data/web/views/_poll_link.erb +2 -2
  106. data/web/views/_summary.erb +23 -23
  107. data/web/views/busy.erb +123 -125
  108. data/web/views/dashboard.erb +71 -82
  109. data/web/views/dead.erb +31 -27
  110. data/web/views/filtering.erb +6 -0
  111. data/web/views/layout.erb +13 -29
  112. data/web/views/metrics.erb +70 -68
  113. data/web/views/metrics_for_job.erb +30 -40
  114. data/web/views/morgue.erb +65 -70
  115. data/web/views/profiles.erb +43 -0
  116. data/web/views/queue.erb +54 -52
  117. data/web/views/queues.erb +43 -37
  118. data/web/views/retries.erb +70 -75
  119. data/web/views/retry.erb +32 -27
  120. data/web/views/scheduled.erb +63 -55
  121. data/web/views/scheduled_job_info.erb +3 -3
  122. metadata +49 -27
  123. data/web/assets/stylesheets/application-dark.css +0 -147
  124. data/web/assets/stylesheets/application-rtl.css +0 -153
  125. data/web/assets/stylesheets/application.css +0 -724
  126. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  127. data/web/assets/stylesheets/bootstrap.css +0 -5
  128. data/web/views/_status.erb +0 -4
data/lib/sidekiq/rails.rb CHANGED
@@ -1,73 +1,63 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sidekiq/job"
4
- require "rails"
5
-
6
3
  module Sidekiq
7
- class Rails < ::Rails::Engine
8
- class Reloader
9
- def initialize(app = ::Rails.application)
10
- @app = app
11
- end
12
-
13
- def call
14
- params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
15
- @app.reloader.wrap(**params) do
16
- yield
4
+ begin
5
+ gem "railties", ">= 7.0"
6
+ require "rails"
7
+ require "sidekiq/job"
8
+ require_relative "../active_job/queue_adapters/sidekiq_adapter"
9
+
10
+ class Rails < ::Rails::Engine
11
+ class Reloader
12
+ def initialize(app = ::Rails.application)
13
+ @app = app
17
14
  end
18
- end
19
15
 
20
- def inspect
21
- "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
22
- end
23
-
24
- def to_json(*)
25
- Sidekiq.dump_json(inspect)
26
- end
27
- end
16
+ def call
17
+ params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
18
+ @app.reloader.wrap(**params) do
19
+ yield
20
+ end
21
+ end
28
22
 
29
- # By including the Options module, we allow AJs to directly control sidekiq features
30
- # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
31
- # AJ retries don't show up in the Sidekiq UI Retries tab, don't save any error data, can't be
32
- # manually retried, don't automatically die, etc.
33
- #
34
- # class SomeJob < ActiveJob::Base
35
- # queue_as :default
36
- # sidekiq_options retry: 3, backtrace: 10
37
- # def perform
38
- # end
39
- # end
40
- initializer "sidekiq.active_job_integration" do
41
- ActiveSupport.on_load(:active_job) do
42
- include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
43
- end
44
- end
23
+ def inspect
24
+ "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
25
+ end
45
26
 
46
- initializer "sidekiq.rails_logger" do
47
- Sidekiq.configure_server do |config|
48
- # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
49
- # it will appear in the Sidekiq console with all of the job context. See #5021 and
50
- # https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
51
- unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
52
- ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
27
+ def to_hash
28
+ {app: @app.class.name}
53
29
  end
54
30
  end
55
- end
56
31
 
57
- initializer "sidekiq.backtrace_cleaner" do
58
- Sidekiq.configure_server do |config|
59
- config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
32
+ initializer "sidekiq.backtrace_cleaner" do
33
+ Sidekiq.configure_server do |config|
34
+ config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
35
+ end
60
36
  end
61
- end
62
37
 
63
- # This hook happens after all initializers are run, just before returning
64
- # from config/environment.rb back to sidekiq/cli.rb.
65
- #
66
- # None of this matters on the client-side, only within the Sidekiq process itself.
67
- config.after_initialize do
68
- Sidekiq.configure_server do |config|
69
- config[:reloader] = Sidekiq::Rails::Reloader.new
38
+ # This hook happens after all initializers are run, just before returning
39
+ # from config/environment.rb back to sidekiq/cli.rb.
40
+ #
41
+ # None of this matters on the client-side, only within the Sidekiq process itself.
42
+ config.after_initialize do
43
+ Sidekiq.configure_server do |config|
44
+ config[:reloader] = Sidekiq::Rails::Reloader.new
45
+
46
+ # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
47
+ # it will appear in the Sidekiq console with all of the job context.
48
+ unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
49
+ if ::Rails.logger.respond_to?(:broadcast_to)
50
+ ::Rails.logger.broadcast_to(config.logger)
51
+ elsif ::ActiveSupport::Logger.respond_to?(:broadcast)
52
+ ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
53
+ else
54
+ ::Rails.logger = ::ActiveSupport::BroadcastLogger.new(::Rails.logger, config.logger)
55
+ end
56
+ end
57
+ end
70
58
  end
71
59
  end
60
+ rescue Gem::LoadError
61
+ # Rails not available or version requirement not met
72
62
  end
73
63
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
3
  require "redis_client"
5
4
  require "redis_client/decorator"
6
5
 
@@ -21,6 +20,22 @@ module Sidekiq
21
20
  @client.call("EVALSHA", sha, keys.size, *keys, *argv)
22
21
  end
23
22
 
23
+ # this is the set of Redis commands used by Sidekiq. Not guaranteed
24
+ # to be comprehensive, we use this as a performance enhancement to
25
+ # avoid calling method_missing on most commands
26
+ USED_COMMANDS = %w[bitfield bitfield_ro del exists expire flushdb
27
+ get hdel hget hgetall hincrby hlen hmget hset hsetnx incr incrby
28
+ lindex llen lmove lpop lpush lrange lrem mget mset ping pttl
29
+ publish rpop rpush sadd scard script set sismember smembers
30
+ srem ttl type unlink zadd zcard zincrby zrange zrem
31
+ zremrangebyrank zremrangebyscore]
32
+
33
+ USED_COMMANDS.each do |name|
34
+ define_method(name) do |*args, **kwargs|
35
+ @client.call(name, *args, **kwargs)
36
+ end
37
+ end
38
+
24
39
  private
25
40
 
26
41
  # this allows us to use methods like `conn.hmset(...)` instead of having to use
@@ -48,6 +63,13 @@ module Sidekiq
48
63
  opts = client_opts(options)
49
64
  @config = if opts.key?(:sentinels)
50
65
  RedisClient.sentinel(**opts)
66
+ elsif opts.key?(:nodes)
67
+ # Sidekiq does not support Redis clustering but Sidekiq Enterprise's
68
+ # rate limiters are cluster-safe so we can scale to millions
69
+ # of rate limiters using a Redis cluster. This requires the
70
+ # `redis-cluster-client` gem.
71
+ # Sidekiq::Limiter.redis = { nodes: [...] }
72
+ RedisClient.cluster(**opts)
51
73
  else
52
74
  RedisClient.config(**opts)
53
75
  end
@@ -63,8 +85,7 @@ module Sidekiq
63
85
  opts = options.dup
64
86
 
65
87
  if opts[:namespace]
66
- raise ArgumentError, "Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature isn't supported by redis-client. " \
67
- "Either use the redis adapter or remove the namespace."
88
+ raise ArgumentError, "Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature is no longer supported in Sidekiq 7+. See https://github.com/sidekiq/sidekiq/blob/main/docs/7.0-Upgrade.md#redis-namespace."
68
89
  end
69
90
 
70
91
  opts.delete(:size)
@@ -75,13 +96,9 @@ module Sidekiq
75
96
  opts.delete(:network_timeout)
76
97
  end
77
98
 
78
- if opts[:driver]
79
- opts[:driver] = opts[:driver].to_sym
80
- end
81
-
82
99
  opts[:name] = opts.delete(:master_name) if opts.key?(:master_name)
83
100
  opts[:role] = opts[:role].to_sym if opts.key?(:role)
84
- opts.delete(:url) if opts.key?(:sentinels)
101
+ opts[:driver] = opts[:driver].to_sym if opts.key?(:driver)
85
102
 
86
103
  # Issue #3303, redis-rb will silently retry an operation.
87
104
  # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
@@ -8,16 +8,30 @@ module Sidekiq
8
8
  module RedisConnection
9
9
  class << self
10
10
  def create(options = {})
11
- symbolized_options = options.transform_keys(&:to_sym)
11
+ symbolized_options = deep_symbolize_keys(options)
12
12
  symbolized_options[:url] ||= determine_redis_provider
13
+ symbolized_options[:password] = wrap(symbolized_options[:password]) if symbolized_options.key?(:password)
14
+ symbolized_options[:sentinel_password] = wrap(symbolized_options[:sentinel_password]) if symbolized_options.key?(:sentinel_password)
13
15
 
14
16
  logger = symbolized_options.delete(:logger)
15
17
  logger&.info { "Sidekiq #{Sidekiq::VERSION} connecting to Redis with options #{scrub(symbolized_options)}" }
16
18
 
19
+ raise "Sidekiq 7+ does not support Redis protocol 2" if symbolized_options[:protocol] == 2
20
+
21
+ safe = !!symbolized_options.delete(:cluster_safe)
22
+ raise ":nodes not allowed, Sidekiq is not safe to run on Redis Cluster" if !safe && symbolized_options.key?(:nodes)
23
+
17
24
  size = symbolized_options.delete(:size) || 5
18
25
  pool_timeout = symbolized_options.delete(:pool_timeout) || 1
19
26
  pool_name = symbolized_options.delete(:pool_name)
20
27
 
28
+ # Default timeout in redis-client is 1 second, which can be too aggressive
29
+ # if the Sidekiq process is CPU-bound. With 10-15 threads and a thread quantum of 100ms,
30
+ # it can be easy to get the occasional ReadTimeoutError. You can still provide
31
+ # a smaller timeout explicitly:
32
+ # config.redis = { url: "...", timeout: 1 }
33
+ symbolized_options[:timeout] ||= 3
34
+
21
35
  redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
22
36
  ConnectionPool.new(timeout: pool_timeout, size: size, name: pool_name) do
23
37
  redis_config.new_client
@@ -26,23 +40,51 @@ module Sidekiq
26
40
 
27
41
  private
28
42
 
43
+ # Wrap hard-coded passwords in a Proc to avoid logging the value
44
+ def wrap(pwd)
45
+ if pwd.is_a?(String)
46
+ ->(username) { pwd }
47
+ else
48
+ pwd
49
+ end
50
+ end
51
+
52
+ def deep_symbolize_keys(object)
53
+ case object
54
+ when Hash
55
+ object.each_with_object({}) do |(key, value), result|
56
+ result[key.to_sym] = deep_symbolize_keys(value)
57
+ end
58
+ when Array
59
+ object.map { |e| deep_symbolize_keys(e) }
60
+ else
61
+ object
62
+ end
63
+ end
64
+
29
65
  def scrub(options)
30
66
  redacted = "REDACTED"
31
67
 
32
68
  # Deep clone so we can muck with these options all we want and exclude
33
69
  # params from dump-and-load that may contain objects that Marshal is
34
70
  # unable to safely dump.
35
- keys = options.keys - [:logger, :ssl_params]
71
+ keys = options.keys - [:logger, :ssl_params, :password, :sentinel_password]
36
72
  scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
37
73
  if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
38
74
  uri.password = redacted
39
75
  scrubbed_options[:url] = uri.to_s
40
76
  end
41
- if scrubbed_options[:password]
42
- scrubbed_options[:password] = redacted
43
- end
77
+ scrubbed_options[:password] = redacted if options.key?(:password)
78
+ scrubbed_options[:sentinel_password] = redacted if options.key?(:sentinel_password)
44
79
  scrubbed_options[:sentinels]&.each do |sentinel|
45
- sentinel[:password] = redacted if sentinel[:password]
80
+ if sentinel.is_a?(String)
81
+ if (uri = URI(sentinel)) && uri.password
82
+ uri.password = redacted
83
+ sentinel.replace(uri.to_s)
84
+ end
85
+ elsif sentinel[:password]
86
+ sentinel[:password] = redacted
87
+ end
46
88
  end
47
89
  scrubbed_options
48
90
  end
@@ -66,9 +108,7 @@ module Sidekiq
66
108
  EOM
67
109
  end
68
110
 
69
- ENV[
70
- p || "REDIS_URL"
71
- ]
111
+ ENV[p.to_s] || ENV["REDIS_URL"]
72
112
  end
73
113
  end
74
114
  end
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "forwardable"
2
4
 
3
5
  module Sidekiq
4
6
  class RingBuffer
5
7
  include Enumerable
6
8
  extend Forwardable
9
+
7
10
  def_delegators :@buf, :[], :each, :size
8
11
 
9
12
  def initialize(size, default = 0)
@@ -144,7 +144,7 @@ module Sidekiq
144
144
  # In the example above, each process should schedule every 10 seconds on average. We special
145
145
  # case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
146
146
  # As we run more processes, the scheduling interval average will approach an even spread
147
- # between 0 and poll interval so we don't need this artifical boost.
147
+ # between 0 and poll interval so we don't need this artificial boost.
148
148
  #
149
149
  count = process_count
150
150
  interval = poll_interval_average(count)
@@ -193,7 +193,7 @@ module Sidekiq
193
193
  # should never depend on sidekiq/api.
194
194
  def cleanup
195
195
  # dont run cleanup more than once per minute
196
- return 0 unless redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) }
196
+ return 0 unless redis { |conn| conn.set("process_cleanup", "1", "NX", "EX", "60") }
197
197
 
198
198
  count = 0
199
199
  redis do |conn|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Sidekiq's systemd integration allows Sidekiq to inform systemd:
3
5
  # 1. when it has successfully started
@@ -5,23 +5,42 @@ require "sidekiq"
5
5
 
6
6
  module Sidekiq
7
7
  class Testing
8
+ class TestModeAlreadySetError < RuntimeError; end
8
9
  class << self
9
- attr_accessor :__test_mode
10
+ attr_accessor :__global_test_mode
10
11
 
12
+ # Calling without a block sets the global test mode, affecting
13
+ # all threads. Calling with a block only affects the current Thread.
11
14
  def __set_test_mode(mode)
12
15
  if block_given?
13
- current_mode = __test_mode
16
+ # Reentrant testing modes will lead to a rat's nest of code which is
17
+ # hard to reason about. You can set the testing mode once globally and
18
+ # you can override that global setting once per-thread.
19
+ raise TestModeAlreadySetError, "Nesting test modes is not supported" if __local_test_mode
20
+
21
+ self.__local_test_mode = mode
14
22
  begin
15
- self.__test_mode = mode
16
23
  yield
17
24
  ensure
18
- self.__test_mode = current_mode
25
+ self.__local_test_mode = nil
19
26
  end
20
27
  else
21
- self.__test_mode = mode
28
+ self.__global_test_mode = mode
22
29
  end
23
30
  end
24
31
 
32
+ def __test_mode
33
+ __local_test_mode || __global_test_mode
34
+ end
35
+
36
+ def __local_test_mode
37
+ Thread.current[:__sidekiq_test_mode]
38
+ end
39
+
40
+ def __local_test_mode=(value)
41
+ Thread.current[:__sidekiq_test_mode] = value
42
+ end
43
+
25
44
  def disable!(&block)
26
45
  __set_test_mode(:disable, &block)
27
46
  end
@@ -64,11 +83,11 @@ module Sidekiq
64
83
  class EmptyQueueError < RuntimeError; end
65
84
 
66
85
  module TestingClient
67
- def raw_push(payloads)
86
+ private def atomic_push(conn, payloads)
68
87
  if Sidekiq::Testing.fake?
69
88
  payloads.each do |job|
70
89
  job = Sidekiq.load_json(Sidekiq.dump_json(job))
71
- job["enqueued_at"] = Time.now.to_f unless job["at"]
90
+ job["enqueued_at"] = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) unless job["at"]
72
91
  Queues.push(job["queue"], job["class"], job)
73
92
  end
74
93
  true
@@ -93,7 +112,7 @@ module Sidekiq
93
112
  # The Queues class is only for testing the fake queue implementation.
94
113
  # There are 2 data structures involved in tandem. This is due to the
95
114
  # Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
96
- # to the array. Because the array was dervied from a filter of the total
115
+ # to the array. Because the array was derived from a filter of the total
97
116
  # jobs enqueued, it appeared as though the array didn't change.
98
117
  #
99
118
  # To solve this, we'll keep 2 hashes containing the jobs. One with keys based
@@ -259,16 +278,16 @@ module Sidekiq
259
278
  def perform_one
260
279
  raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
261
280
  next_job = jobs.first
262
- Queues.delete_for(next_job["jid"], queue, to_s)
281
+ Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
263
282
  process_job(next_job)
264
283
  end
265
284
 
266
285
  def process_job(job)
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"])
286
+ instance = new
287
+ instance.jid = job["jid"]
288
+ instance.bid = job["bid"] if instance.respond_to?(:bid=)
289
+ Sidekiq::Testing.server_middleware.invoke(instance, job, job["queue"]) do
290
+ execute_job(instance, job["args"])
272
291
  end
273
292
  end
274
293
 
@@ -310,6 +329,6 @@ module Sidekiq
310
329
  end
311
330
  end
312
331
 
313
- if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
332
+ if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING # rubocop:disable Style/GlobalVars
314
333
  warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
315
334
  end
@@ -7,13 +7,26 @@ 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
16
+ end
17
+
18
+ def batching?
19
+ Thread.current[:sidekiq_batch]
10
20
  end
11
21
 
12
22
  def push(item)
23
+ # 6160 we can't support both Sidekiq::Batch and transactions.
24
+ return @redis_client.push(item) if batching?
25
+
13
26
  # pre-allocate the JID so we can return it immediately and
14
27
  # save it to the database as part of the transaction.
15
28
  item["jid"] ||= SecureRandom.hex(12)
16
- AfterCommitEverywhere.after_commit { @redis_client.push(item) }
29
+ @transaction_backend.call { @redis_client.push(item) }
17
30
  item["jid"]
18
31
  end
19
32
 
@@ -31,10 +44,12 @@ end
31
44
  # Use `Sidekiq.transactional_push!` in your sidekiq.rb initializer
32
45
  module Sidekiq
33
46
  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)
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
38
53
  end
39
54
 
40
55
  Sidekiq.default_job_options["client_class"] = Sidekiq::TransactionAwareClient
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "7.1.4"
5
- MAJOR = 7
4
+ VERSION = "8.0.9"
5
+ MAJOR = 8
6
+
7
+ def self.gem_version
8
+ Gem::Version.new(VERSION)
9
+ end
6
10
  end