sidekiq 6.5.12 → 7.3.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +340 -20
  3. data/README.md +43 -35
  4. data/bin/multi_queue_bench +271 -0
  5. data/bin/sidekiq +3 -8
  6. data/bin/sidekiqload +213 -118
  7. data/bin/sidekiqmon +3 -0
  8. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
  9. data/lib/generators/sidekiq/job_generator.rb +2 -0
  10. data/lib/sidekiq/api.rb +243 -162
  11. data/lib/sidekiq/capsule.rb +132 -0
  12. data/lib/sidekiq/cli.rb +60 -75
  13. data/lib/sidekiq/client.rb +87 -38
  14. data/lib/sidekiq/component.rb +26 -1
  15. data/lib/sidekiq/config.rb +311 -0
  16. data/lib/sidekiq/deploy.rb +64 -0
  17. data/lib/sidekiq/embedded.rb +63 -0
  18. data/lib/sidekiq/fetch.rb +11 -14
  19. data/lib/sidekiq/iterable_job.rb +55 -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 +294 -0
  25. data/lib/sidekiq/job.rb +382 -10
  26. data/lib/sidekiq/job_logger.rb +8 -7
  27. data/lib/sidekiq/job_retry.rb +42 -19
  28. data/lib/sidekiq/job_util.rb +53 -15
  29. data/lib/sidekiq/launcher.rb +71 -65
  30. data/lib/sidekiq/logger.rb +2 -27
  31. data/lib/sidekiq/manager.rb +9 -11
  32. data/lib/sidekiq/metrics/query.rb +9 -4
  33. data/lib/sidekiq/metrics/shared.rb +21 -9
  34. data/lib/sidekiq/metrics/tracking.rb +40 -26
  35. data/lib/sidekiq/middleware/chain.rb +19 -18
  36. data/lib/sidekiq/middleware/current_attributes.rb +85 -20
  37. data/lib/sidekiq/middleware/modules.rb +2 -0
  38. data/lib/sidekiq/monitor.rb +18 -4
  39. data/lib/sidekiq/paginator.rb +8 -2
  40. data/lib/sidekiq/processor.rb +62 -57
  41. data/lib/sidekiq/rails.rb +27 -10
  42. data/lib/sidekiq/redis_client_adapter.rb +31 -71
  43. data/lib/sidekiq/redis_connection.rb +44 -115
  44. data/lib/sidekiq/ring_buffer.rb +2 -0
  45. data/lib/sidekiq/scheduled.rb +22 -23
  46. data/lib/sidekiq/systemd.rb +2 -0
  47. data/lib/sidekiq/testing.rb +37 -46
  48. data/lib/sidekiq/transaction_aware_client.rb +11 -5
  49. data/lib/sidekiq/version.rb +6 -1
  50. data/lib/sidekiq/web/action.rb +29 -7
  51. data/lib/sidekiq/web/application.rb +82 -28
  52. data/lib/sidekiq/web/csrf_protection.rb +10 -7
  53. data/lib/sidekiq/web/helpers.rb +110 -49
  54. data/lib/sidekiq/web/router.rb +5 -2
  55. data/lib/sidekiq/web.rb +70 -17
  56. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  57. data/lib/sidekiq.rb +78 -274
  58. data/sidekiq.gemspec +13 -10
  59. data/web/assets/javascripts/application.js +44 -0
  60. data/web/assets/javascripts/base-charts.js +106 -0
  61. data/web/assets/javascripts/dashboard-charts.js +194 -0
  62. data/web/assets/javascripts/dashboard.js +17 -233
  63. data/web/assets/javascripts/metrics.js +151 -115
  64. data/web/assets/stylesheets/application-dark.css +4 -0
  65. data/web/assets/stylesheets/application-rtl.css +10 -89
  66. data/web/assets/stylesheets/application.css +56 -296
  67. data/web/locales/ar.yml +70 -70
  68. data/web/locales/cs.yml +62 -62
  69. data/web/locales/da.yml +60 -53
  70. data/web/locales/de.yml +65 -65
  71. data/web/locales/el.yml +2 -7
  72. data/web/locales/en.yml +81 -71
  73. data/web/locales/es.yml +68 -68
  74. data/web/locales/fa.yml +65 -65
  75. data/web/locales/fr.yml +80 -67
  76. data/web/locales/gd.yml +98 -0
  77. data/web/locales/he.yml +65 -64
  78. data/web/locales/hi.yml +59 -59
  79. data/web/locales/it.yml +85 -54
  80. data/web/locales/ja.yml +67 -70
  81. data/web/locales/ko.yml +52 -52
  82. data/web/locales/lt.yml +66 -66
  83. data/web/locales/nb.yml +61 -61
  84. data/web/locales/nl.yml +52 -52
  85. data/web/locales/pl.yml +45 -45
  86. data/web/locales/pt-br.yml +78 -69
  87. data/web/locales/pt.yml +51 -51
  88. data/web/locales/ru.yml +67 -66
  89. data/web/locales/sv.yml +53 -53
  90. data/web/locales/ta.yml +60 -60
  91. data/web/locales/tr.yml +100 -0
  92. data/web/locales/uk.yml +85 -61
  93. data/web/locales/ur.yml +64 -64
  94. data/web/locales/vi.yml +67 -67
  95. data/web/locales/zh-cn.yml +20 -19
  96. data/web/locales/zh-tw.yml +10 -2
  97. data/web/views/_footer.erb +16 -2
  98. data/web/views/_job_info.erb +18 -2
  99. data/web/views/_metrics_period_select.erb +12 -0
  100. data/web/views/_paging.erb +2 -0
  101. data/web/views/_poll_link.erb +1 -1
  102. data/web/views/_summary.erb +7 -7
  103. data/web/views/busy.erb +46 -35
  104. data/web/views/dashboard.erb +32 -8
  105. data/web/views/filtering.erb +6 -0
  106. data/web/views/layout.erb +6 -6
  107. data/web/views/metrics.erb +47 -26
  108. data/web/views/metrics_for_job.erb +43 -71
  109. data/web/views/morgue.erb +7 -11
  110. data/web/views/queue.erb +11 -15
  111. data/web/views/queues.erb +9 -3
  112. data/web/views/retries.erb +5 -9
  113. data/web/views/scheduled.erb +12 -13
  114. metadata +66 -41
  115. data/lib/sidekiq/delay.rb +0 -43
  116. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  117. data/lib/sidekiq/extensions/active_record.rb +0 -43
  118. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  119. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  120. data/lib/sidekiq/metrics/deploy.rb +0 -47
  121. data/lib/sidekiq/worker.rb +0 -370
  122. data/web/assets/javascripts/graph.js +0 -16
  123. /data/{LICENSE → LICENSE.txt} +0 -0
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "connection_pool"
3
+ require "set"
4
4
  require "redis_client"
