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.
- checksums.yaml +4 -4
- data/Changes.md +340 -20
- data/README.md +43 -35
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiq +3 -8
- data/bin/sidekiqload +213 -118
- data/bin/sidekiqmon +3 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +243 -162
- data/lib/sidekiq/capsule.rb +132 -0
- data/lib/sidekiq/cli.rb +60 -75
- data/lib/sidekiq/client.rb +87 -38
- data/lib/sidekiq/component.rb +26 -1
- data/lib/sidekiq/config.rb +311 -0
- data/lib/sidekiq/deploy.rb +64 -0
- data/lib/sidekiq/embedded.rb +63 -0
- data/lib/sidekiq/fetch.rb +11 -14
- data/lib/sidekiq/iterable_job.rb +55 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +294 -0
- data/lib/sidekiq/job.rb +382 -10
- data/lib/sidekiq/job_logger.rb +8 -7
- data/lib/sidekiq/job_retry.rb +42 -19
- data/lib/sidekiq/job_util.rb +53 -15
- data/lib/sidekiq/launcher.rb +71 -65
- data/lib/sidekiq/logger.rb +2 -27
- data/lib/sidekiq/manager.rb +9 -11
- data/lib/sidekiq/metrics/query.rb +9 -4
- data/lib/sidekiq/metrics/shared.rb +21 -9
- data/lib/sidekiq/metrics/tracking.rb +40 -26
- data/lib/sidekiq/middleware/chain.rb +19 -18
- data/lib/sidekiq/middleware/current_attributes.rb +85 -20
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +18 -4
- data/lib/sidekiq/paginator.rb +8 -2
- data/lib/sidekiq/processor.rb +62 -57
- data/lib/sidekiq/rails.rb +27 -10
- data/lib/sidekiq/redis_client_adapter.rb +31 -71
- data/lib/sidekiq/redis_connection.rb +44 -115
- data/lib/sidekiq/ring_buffer.rb +2 -0
- data/lib/sidekiq/scheduled.rb +22 -23
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +37 -46
- data/lib/sidekiq/transaction_aware_client.rb +11 -5
- data/lib/sidekiq/version.rb +6 -1
- data/lib/sidekiq/web/action.rb +29 -7
- data/lib/sidekiq/web/application.rb +82 -28
- data/lib/sidekiq/web/csrf_protection.rb +10 -7
- data/lib/sidekiq/web/helpers.rb +110 -49
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +70 -17
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +78 -274
- data/sidekiq.gemspec +13 -10
- data/web/assets/javascripts/application.js +44 -0
- data/web/assets/javascripts/base-charts.js +106 -0
- data/web/assets/javascripts/dashboard-charts.js +194 -0
- data/web/assets/javascripts/dashboard.js +17 -233
- data/web/assets/javascripts/metrics.js +151 -115
- data/web/assets/stylesheets/application-dark.css +4 -0
- data/web/assets/stylesheets/application-rtl.css +10 -89
- data/web/assets/stylesheets/application.css +56 -296
- data/web/locales/ar.yml +70 -70
- data/web/locales/cs.yml +62 -62
- data/web/locales/da.yml +60 -53
- data/web/locales/de.yml +65 -65
- data/web/locales/el.yml +2 -7
- data/web/locales/en.yml +81 -71
- data/web/locales/es.yml +68 -68
- data/web/locales/fa.yml +65 -65
- data/web/locales/fr.yml +80 -67
- data/web/locales/gd.yml +98 -0
- data/web/locales/he.yml +65 -64
- data/web/locales/hi.yml +59 -59
- data/web/locales/it.yml +85 -54
- data/web/locales/ja.yml +67 -70
- data/web/locales/ko.yml +52 -52
- data/web/locales/lt.yml +66 -66
- data/web/locales/nb.yml +61 -61
- data/web/locales/nl.yml +52 -52
- data/web/locales/pl.yml +45 -45
- data/web/locales/pt-br.yml +78 -69
- data/web/locales/pt.yml +51 -51
- data/web/locales/ru.yml +67 -66
- data/web/locales/sv.yml +53 -53
- data/web/locales/ta.yml +60 -60
- data/web/locales/tr.yml +100 -0
- data/web/locales/uk.yml +85 -61
- data/web/locales/ur.yml +64 -64
- data/web/locales/vi.yml +67 -67
- data/web/locales/zh-cn.yml +20 -19
- data/web/locales/zh-tw.yml +10 -2
- data/web/views/_footer.erb +16 -2
- data/web/views/_job_info.erb +18 -2
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_paging.erb +2 -0
- data/web/views/_poll_link.erb +1 -1
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +46 -35
- data/web/views/dashboard.erb +32 -8
- data/web/views/filtering.erb +6 -0
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +47 -26
- data/web/views/metrics_for_job.erb +43 -71
- data/web/views/morgue.erb +7 -11
- data/web/views/queue.erb +11 -15
- data/web/views/queues.erb +9 -3
- data/web/views/retries.erb +5 -9
- data/web/views/scheduled.erb +12 -13
- metadata +66 -41
- data/lib/sidekiq/delay.rb +0 -43
- data/lib/sidekiq/extensions/action_mailer.rb +0 -48
- data/lib/sidekiq/extensions/active_record.rb +0 -43
- data/lib/sidekiq/extensions/class_methods.rb +0 -43
- data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
- data/lib/sidekiq/metrics/deploy.rb +0 -47
- data/lib/sidekiq/worker.rb +0 -370
- data/web/assets/javascripts/graph.js +0 -16
- /data/{LICENSE → LICENSE.txt} +0 -0
@@ -1,15 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
40
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
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
|
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
|
11
|
+
symbolized_options = deep_symbolize_keys(options)
|
12
|
+
symbolized_options[:url] ||= determine_redis_provider
|
83
13
|
|
84
|
-
|
85
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
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
|
-
|
103
|
-
|
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 =
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
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
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
data/lib/sidekiq/ring_buffer.rb
CHANGED
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -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("
|
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
|
-
|
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
|
-
|
36
|
-
|
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
|
-
|
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.
|
55
|
-
rescue
|
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(
|
74
|
-
@config =
|
75
|
-
@enq = (
|
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
|
88
|
+
@enq.terminate
|
86
89
|
|
87
|
-
|
88
|
-
|
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
|
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
|
196
|
+
return 0 unless redis { |conn| conn.set("process_cleanup", "1", "NX", "EX", "60") }
|
198
197
|
|
199
198
|
count = 0
|
200
|
-
|
201
|
-
procs = conn.
|
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")
|
data/lib/sidekiq/systemd.rb
CHANGED
data/lib/sidekiq/testing.rb
CHANGED
@@ -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 :
|
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
|
-
|
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.
|
25
|
+
self.__local_test_mode = nil
|
19
26
|
end
|
20
27
|
else
|
21
|
-
self.
|
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
|
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 =
|
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
|
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
|
-
#
|
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
|
-
|
293
|
-
|
294
|
-
|
295
|
-
Sidekiq::Testing.server_middleware.invoke(
|
296
|
-
execute_job(
|
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
|
-
|
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.
|
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
|