sidekiq 6.5.10 → 8.1.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 +4 -4
- data/Changes.md +568 -18
- data/README.md +66 -38
- data/bin/kiq +17 -0
- data/bin/lint-herb +13 -0
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiq +3 -8
- data/bin/sidekiqload +215 -118
- data/bin/sidekiqmon +3 -0
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
- data/lib/generators/sidekiq/job_generator.rb +17 -3
- data/lib/generators/sidekiq/templates/job.rb.erb +1 -1
- data/lib/sidekiq/api.rb +526 -218
- data/lib/sidekiq/capsule.rb +135 -0
- data/lib/sidekiq/cli.rb +88 -96
- data/lib/sidekiq/client.rb +114 -47
- data/lib/sidekiq/component.rb +71 -4
- data/lib/sidekiq/config.rb +316 -0
- data/lib/sidekiq/deploy.rb +64 -0
- data/lib/sidekiq/embedded.rb +64 -0
- data/lib/sidekiq/fetch.rb +12 -14
- data/lib/sidekiq/iterable_job.rb +56 -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 +322 -0
- data/lib/sidekiq/job.rb +397 -10
- data/lib/sidekiq/job_logger.rb +14 -11
- data/lib/sidekiq/job_retry.rb +78 -28
- data/lib/sidekiq/job_util.rb +58 -16
- data/lib/sidekiq/launcher.rb +86 -70
- data/lib/sidekiq/loader.rb +57 -0
- data/lib/sidekiq/logger.rb +25 -94
- data/lib/sidekiq/manager.rb +11 -13
- data/lib/sidekiq/metrics/query.rb +84 -48
- data/lib/sidekiq/metrics/shared.rb +29 -14
- data/lib/sidekiq/metrics/tracking.rb +49 -30
- data/lib/sidekiq/middleware/chain.rb +19 -18
- data/lib/sidekiq/middleware/current_attributes.rb +78 -21
- data/lib/sidekiq/middleware/i18n.rb +2 -0
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +15 -5
- data/lib/sidekiq/paginator.rb +26 -4
- data/lib/sidekiq/processor.rb +78 -65
- data/lib/sidekiq/profiler.rb +73 -0
- data/lib/sidekiq/rails.rb +45 -60
- data/lib/sidekiq/redis_client_adapter.rb +35 -72
- data/lib/sidekiq/redis_connection.rb +54 -114
- data/lib/sidekiq/ring_buffer.rb +3 -0
- data/lib/sidekiq/scheduled.rb +28 -30
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/test_api.rb +331 -0
- data/lib/sidekiq/testing/inline.rb +2 -30
- data/lib/sidekiq/testing.rb +2 -342
- data/lib/sidekiq/transaction_aware_client.rb +23 -9
- data/lib/sidekiq/tui/controls.rb +53 -0
- data/lib/sidekiq/tui/filtering.rb +53 -0
- data/lib/sidekiq/tui/tabs/base_tab.rb +230 -0
- data/lib/sidekiq/tui/tabs/busy.rb +104 -0
- data/lib/sidekiq/tui/tabs/dead.rb +14 -0
- data/lib/sidekiq/tui/tabs/home.rb +115 -0
- data/lib/sidekiq/tui/tabs/metrics.rb +114 -0
- data/lib/sidekiq/tui/tabs/queues.rb +83 -0
- data/lib/sidekiq/tui/tabs/retries.rb +14 -0
- data/lib/sidekiq/tui/tabs/scheduled.rb +14 -0
- data/lib/sidekiq/tui/tabs/set_tab.rb +91 -0
- data/lib/sidekiq/tui/tabs.rb +15 -0
- data/lib/sidekiq/tui.rb +390 -0
- data/lib/sidekiq/version.rb +6 -1
- data/lib/sidekiq/web/action.rb +149 -64
- data/lib/sidekiq/web/application.rb +379 -281
- data/lib/sidekiq/web/config.rb +117 -0
- data/lib/sidekiq/web/helpers.rb +190 -71
- data/lib/sidekiq/web/router.rb +62 -75
- data/lib/sidekiq/web.rb +68 -101
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +85 -269
- data/sidekiq.gemspec +15 -11
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status.png +0 -0
- data/web/assets/javascripts/application.js +84 -24
- data/web/assets/javascripts/base-charts.js +120 -0
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/dashboard-charts.js +194 -0
- data/web/assets/javascripts/dashboard.js +18 -234
- data/web/assets/javascripts/metrics.js +138 -120
- data/web/assets/stylesheets/style.css +776 -0
- data/web/locales/ar.yml +72 -70
- data/web/locales/cs.yml +64 -62
- data/web/locales/da.yml +62 -53
- data/web/locales/de.yml +67 -65
- data/web/locales/el.yml +4 -7
- data/web/locales/en.yml +89 -71
- data/web/locales/es.yml +91 -68
- data/web/locales/fa.yml +67 -65
- data/web/locales/fr.yml +82 -67
- data/web/locales/gd.yml +110 -0
- data/web/locales/he.yml +67 -64
- data/web/locales/hi.yml +95 -59
- data/web/locales/it.yml +94 -54
- data/web/locales/ja.yml +69 -70
- data/web/locales/ko.yml +54 -52
- data/web/locales/lt.yml +68 -66
- data/web/locales/nb.yml +63 -61
- data/web/locales/nl.yml +54 -52
- data/web/locales/pl.yml +47 -45
- data/web/locales/{pt-br.yml → pt-BR.yml} +81 -70
- data/web/locales/pt.yml +53 -51
- data/web/locales/ru.yml +69 -66
- data/web/locales/sv.yml +55 -53
- data/web/locales/ta.yml +100 -64
- data/web/locales/tr.yml +102 -0
- data/web/locales/uk.yml +87 -61
- data/web/locales/ur.yml +66 -64
- data/web/locales/vi.yml +69 -67
- data/web/locales/zh-CN.yml +107 -0
- data/web/locales/{zh-tw.yml → zh-TW.yml} +13 -3
- data/web/views/_footer.html.erb +32 -0
- data/web/views/_job_info.html.erb +115 -0
- data/web/views/_metrics_period_select.html.erb +15 -0
- data/web/views/_nav.html.erb +45 -0
- data/web/views/_paging.html.erb +26 -0
- data/web/views/_poll_link.html.erb +4 -0
- data/web/views/_summary.html.erb +40 -0
- data/web/views/busy.html.erb +155 -0
- data/web/views/dashboard.html.erb +104 -0
- data/web/views/dead.html.erb +38 -0
- data/web/views/filtering.html.erb +6 -0
- data/web/views/layout.html.erb +26 -0
- data/web/views/metrics.html.erb +85 -0
- data/web/views/metrics_for_job.html.erb +58 -0
- data/web/views/morgue.html.erb +69 -0
- data/web/views/profiles.html.erb +43 -0
- data/web/views/queue.html.erb +57 -0
- data/web/views/queues.html.erb +46 -0
- data/web/views/retries.html.erb +77 -0
- data/web/views/retry.html.erb +39 -0
- data/web/views/scheduled.html.erb +64 -0
- data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
- metadata +113 -70
- 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/web/csrf_protection.rb +0 -180
- data/lib/sidekiq/worker.rb +0 -370
- data/web/assets/javascripts/graph.js +0 -16
- data/web/assets/stylesheets/application-dark.css +0 -143
- data/web/assets/stylesheets/application-rtl.css +0 -242
- data/web/assets/stylesheets/application.css +0 -999
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/locales/zh-cn.yml +0 -93
- data/web/views/_footer.erb +0 -20
- data/web/views/_job_info.erb +0 -89
- data/web/views/_nav.erb +0 -52
- data/web/views/_paging.erb +0 -23
- data/web/views/_poll_link.erb +0 -4
- data/web/views/_status.erb +0 -4
- data/web/views/_summary.erb +0 -40
- data/web/views/busy.erb +0 -137
- data/web/views/dashboard.erb +0 -84
- data/web/views/dead.erb +0 -34
- data/web/views/layout.erb +0 -42
- data/web/views/metrics.erb +0 -69
- data/web/views/metrics_for_job.erb +0 -87
- data/web/views/morgue.erb +0 -78
- data/web/views/queue.erb +0 -59
- data/web/views/queues.erb +0 -38
- data/web/views/retries.erb +0 -83
- data/web/views/retry.erb +0 -34
- data/web/views/scheduled.erb +0 -57
- /data/{LICENSE → LICENSE.txt} +0 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "connection_pool"
|
|
4
3
|
require "redis_client"
|
|
5
4
|
require "redis_client/decorator"
|
|
6
|
-
require "uri"
|
|
7
5
|
|
|
8
6
|
module Sidekiq
|
|
9
7
|
class RedisClientAdapter
|
|
10
8
|
BaseError = RedisClient::Error
|
|
11
9
|
CommandError = RedisClient::CommandError
|
|
12
10
|
|
|
11
|
+
# You can add/remove items or clear the whole thing if you don't want deprecation warnings.
|
|
12
|
+
DEPRECATED_COMMANDS = %i[rpoplpush zrangebyscore zrevrange zrevrangebyscore getset hmset setex setnx].to_set
|
|
13
|
+
|
|
13
14
|
module CompatMethods
|
|
14
15
|
def info
|
|
15
16
|
@client.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
|
|
@@ -19,31 +20,29 @@ module Sidekiq
|
|
|
19
20
|
@client.call("EVALSHA", sha, keys.size, *keys, *argv)
|
|
20
21
|
end
|
|
21
22
|
|
|
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
|
|
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]
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
USED_COMMANDS.each do |name|
|
|
34
|
+
define_method(name) do |*args, **kwargs|
|
|
35
|
+
@client.call(name, *args, **kwargs)
|
|
36
|
+
end
|
|
41
37
|
end
|
|
42
38
|
|
|
43
39
|
private
|
|
44
40
|
|
|
41
|
+
# this allows us to use methods like `conn.hmset(...)` instead of having to use
|
|
42
|
+
# redis-client's native `conn.call("hmset", ...)`
|
|
45
43
|
def method_missing(*args, &block)
|
|
46
|
-
|
|
44
|
+
warn("[sidekiq#5788] Redis has deprecated the `#{args.first}` command, called at #{caller(1..1)}") if DEPRECATED_COMMANDS.include?(args.first)
|
|
45
|
+
@client.call(*args, &block)
|
|
47
46
|
end
|
|
48
47
|
ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
|
|
49
48
|
|
|
@@ -55,47 +54,8 @@ module Sidekiq
|
|
|
55
54
|
CompatClient = RedisClient::Decorator.create(CompatMethods)
|
|
56
55
|
|
|
57
56
|
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
|
|
57
|
+
def config
|
|
58
|
+
@client.config
|
|
99
59
|
end
|
|
100
60
|
end
|
|
101
61
|
|
|
@@ -103,6 +63,13 @@ module Sidekiq
|
|
|
103
63
|
opts = client_opts(options)
|
|
104
64
|
@config = if opts.key?(:sentinels)
|
|
105
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)
|
|
106
73
|
else
|
|
107
74
|
RedisClient.config(**opts)
|
|
108
75
|
end
|
|
@@ -118,9 +85,7 @@ module Sidekiq
|
|
|
118
85
|
opts = options.dup
|
|
119
86
|
|
|
120
87
|
if opts[:namespace]
|
|
121
|
-
|
|
122
|
-
"Either use the redis adapter or remove the namespace.")
|
|
123
|
-
Kernel.exit(-127)
|
|
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."
|
|
124
89
|
end
|
|
125
90
|
|
|
126
91
|
opts.delete(:size)
|
|
@@ -131,13 +96,9 @@ module Sidekiq
|
|
|
131
96
|
opts.delete(:network_timeout)
|
|
132
97
|
end
|
|
133
98
|
|
|
134
|
-
if opts[:driver]
|
|
135
|
-
opts[:driver] = opts[:driver].to_sym
|
|
136
|
-
end
|
|
137
|
-
|
|
138
99
|
opts[:name] = opts.delete(:master_name) if opts.key?(:master_name)
|
|
139
100
|
opts[:role] = opts[:role].to_sym if opts.key?(:role)
|
|
140
|
-
opts
|
|
101
|
+
opts[:driver] = opts[:driver].to_sym if opts.key?(:driver)
|
|
141
102
|
|
|
142
103
|
# Issue #3303, redis-rb will silently retry an operation.
|
|
143
104
|
# This can lead to duplicate jobs if Sidekiq::Client's LPUSH
|
|
@@ -146,9 +107,11 @@ module Sidekiq
|
|
|
146
107
|
# on by default.
|
|
147
108
|
opts[:reconnect_attempts] ||= 1
|
|
148
109
|
|
|
110
|
+
# Identify ourselves to Redis via CLIENT SETINFO so connections
|
|
111
|
+
# are distinguishable in CLIENT LIST / CLIENT INFO output.
|
|
112
|
+
opts[:driver_info] ||= "sidekiq_v#{Sidekiq::VERSION}"
|
|
113
|
+
|
|
149
114
|
opts
|
|
150
115
|
end
|
|
151
116
|
end
|
|
152
117
|
end
|
|
153
|
-
|
|
154
|
-
Sidekiq::RedisConnection.adapter = Sidekiq::RedisClientAdapter
|
|
@@ -1,150 +1,92 @@
|
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
class << self
|
|
10
|
+
def create(options = {})
|
|
11
|
+
symbolized_options = deep_symbolize_keys(options)
|
|
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)
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@options = options
|
|
16
|
-
end
|
|
16
|
+
logger = symbolized_options.delete(:logger)
|
|
17
|
+
logger&.info { "Sidekiq #{Sidekiq::VERSION} connecting to Redis with options #{scrub(symbolized_options)}" }
|
|
17
18
|
|
|
18
|
-
|
|
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
|
|
19
|
+
raise "Sidekiq 7+ does not support Redis protocol 2" if symbolized_options[:protocol] == 2
|
|
37
20
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if opts[:namespace]
|
|
41
|
-
opts.delete(:namespace)
|
|
42
|
-
end
|
|
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)
|
|
43
23
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
end
|
|
24
|
+
size = symbolized_options.delete(:size) || 5
|
|
25
|
+
pool_timeout = symbolized_options.delete(:pool_timeout) || 1
|
|
26
|
+
symbolized_options.delete(:pool_name)
|
|
48
27
|
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
|
|
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
|
|
55
34
|
|
|
56
|
-
|
|
35
|
+
redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
|
|
36
|
+
ConnectionPool.new(timeout: pool_timeout, size: size) do
|
|
37
|
+
redis_config.new_client
|
|
38
|
+
end
|
|
57
39
|
end
|
|
58
|
-
end
|
|
59
40
|
|
|
60
|
-
|
|
41
|
+
private
|
|
61
42
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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 }
|
|
74
47
|
else
|
|
75
|
-
|
|
76
|
-
nil
|
|
48
|
+
pwd
|
|
77
49
|
end
|
|
78
|
-
@adapter = result if result
|
|
79
50
|
end
|
|
80
51
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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"])
|
|
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) }
|
|
96
60
|
else
|
|
97
|
-
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
verify_sizing(size, Sidekiq[:concurrency]) if Sidekiq.server?
|
|
101
|
-
|
|
102
|
-
pool_timeout = symbolized_options[:pool_timeout] || 1
|
|
103
|
-
log_info(symbolized_options)
|
|
104
|
-
|
|
105
|
-
redis_config = adapter.new(symbolized_options)
|
|
106
|
-
ConnectionPool.new(timeout: pool_timeout, size: size) do
|
|
107
|
-
redis_config.new_client
|
|
61
|
+
object
|
|
108
62
|
end
|
|
109
63
|
end
|
|
110
64
|
|
|
111
|
-
|
|
112
|
-
|
|
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)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def log_info(options)
|
|
65
|
+
def scrub(options)
|
|
126
66
|
redacted = "REDACTED"
|
|
127
67
|
|
|
128
68
|
# Deep clone so we can muck with these options all we want and exclude
|
|
129
69
|
# params from dump-and-load that may contain objects that Marshal is
|
|
130
70
|
# unable to safely dump.
|
|
131
|
-
keys = options.keys - [:logger, :ssl_params]
|
|
71
|
+
keys = options.keys - [:logger, :ssl_params, :password, :sentinel_password]
|
|
132
72
|
scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
|
|
133
73
|
if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
|
|
134
74
|
uri.password = redacted
|
|
135
75
|
scrubbed_options[:url] = uri.to_s
|
|
136
76
|
end
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
end
|
|
77
|
+
scrubbed_options[:password] = redacted if options.key?(:password)
|
|
78
|
+
scrubbed_options[:sentinel_password] = redacted if options.key?(:sentinel_password)
|
|
140
79
|
scrubbed_options[:sentinels]&.each do |sentinel|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
147
88
|
end
|
|
89
|
+
scrubbed_options
|
|
148
90
|
end
|
|
149
91
|
|
|
150
92
|
def determine_redis_provider
|
|
@@ -166,9 +108,7 @@ module Sidekiq
|
|
|
166
108
|
EOM
|
|
167
109
|
end
|
|
168
110
|
|
|
169
|
-
ENV[
|
|
170
|
-
p || "REDIS_URL"
|
|
171
|
-
]
|
|
111
|
+
ENV[p.to_s] || ENV["REDIS_URL"]
|
|
172
112
|
end
|
|
173
113
|
end
|
|
174
114
|
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
|
|
@@ -69,27 +72,25 @@ module Sidekiq
|
|
|
69
72
|
include Sidekiq::Component
|
|
70
73
|
|
|
71
74
|
INITIAL_WAIT = 10
|
|
75
|
+
attr_accessor :rnd
|
|
72
76
|
|
|
73
|
-
def initialize(
|
|
74
|
-
@config =
|
|
75
|
-
@enq = (
|
|
77
|
+
def initialize(config)
|
|
78
|
+
@config = config
|
|
79
|
+
@enq = (config[:scheduled_enq] || Sidekiq::Scheduled::Enq).new(config)
|
|
76
80
|
@sleeper = ConnectionPool::TimedStack.new
|
|
77
81
|
@done = false
|
|
78
82
|
@thread = nil
|
|
79
83
|
@count_calls = 0
|
|
84
|
+
@rnd = Random.new
|
|
80
85
|
end
|
|
81
86
|
|
|
82
87
|
# Shut down this instance, will pause until the thread is dead.
|
|
83
88
|
def terminate
|
|
84
89
|
@done = true
|
|
85
|
-
@enq.terminate
|
|
90
|
+
@enq.terminate
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@thread = nil
|
|
90
|
-
@sleeper << 0
|
|
91
|
-
t.value
|
|
92
|
-
end
|
|
92
|
+
@sleeper << 0
|
|
93
|
+
@thread&.value
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
def start
|
|
@@ -116,9 +117,7 @@ module Sidekiq
|
|
|
116
117
|
private
|
|
117
118
|
|
|
118
119
|
def wait
|
|
119
|
-
@sleeper.pop(random_poll_interval)
|
|
120
|
-
rescue Timeout::Error
|
|
121
|
-
# expected
|
|
120
|
+
@sleeper.pop(timeout: random_poll_interval, exception: false)
|
|
122
121
|
rescue => ex
|
|
123
122
|
# if poll_interval_average hasn't been calculated yet, we can
|
|
124
123
|
# raise an error trying to reach Redis.
|
|
@@ -145,18 +144,18 @@ 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)
|
|
152
151
|
|
|
153
152
|
if count < 10
|
|
154
153
|
# For small clusters, calculate a random interval that is ±50% the desired average.
|
|
155
|
-
interval * rand + interval.to_f / 2
|
|
154
|
+
interval * @rnd.rand + interval.to_f / 2
|
|
156
155
|
else
|
|
157
156
|
# With 10+ processes, we should have enough randomness to get decent polling
|
|
158
157
|
# across the entire timespan
|
|
159
|
-
interval * rand
|
|
158
|
+
interval * @rnd.rand * 2
|
|
160
159
|
end
|
|
161
160
|
end
|
|
162
161
|
|
|
@@ -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")
|
|
@@ -224,8 +223,7 @@ module Sidekiq
|
|
|
224
223
|
total += INITIAL_WAIT unless @config[:poll_interval_average]
|
|
225
224
|
total += (5 * rand)
|
|
226
225
|
|
|
227
|
-
@sleeper.pop(total)
|
|
228
|
-
rescue Timeout::Error
|
|
226
|
+
@sleeper.pop(timeout: total, exception: false)
|
|
229
227
|
ensure
|
|
230
228
|
# periodically clean out the `processes` set in Redis which can collect
|
|
231
229
|
# references to dead processes over time. The process count affects how
|