5
5
  require "redis_client/decorator"
6
- require "uri"
7
6
 
8
7
  module Sidekiq
9
8
  class RedisClientAdapter
10
9
  BaseError = RedisClient::Error
11
10
  CommandError = RedisClient::CommandError
12
11
 
12
+ # You can add/remove items or clear the whole thing if you don't want deprecation warnings.
13
+ DEPRECATED_COMMANDS = %i[rpoplpush zrangebyscore zrevrange zrevrangebyscore getset hmset setex setnx].to_set
14
+
13
15
  module CompatMethods
14
16
  def info
15
17
  @client.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
@@ -19,30 +21,28 @@ module Sidekiq
19
21
  @client.call("EVALSHA", sha, keys.size, *keys, *argv)
20
22
  end
21
23
 
22
- def brpoplpush(*args)
23
- @client.blocking_call(false, "BRPOPLPUSH", *args)
24
- end
25
-
26
- def brpop(*args)
27
- @client.blocking_call(false, "BRPOP", *args)
28
- end
29
-
30
- def set(*args)
31
- @client.call("SET", *args) { |r| r == "OK" }
32
- end
33
- ruby2_keywords :set if respond_to?(:ruby2_keywords, true)
34
-
35
- def sismember(*args)
36
- @client.call("SISMEMBER", *args) { |c| c > 0 }
37
- end
24
+ # this is the set of Redis commands used by Sidekiq. Not guaranteed
25
+ # to be comprehensive, we use this as a performance enhancement to
26
+ # avoid calling method_missing on most commands
27
+ USED_COMMANDS = %w[bitfield bitfield_ro del exists expire flushdb
28
+ get hdel hget hgetall hincrby hlen hmget hset hsetnx incr incrby
29
+ lindex llen lmove lpop lpush lrange lrem mget mset ping pttl
30
+ publish rpop rpush sadd scard script set sismember smembers
31
+ srem ttl type unlink zadd zcard zincrby zrange zrem
32
+ zremrangebyrank zremrangebyscore]
38
33
 
