# frozen_string_literal: trueclassRedisClientclassSentinelConfigincludeConfig::CommonSENTINEL_DELAY=0.25DEFAULT_RECONNECT_ATTEMPTS=2attr_reader:namedefinitialize(sentinels:,sentinel_password: nil,sentinel_username: nil,role: :master,name: nil,url: nil,**client_config)unless%i(master replica slave).include?(role.to_sym)raiseArgumentError,"Expected role to be either :master or :replica, got: #{role.inspect}"endifurlurl_config=URLConfig.new(url)client_config={username: url_config.username,password: url_config.password,db: url_config.db,}.compact.merge(client_config)name||=url_config.hostend@name=nameunless@nameraiseArgumentError,"RedisClient::SentinelConfig requires either a name or an url with a host"end@to_list_of_hash=@to_hash=nilpassword=ifsentinel_password&&!sentinel_password.respond_to?(:call)->(_){sentinel_password}elsesentinel_passwordend@extra_config={username: sentinel_username,password: password,db: nil,}ifclient_config[:protocol]==2@extra_config[:protocol]=client_config[:protocol]@to_list_of_hash=lambdado|may_be_a_list|ifmay_be_a_list.is_a?(Array)may_be_a_list.map{|l|l.each_slice(2).to_h}elsemay_be_a_listendendend@sentinels={}.compare_by_identity@role=role.to_sym@mutex=Mutex.new@config=nilclient_config[:reconnect_attempts]||=DEFAULT_RECONNECT_ATTEMPTS@client_config=client_config||{}super(**client_config)@sentinel_configs=sentinels_to_configs(sentinels)enddefsentinels@mutex.synchronizedo@sentinel_configs.dupendenddefreset@mutex.synchronizedo@config=nilendenddefhostconfig.hostenddefportconfig.portenddefpathnilenddefretry_connecting?(attempt,error)resetunlesserror.is_a?(TimeoutError)superenddefsentinel?trueenddefcheck_role!(role)if@role==:masterunlessrole=="master"sleepSENTINEL_DELAYraiseFailoverError,"Expected to connect to a master, but the server is a replica"endelseunlessrole=="slave"sleepSENTINEL_DELAYraiseFailoverError,"Expected to connect to a replica, but the server is a master"endendenddefresolved?@mutex.synchronizedo!!@configendendprivatedefsentinels_to_configs(sentinels)sentinels.mapdo|sentinel|casesentinelwhenStringConfig.new(**@client_config,**@extra_config,url: sentinel)elseConfig.new(**@client_config,**@extra_config,**sentinel)endendenddefconfig@mutex.synchronizedo@config||=if@role==:masterresolve_masterelseresolve_replicaendendenddefresolve_mastereach_sentineldo|sentinel_client|host,port=sentinel_client.call("SENTINEL","get-master-addr-by-name",@name)nextunlesshost&&portrefresh_sentinels(sentinel_client)returnConfig.new(host: host,port: Integer(port),**@client_config)endrescueConnectionErrorraiseConnectionError,"No sentinels available"elseraiseConnectionError,"Couldn't locate a replica for role: #{@name}"enddefsentinel_client(sentinel_config)@sentinels[sentinel_config]||=sentinel_config.new_clientenddefresolve_replicaeach_sentineldo|sentinel_client|replicas=sentinel_client.call("SENTINEL","replicas",@name,&@to_list_of_hash)replicas.reject!do|r|flags=r["flags"].to_s.split(",")flags.include?("s_down")||flags.include?("o_down")endnextifreplicas.empty?replica=replicas.samplereturnConfig.new(host: replica["ip"],port: Integer(replica["port"]),**@client_config)endrescueConnectionErrorraiseConnectionError,"No sentinels available"elseraiseConnectionError,"Couldn't locate a replica for role: #{@name}"enddefeach_sentinellast_error=nil@sentinel_configs.dup.eachdo|sentinel_config|sentinel_client=sentinel_client(sentinel_config)success=truebeginyieldsentinel_clientrescueRedisClient::Error=>errorlast_error=errorsuccess=falsesleepSENTINEL_DELAYensureifsuccess@sentinel_configs.unshift(@sentinel_configs.delete(sentinel_config))end# Redis Sentinels may be configured to have a lower maxclients setting than# the Redis nodes. Close the connection to the Sentinel node to avoid using# a connection.sentinel_client.closeendendraiselast_erroriflast_errorenddefrefresh_sentinels(sentinel_client)sentinel_response=sentinel_client.call("SENTINEL","sentinels",@name,&@to_list_of_hash)sentinels=sentinel_response.mapdo|sentinel|{host: sentinel.fetch("ip"),port: Integer(sentinel.fetch("port"))}endnew_sentinels=sentinels.selectdo|sentinel|@sentinel_configs.none?do|sentinel_config|sentinel_config.host==sentinel.fetch(:host)&&sentinel_config.port==sentinel.fetch(:port)endend@sentinel_configs.concatsentinels_to_configs(new_sentinels)endendend