2015-12-31 15:33:35 -08:00
# frozen_string_literal: true
2019-04-01 18:20:41 +02:00
require " connection_pool "
require " redis "
require " uri "
2012-02-08 16:43:57 -06:00
module Sidekiq
class RedisConnection
2013-04-17 10:07:46 -07:00
class << self
2019-04-01 18:20:41 +02:00
def create ( options = { } )
2020-05-23 00:38:52 +02:00
symbolized_options = options . transform_keys ( & :to_sym )
2016-05-26 12:50:37 +07:00
2020-05-23 00:38:52 +02:00
if ! symbolized_options [ :url ] && ( u = determine_redis_provider )
symbolized_options [ :url ] = u
2020-03-23 10:41:54 -07:00
end
2013-11-24 08:16:37 -06:00
2020-05-23 00:38:52 +02:00
size = if symbolized_options [ :size ]
symbolized_options [ :size ]
2019-04-01 18:20:41 +02:00
elsif Sidekiq . server?
# Give ourselves plenty of connections. pool is lazy
# so we won't create them until we need them.
Sidekiq . options [ :concurrency ] + 5
elsif ENV [ " RAILS_MAX_THREADS " ]
Integer ( ENV [ " RAILS_MAX_THREADS " ] )
else
5
end
2013-04-17 10:07:46 -07:00
2015-10-20 21:25:03 -07:00
verify_sizing ( size , Sidekiq . options [ :concurrency ] ) if Sidekiq . server?
2020-05-23 00:38:52 +02:00
pool_timeout = symbolized_options [ :pool_timeout ] || 1
log_info ( symbolized_options )
2013-04-17 10:07:46 -07:00
2019-04-01 18:20:41 +02:00
ConnectionPool . new ( timeout : pool_timeout , size : size ) do
2020-05-23 00:38:52 +02:00
build_client ( symbolized_options )
2013-04-17 10:07:46 -07:00
end
end
private
2015-10-20 21:25:03 -07:00
# Sidekiq needs a lot of concurrent Redis connections.
#
# We need a connection for each Processor.
# We need a connection for Pro's real-time change listener
# We need a connection to various features to call Redis every few seconds:
# - the process heartbeat.
# - enterprise's leader election
# - enterprise's cron support
def verify_sizing ( size , concurrency )
2019-04-01 18:20:41 +02:00
raise ArgumentError , " Your Redis connection pool is too small for Sidekiq to work. Your pool has #{ size } connections but must have at least #{ concurrency + 2 } " if size < ( concurrency + 2 )
2015-10-20 21:25:03 -07:00
end
2013-10-21 20:28:38 +02:00
def build_client ( options )
namespace = options [ :namespace ]
client = Redis . new client_opts ( options )
2013-04-17 10:07:46 -07:00
if namespace
2015-10-20 21:25:03 -07:00
begin
2019-04-01 18:20:41 +02:00
require " redis/namespace "
Redis :: Namespace . new ( namespace , redis : client )
2015-10-20 21:25:03 -07:00
rescue LoadError
2016-05-26 12:50:37 +07:00
Sidekiq . logger . error ( " Your Redis configuration uses the namespace ' #{ namespace } ' but the redis-namespace gem is not included in the Gemfile. " \
" Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter. " )
2015-10-20 21:25:03 -07:00
exit ( - 127 )
end
2013-04-17 10:07:46 -07:00
else
client
end
end
2013-10-21 20:28:38 +02:00
def client_opts ( options )
2013-10-21 21:36:51 +02:00
opts = options . dup
if opts [ :namespace ]
opts . delete ( :namespace )
2013-07-17 09:02:51 -06:00
end
2013-10-21 20:28:38 +02:00
2013-10-21 21:36:51 +02:00
if opts [ :network_timeout ]
opts [ :timeout ] = opts [ :network_timeout ]
opts . delete ( :network_timeout )
2013-10-21 20:28:38 +02:00
end
2019-04-01 18:20:41 +02:00
opts [ :driver ] || = Redis :: Connection . drivers . last || " ruby "
2017-01-13 08:19:31 -08:00
# Issue #3303, redis-rb will silently retry an operation.
# This can lead to duplicate jobs if Sidekiq::Client's LPUSH
# is performed twice but I believe this is much, much rarer
# than the reconnect silently fixing a problem; we keep it
# on by default.
opts [ :reconnect_attempts ] || = 1
2013-10-21 20:28:38 +02:00
2013-10-21 21:36:51 +02:00
opts
2013-07-17 09:02:51 -06:00
end
2013-10-21 20:28:38 +02:00
def log_info ( options )
2014-06-20 01:12:53 -07:00
redacted = " REDACTED "
2020-04-01 08:11:44 -07:00
2021-10-12 17:14:55 -07:00
# Deep clone so we can muck with these options all we want and exclude
# params from dump-and-load that may contain objects that Marshal is
# unable to safely dump.
keys = options . keys - [ :logger , :ssl_params ]
2020-04-28 23:40:02 -04:00
scrubbed_options = Marshal . load ( Marshal . dump ( options . slice ( * keys ) ) )
2013-11-17 10:20:28 -05:00
if scrubbed_options [ :url ] && ( uri = URI . parse ( scrubbed_options [ :url ] ) ) && uri . password
2014-06-20 01:12:53 -07:00
uri . password = redacted
2013-11-17 10:20:28 -05:00
scrubbed_options [ :url ] = uri . to_s
end
2014-06-20 01:12:53 -07:00
if scrubbed_options [ :password ]
scrubbed_options [ :password ] = redacted
end
2019-11-06 16:49:34 +00:00
scrubbed_options [ :sentinels ] & . each do | sentinel |
sentinel [ :password ] = redacted if sentinel [ :password ]
2019-11-03 06:27:36 -08:00
end
2013-04-17 10:07:46 -07:00
if Sidekiq . server?
2013-11-17 10:20:28 -05:00
Sidekiq . logger . info ( " Booting Sidekiq #{ Sidekiq :: VERSION } with redis options #{ scrubbed_options } " )
2013-04-17 10:07:46 -07:00
else
2014-06-21 21:01:59 -07:00
Sidekiq . logger . debug ( " #{ Sidekiq :: NAME } client with redis options #{ scrubbed_options } " )
2013-04-17 10:07:46 -07:00
end
2013-04-17 09:53:01 -07:00
end
2013-04-17 10:07:46 -07:00
def determine_redis_provider
2017-06-19 07:42:00 -07:00
# If you have this in your environment:
# MY_REDIS_URL=redis://hostname.example.com:1238/4
# then set:
# REDIS_PROVIDER=MY_REDIS_URL
# and Sidekiq will find your custom URL variable with no custom
# initialization code at all.
2018-09-17 11:51:27 -07:00
#
2019-04-01 18:20:41 +02:00
p = ENV [ " REDIS_PROVIDER " ]
2020-06-17 15:15:20 -07:00
if p && p =~ / : /
2019-04-01 18:20:41 +02:00
raise << ~ EOM
REDIS_PROVIDER should be set to the name of the variable which contains the Redis URL , not a URL itself .
Platforms like Heroku will sell addons that publish a * _URL variable . You need to tell Sidekiq with REDIS_PROVIDER , e . g . :
2018-11-06 08:56:50 -08:00
2019-04-01 18:20:41 +02:00
REDISTOGO_URL = redis : / /some host . example . com : 6379 / 4
2019-08-28 09:59:28 -07:00
REDIS_PROVIDER = REDISTOGO_URL
2019-04-01 18:20:41 +02:00
EOM
2018-11-06 08:56:50 -08:00
end
2018-09-17 11:51:27 -07:00
2017-06-19 07:42:00 -07:00
ENV [
2019-04-01 18:20:41 +02:00
p || " REDIS_URL "
2017-06-19 07:42:00 -07:00
]
2012-03-13 21:19:46 -07:00
end
2012-10-14 14:58:20 -07:00
end
2012-02-08 16:43:57 -06:00
end
end