39
- def exists?(key)
40
- @client.call("EXISTS", key) { |c| c > 0 }
34
+ USED_COMMANDS.each do |name|
35
+ define_method(name) do |*args, **kwargs|
36
+ @client.call(name, *args, **kwargs)
37
+ end
41
38
  end
42
39
 
43
40
  private
44
41
 
42
+ # this allows us to use methods like `conn.hmset(...)` instead of having to use
43
+ # redis-client's native `conn.call("hmset", ...)`
45
44
  def method_missing(*args, &block)
45
+ warn("[sidekiq#5788] Redis has deprecated the `#{args.first}`command, called at #{caller(1..1)}") if DEPRECATED_COMMANDS.include?(args.first)
46
46
  @client.call(*args, *block)
47
47
  end
48
48
  ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
@@ -55,47 +55,8 @@ module Sidekiq
55
55
  CompatClient = RedisClient::Decorator.create(CompatMethods)
56
56
 
57
57
  class CompatClient
58
- %i[scan sscan zscan hscan].each do |method|
59
- alias_method :"#{method}_each", method
60
- undef_method method
61
- end
62
-
63
- def disconnect!
64
- @client.close
65
- end
66
-
67
- def connection
68
- {id: @client.id}
69
- end
70
-
71
- def redis
72
- self
73
- end
74
-
75
- def _client
76
- @client
77
- end
78
-
79
- def message
80
- yield nil, @queue.pop
81
- end
82
-
83
- # NB: this method does not return
84
- def subscribe(chan)
85
- @queue = ::Queue.new
86
-
87
- pubsub = @client.pubsub
88
- pubsub.call("subscribe", chan)
89
-
90
- loop do
91
- evt = pubsub.next_event
92
- next if evt.nil?
93
- next unless evt[0] == "message" && evt[1] == chan
94
-
95
- (_, _, msg) = evt
96
- @queue << msg
97
- yield self
98
- end
58
+ def config
59
+ @client.config
99
60
  end
100
61
  end
101
62
 
@@ -103,6 +64,13 @@ module Sidekiq
103
64
  opts = client_opts(options)
104
65
  @config = if opts.key?(:sentinels)
105
66
  RedisClient.sentinel(**opts)
67
+ elsif opts.key?(:nodes)
68
+ # Sidekiq does not support Redis clustering but Sidekiq Enterprise's
69
+ # rate limiters are cluster-safe so we can scale to millions
70
+ # of rate limiters using a Redis cluster. This requires the
71
+ # `redis-cluster-client` gem.
72
+ # Sidekiq::Limiter.redis = { nodes: [...] }
73
+ RedisClient.cluster(**opts)
106
74
  else
107
75
  RedisClient.config(**opts)
108
76
  end
@@ -118,9 +86,7 @@ module Sidekiq
118
86
  opts = options.dup
119
87
 
120
88
  if opts[:namespace]
121
- Sidekiq.logger.error("Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature isn't supported by redis-client. " \
122
- "Either use the redis adapter or remove the namespace.")
123
- Kernel.exit(-127)
89
+ 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."
124
90
  end
125
91
 
126
92
  opts.delete(:size)
@@ -131,13 +97,9 @@ module Sidekiq
131
97
  opts.delete(:network_timeout)
132
98
  end
133
99
 
134
- if opts[:driver]
135
- opts[:driver] = opts[:driver].to_sym
136
- end
137
-
138
100
  opts[:name] = opts.delete(:master_name) if opts.key?(:master_name)
139
101
  opts[:role] = opts[:role].to_sym if opts.key?(:role)
140
- opts.delete(:url) if opts.key?(:sentinels)
102
+ opts[:driver] = opts[:driver].to_sym if opts.key?(:driver)
141
103
 
142
104
  # Issue #3303, redis-rb will silently retry an operation.
143
105
  # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
@@ -150,5 +112,3 @@ module Sidekiq
150
112
  end
151
113
  end
152
114
  end
153
-
154
- Sidekiq::RedisConnection.adapter = Sidekiq::RedisClientAdapter
@@ -1,128 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "connection_pool"
4
- require "redis"
5
4
  require "uri"
5
+ require "sidekiq/redis_client_adapter"
6
6
 
7
7
  module Sidekiq
8
8
  module RedisConnection
9
- class RedisAdapter
10
- BaseError = Redis::BaseError
11
- CommandError = Redis::CommandError
12
-
13
- def initialize(options)
14
- warn("Usage of the 'redis' gem within Sidekiq itself is deprecated, Sidekiq 7.0 will only use the new, simpler 'redis-client' gem", caller) if ENV["SIDEKIQ_REDIS_CLIENT"] == "1"
15
- @options = options
16
- end
17
-
18
- def new_client
19
- namespace = @options[:namespace]
20
-
21
- client = Redis.new client_opts(@options)
22
- if namespace
23
- begin
24
- require "redis/namespace"
25
- Redis::Namespace.new(namespace, redis: client)
26
- rescue LoadError
27
- Sidekiq.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \
28
- "Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.")
29
- exit(-127)
30
- end
31
- else
32
- client
33
- end
34
- end
35
-
36
- private
37
-
38
- def client_opts(options)
39
- opts = options.dup
40
- if opts[:namespace]
41
- opts.delete(:namespace)
42
- end
43
-
44
- if opts[:network_timeout]
45
- opts[:timeout] = opts[:network_timeout]
46
- opts.delete(:network_timeout)
47
- end
48
-
49
- # Issue #3303, redis-rb will silently retry an operation.
50
- # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
51
- # is performed twice but I believe this is much, much rarer
52
- # than the reconnect silently fixing a problem; we keep it
53
- # on by default.
54
- opts[:reconnect_attempts] ||= 1
55
-
56
- opts
57
- end
58
- end
59
-
60
- @adapter = RedisAdapter
61
-
62
9
  class << self
63
- attr_reader :adapter
64
-
65
- # RedisConnection.adapter = :redis
66
- # RedisConnection.adapter = :redis_client
67
- def adapter=(adapter)
68
- raise "no" if adapter == self
69
- result = case adapter
70
- when :redis
71
- RedisAdapter
72
- when Class
73
- adapter
74
- else
75
- require "sidekiq/#{adapter}_adapter"
76
- nil
77
- end
78
- @adapter = result if result
79
- end
80
-
81
10
  def create(options = {})
82
- symbolized_options = options.transform_keys(&:to_sym)
11
+ symbolized_options = deep_symbolize_keys(options)
12
+ symbolized_options[:url] ||= determine_redis_provider
83
13
 
84
- if !symbolized_options[:url] && (u = determine_redis_provider)
85
- symbolized_options[:url] = u
86
- end
14
+ logger = symbolized_options.delete(:logger)
15
+ logger&.info { "Sidekiq #{Sidekiq::VERSION} connecting to Redis with options #{scrub(symbolized_options)}" }
87
16
 
88
- size = if symbolized_options[:size]
89
- symbolized_options[:size]
90
- elsif Sidekiq.server?
91
- # Give ourselves plenty of connections. pool is lazy
92
- # so we won't create them until we need them.
93
- Sidekiq[:concurrency] + 5
94
- elsif ENV["RAILS_MAX_THREADS"]
95
- Integer(ENV["RAILS_MAX_THREADS"])
96
- else
97
- 5
98
- end
17
+ raise "Sidekiq 7+ does not support Redis protocol 2" if symbolized_options[:protocol] == 2
18
+
19
+ safe = !!symbolized_options.delete(:cluster_safe)
20
+ raise ":nodes not allowed, Sidekiq is not safe to run on Redis Cluster" if !safe && symbolized_options.key?(:nodes)
99
21
 
100
- verify_sizing(size, Sidekiq[:concurrency]) if Sidekiq.server?
22
+ size = symbolized_options.delete(:size) || 5
23
+ pool_timeout = symbolized_options.delete(:pool_timeout) || 1
24
+ pool_name = symbolized_options.delete(:pool_name)
101
25
 
102
- pool_timeout = symbolized_options[:pool_timeout] || 1
103
- log_info(symbolized_options)
26
+ # Default timeout in redis-client is 1 second, which can be too aggressive
27
+ # if the Sidekiq process is CPU-bound. With 10-15 threads and a thread quantum of 100ms,
28
+ # it can be easy to get the occasional ReadTimeoutError. You can still provide
29
+ # a smaller timeout explicitly:
30
+ # config.redis = { url: "...", timeout: 1 }
31
+ symbolized_options[:timeout] ||= 3
104
32
 
105
- redis_config = adapter.new(symbolized_options)
106
- ConnectionPool.new(timeout: pool_timeout, size: size) do
33
+ redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
34
+ ConnectionPool.new(timeout: pool_timeout, size: size, name: pool_name) do
107
35
  redis_config.new_client
108
36
  end
109
37
  end
110
38
 
111
39
  private
112
40
 
113
- # Sidekiq needs many concurrent Redis connections.
114
- #
115
- # We need a connection for each Processor.
116
- # We need a connection for Pro's real-time change listener
117
- # We need a connection to various features to call Redis every few seconds:
118
- # - the process heartbeat.
119
- # - enterprise's leader election
120
- # - enterprise's cron support
121
- def verify_sizing(size, concurrency)
122
- raise ArgumentError, "Your Redis connection pool is too small for Sidekiq. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size < (concurrency + 2)
41
+ def deep_symbolize_keys(object)
42
+ case object
43
+ when Hash
44
+ object.each_with_object({}) do |(key, value), result|
45
+ result[key.to_sym] = deep_symbolize_keys(value)
46
+ end
47
+ when Array
48
+ object.map { |e| deep_symbolize_keys(e) }
49
+ else
50
+ object
51
+ end
123
52
  end
124
53
 
125
- def log_info(options)
54
+ def scrub(options)
126
55
  redacted = "REDACTED"
127
56
 
128
57
  # Deep clone so we can muck with these options all we want and exclude
@@ -134,17 +63,19 @@ module Sidekiq
134
63
  uri.password = redacted
135
64
  scrubbed_options[:url] = uri.to_s
136
65
  end
137
- if scrubbed_options[:password]
138
- scrubbed_options[:password] = redacted
139
- end
66
+ scrubbed_options[:password] = redacted if scrubbed_options[:password]
67
+ scrubbed_options[:sentinel_password] = redacted if scrubbed_options[:sentinel_password]
140
68
  scrubbed_options[:sentinels]&.each do |sentinel|
141
- sentinel[:password] = redacted if sentinel[:password]
142
- end
143
- if Sidekiq.server?
144
- Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with #{adapter.name} options #{scrubbed_options}")
145
- else
146
- Sidekiq.logger.debug("#{Sidekiq::NAME} client with #{adapter.name} options #{scrubbed_options}")
69
+ if sentinel.is_a?(String)
70
+ if (uri = URI(sentinel)) && uri.password
71
+ uri.password = redacted
72
+ sentinel.replace(uri.to_s)
73
+ end
74
+ elsif sentinel[:password]
75
+ sentinel[:password] = redacted
76
+ end
147
77
  end
78
+ scrubbed_options
148
79
  end
149
80
 
150
81
  def determine_redis_provider
@@ -166,9 +97,7 @@ module Sidekiq
166
97
  EOM
167
98
  end
168
99
 
169
- ENV[
170
- p || "REDIS_URL"
171
- ]
100
+ ENV[p.to_s] || ENV["REDIS_URL"]
172
101
  end
173
102
  end
174
103
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "forwardable"
2
4
 
3
5
  module Sidekiq
@@ -8,16 +8,20 @@ module Sidekiq
8
8
  SETS = %w[retry schedule]
9
9
 
10
10
  class Enq
11
+ include Sidekiq::Component
12
+
11
13
  LUA_ZPOPBYSCORE = <<~LUA
12
14
  local key, now = KEYS[1], ARGV[1]
13
- local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1)
15
+ local jobs = redis.call("zrange", key, "-inf", now, "byscore", "limit", 0, 1)
14
16
  if jobs[1] then
15
17
  redis.call("zrem", key, jobs[1])
16
18
  return jobs[1]
17
19
  end
18
20
  LUA
19
21
 
20
- def initialize
22
+ def initialize(container)
23
+ @config = container
24
+ @client = Sidekiq::Client.new(config: container)
21
25
  @done = false
22
26
  @lua_zpopbyscore_sha = nil
23
27
  end
@@ -25,15 +29,15 @@ module Sidekiq
25
29
  def enqueue_jobs(sorted_sets = SETS)
26
30
  # A job's "score" in Redis is the time at which it should be processed.
27
31
  # Just check Redis for the set of jobs with a timestamp before now.
28
- Sidekiq.redis do |conn|
32
+ redis do |conn|
29
33
  sorted_sets.each do |sorted_set|
30
34
  # Get next item in the queue with score (time to execute) <= now.
31
35
  # We need to go through the list one at a time to reduce the risk of something
32
36
  # going wrong between the time jobs are popped from the scheduled queue and when
33
37
  # they are pushed onto a work queue and losing the jobs.
34
38
  while !@done && (job = zpopbyscore(conn, keys: [sorted_set], argv: [Time.now.to_f.to_s]))
35
- Sidekiq::Client.push(Sidekiq.load_json(job))
36
- Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
39
+ @client.push(Sidekiq.load_json(job))
40
+ logger.debug { "enqueued #{sorted_set}: #{job}" }
37
41
  end
38
42
  end
39
43
  end
@@ -47,12 +51,11 @@ module Sidekiq
47
51
 
48
52
  def zpopbyscore(conn, keys: nil, argv: nil)
49
53
  if @lua_zpopbyscore_sha.nil?
50
- raw_conn = conn.respond_to?(:redis) ? conn.redis : conn
51
- @lua_zpopbyscore_sha = raw_conn.script(:load, LUA_ZPOPBYSCORE)
54
+ @lua_zpopbyscore_sha = conn.script(:load, LUA_ZPOPBYSCORE)
52
55
  end
53
56
 
54
- conn.evalsha(@lua_zpopbyscore_sha, keys, argv)
55
- rescue RedisConnection.adapter::CommandError => e
57
+ conn.call("EVALSHA", @lua_zpopbyscore_sha, keys.size, *keys, *argv)
58
+ rescue RedisClient::CommandError => e
56
59
  raise unless e.message.start_with?("NOSCRIPT")
57
60
 
58
61
  @lua_zpopbyscore_sha = nil
@@ -70,9 +73,9 @@ module Sidekiq
70
73
 
71
74
  INITIAL_WAIT = 10
72
75
 
73
- def initialize(options)
74
- @config = options
75
- @enq = (options[:scheduled_enq] || Sidekiq::Scheduled::Enq).new
76
+ def initialize(config)
77
+ @config = config
78
+ @enq = (config[:scheduled_enq] || Sidekiq::Scheduled::Enq).new(config)
76
79
  @sleeper = ConnectionPool::TimedStack.new
77
80
  @done = false
78
81
  @thread = nil
@@ -82,14 +85,10 @@ module Sidekiq
82
85
  # Shut down this instance, will pause until the thread is dead.
83
86
  def terminate
84
87
  @done = true
85
- @enq.terminate if @enq.respond_to?(:terminate)
88
+ @enq.terminate
86
89
 
87
- if @thread
88
- t = @thread
89
- @thread = nil
90
- @sleeper << 0
91
- t.value
92
- end
90
+ @sleeper << 0
91
+ @thread&.value
93
92
  end
94
93
 
95
94
  def start
@@ -145,7 +144,7 @@ module Sidekiq
145
144
  # In the example above, each process should schedule every 10 seconds on average. We special
146
145
  # case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
147
146
  # As we run more processes, the scheduling interval average will approach an even spread
148
- # 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.
149
148
  #
150
149
  count = process_count
151
150
  interval = poll_interval_average(count)
@@ -194,11 +193,11 @@ module Sidekiq
194
193
  # should never depend on sidekiq/api.
195
194
  def cleanup
196
195
  # dont run cleanup more than once per minute
197
- return 0 unless Sidekiq.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") }
198
197
 
199
198
  count = 0
200
- Sidekiq.redis do |conn|
201
- procs = conn.sscan_each("processes").to_a
199
+ redis do |conn|
200
+ procs = conn.sscan("processes").to_a
202
201
  heartbeats = conn.pipelined { |pipeline|
203
202
  procs.each do |key|
204
203
  pipeline.hget(key, "info")
@@ -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
@@ -51,19 +70,10 @@ module Sidekiq
51
70
  end
52
71
 
53
72
  def server_middleware
54
- @server_chain ||= Middleware::Chain.new
73
+ @server_chain ||= Middleware::Chain.new(Sidekiq.default_configuration)
55
74
  yield @server_chain if block_given?
56
75
  @server_chain
57
76
  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
77
  end
68
78
  end
69
79
 
@@ -73,7 +83,7 @@ module Sidekiq
73
83
  class EmptyQueueError < RuntimeError; end
74
84
 
75
85
  module TestingClient
76
- def raw_push(payloads)
86
+ def atomic_push(conn, payloads)
77
87
  if Sidekiq::Testing.fake?
78
88
  payloads.each do |job|
79
89
  job = Sidekiq.load_json(Sidekiq.dump_json(job))
@@ -83,7 +93,7 @@ module Sidekiq
83
93
  true
84
94
  elsif Sidekiq::Testing.inline?
85
95
  payloads.each do |job|
86
- klass = Sidekiq::Testing.constantize(job["class"])
96
+ klass = Object.const_get(job["class"])
87
97
  job["id"] ||= SecureRandom.hex(12)
88
98
  job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
89
99
  klass.process_job(job_hash)
@@ -102,7 +112,7 @@ module Sidekiq
102
112
  # The Queues class is only for testing the fake queue implementation.
103
113
  # There are 2 data structures involved in tandem. This is due to the
104
114
  # Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
105
- # 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
106
116
  # jobs enqueued, it appeared as though the array didn't change.
107
117
  #
108
118
  # To solve this, we'll keep 2 hashes containing the jobs. One with keys based
@@ -218,25 +228,9 @@ module Sidekiq
218
228
  # assert_equal 1, HardJob.jobs.size
219
229
  # assert_equal :something, HardJob.jobs[0]['args'][0]
220
230
  #
221
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
222
- # MyMailer.delay.send_welcome_email('foo@example.com')
223
- # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
224
- #
225
231
  # You can also clear and drain all job types:
226
232
  #
227
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
228
- # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
229
- #
230
- # MyMailer.delay.send_welcome_email('foo@example.com')
231
- # MyModel.delay.do_something_hard
232
- #
233
- # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
234
- # assert_equal 1, Sidekiq::Extensions::DelayedModel.jobs.size
235
- #
236
- # Sidekiq::Worker.clear_all # or .drain_all
237
- #
238
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
239
- # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
233
+ # Sidekiq::Job.clear_all # or .drain_all
240
234
  #
241
235
  # This can be useful to make sure jobs don't linger between tests:
242
236
  #
@@ -284,16 +278,16 @@ module Sidekiq
284
278
  def perform_one
285
279
  raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
286
280
  next_job = jobs.first
287
- Queues.delete_for(next_job["jid"], queue, to_s)
281
+ Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
288
282
  process_job(next_job)
289
283
  end
290
284
 
291
285
  def process_job(job)
292
- inst = new
293
- inst.jid = job["jid"]
294
- inst.bid = job["bid"] if inst.respond_to?(:bid=)
295
- Sidekiq::Testing.server_middleware.invoke(inst, job, job["queue"]) do
296
- 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"])
297
291
  end
298
292
  end
299
293
 
@@ -318,7 +312,7 @@ module Sidekiq
318
312
  job_classes = jobs.map { |job| job["class"] }.uniq
319
313
 
320
314
  job_classes.each do |job_class|
321
- Sidekiq::Testing.constantize(job_class).drain
315
+ Object.const_get(job_class).drain
322
316
  end
323
317
  end
324
318
  end
@@ -329,13 +323,10 @@ module Sidekiq
329
323
  def jobs_for(klass)
330
324
  jobs.select do |job|
331
325
  marshalled = job["args"][0]
332
- marshalled.index(klass.to_s) && YAML.load(marshalled)[0] == klass
326
+ marshalled.index(klass.to_s) && YAML.safe_load(marshalled)[0] == klass
333
327
  end
334
328
  end
335
329
  end
336
-
337
- Sidekiq::Extensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedMailer)
338
- Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel)
339
330
  end
340
331
 
341
332
  if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING