redis-cluster-client 0.11.5 → 0.12.0
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/lib/redis_client/cluster/command.rb +2 -2
- data/lib/redis_client/cluster/errors.rb +27 -18
- data/lib/redis_client/cluster/node.rb +4 -4
- data/lib/redis_client/cluster/optimistic_locking.rb +15 -6
- data/lib/redis_client/cluster/pipeline.rb +4 -4
- data/lib/redis_client/cluster/router.rb +31 -13
- data/lib/redis_client/cluster/transaction.rb +5 -4
- data/lib/redis_client/cluster_config.rb +16 -2
- metadata +3 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 2465b865cd79c78cca3a1e7e3950989cce94f6675b0497763e03e261d3fa9c6f
         | 
| 4 | 
            +
              data.tar.gz: df607279a58171aca75835b767f3bf5965256db0fcb7b540bfcb3a8f302eb638
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: bfb9031660b642bb14768dc8edc66fb15073be9ca23ddc061d279a351c497a83ff8a3aad21903e2d0a37aa6824da35761188ba4dc1fe8ae0182b54584edaa5e5
         | 
| 7 | 
            +
              data.tar.gz: 9fab63d0595bfb7dac85a07c255a41573ab8d44598605919fd4182e99cf3da585bf931abe63da9e2f470815c6ef0d34ab24a5b7f2de346ba29870fb894c556ee
         | 
| @@ -25,7 +25,7 @@ class RedisClient | |
| 25 25 | 
             
                  )
         | 
| 26 26 |  | 
| 27 27 | 
             
                  class << self
         | 
| 28 | 
            -
                    def load(nodes, slow_command_timeout: -1)
         | 
| 28 | 
            +
                    def load(nodes, slow_command_timeout: -1) # rubocop:disable Metrics/AbcSize
         | 
| 29 29 | 
             
                      cmd = errors = nil
         | 
| 30 30 |  | 
| 31 31 | 
             
                      nodes&.each do |node|
         | 
| @@ -43,7 +43,7 @@ class RedisClient | |
| 43 43 |  | 
| 44 44 | 
             
                      return cmd unless cmd.nil?
         | 
| 45 45 |  | 
| 46 | 
            -
                      raise ::RedisClient::Cluster::InitialSetupError | 
| 46 | 
            +
                      raise ::RedisClient::Cluster::InitialSetupError.from_errors(errors)
         | 
| 47 47 | 
             
                    end
         | 
| 48 48 |  | 
| 49 49 | 
             
                    private
         | 
| @@ -4,51 +4,60 @@ require 'redis_client' | |
| 4 4 |  | 
| 5 5 | 
             
            class RedisClient
         | 
| 6 6 | 
             
              class Cluster
         | 
| 7 | 
            +
                class Error < ::RedisClient::Error
         | 
| 8 | 
            +
                  def with_config(config)
         | 
| 9 | 
            +
                    @config = config
         | 
| 10 | 
            +
                    self
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 7 14 | 
             
                ERR_ARG_NORMALIZATION = ->(arg) { Array[arg].flatten.reject { |e| e.nil? || (e.respond_to?(:empty?) && e.empty?) } }
         | 
| 8 15 |  | 
| 9 16 | 
             
                private_constant :ERR_ARG_NORMALIZATION
         | 
| 10 17 |  | 
| 11 | 
            -
                class InitialSetupError <  | 
| 12 | 
            -
                  def  | 
| 18 | 
            +
                class InitialSetupError < Error
         | 
| 19 | 
            +
                  def self.from_errors(errors)
         | 
| 13 20 | 
             
                    msg = ERR_ARG_NORMALIZATION.call(errors).map(&:message).uniq.join(',')
         | 
| 14 | 
            -
                     | 
| 21 | 
            +
                    new("Redis client could not fetch cluster information: #{msg}")
         | 
| 15 22 | 
             
                  end
         | 
| 16 23 | 
             
                end
         | 
| 17 24 |  | 
| 18 | 
            -
                class OrchestrationCommandNotSupported <  | 
| 19 | 
            -
                  def  | 
| 25 | 
            +
                class OrchestrationCommandNotSupported < Error
         | 
| 26 | 
            +
                  def self.from_command(command)
         | 
| 20 27 | 
             
                    str = ERR_ARG_NORMALIZATION.call(command).map(&:to_s).join(' ').upcase
         | 
| 21 28 | 
             
                    msg = "#{str} command should be used with care " \
         | 
| 22 29 | 
             
                          'only by applications orchestrating Redis Cluster, like redis-cli, ' \
         | 
| 23 30 | 
             
                          'and the command if used out of the right context can leave the cluster ' \
         | 
| 24 31 | 
             
                          'in a wrong state or cause data loss.'
         | 
| 25 | 
            -
                     | 
| 32 | 
            +
                    new(msg)
         | 
| 26 33 | 
             
                  end
         | 
| 27 34 | 
             
                end
         | 
| 28 35 |  | 
| 29 | 
            -
                class ErrorCollection <  | 
| 36 | 
            +
                class ErrorCollection < Error
         | 
| 30 37 | 
             
                  attr_reader :errors
         | 
| 31 38 |  | 
| 32 | 
            -
                  def  | 
| 33 | 
            -
                    @errors = {}
         | 
| 39 | 
            +
                  def self.with_errors(errors)
         | 
| 34 40 | 
             
                    if !errors.is_a?(Hash) || errors.empty?
         | 
| 35 | 
            -
                       | 
| 36 | 
            -
             | 
| 41 | 
            +
                      new(errors.to_s).with_errors({})
         | 
| 42 | 
            +
                    else
         | 
| 43 | 
            +
                      messages = errors.map { |node_key, error| "#{node_key}: (#{error.class}) #{error.message}" }
         | 
| 44 | 
            +
                      new(messages.join(', ')).with_errors(errors)
         | 
| 37 45 | 
             
                    end
         | 
| 46 | 
            +
                  end
         | 
| 38 47 |  | 
| 39 | 
            -
             | 
| 40 | 
            -
                     | 
| 41 | 
            -
                     | 
| 48 | 
            +
                  def with_errors(errors)
         | 
| 49 | 
            +
                    @errors = errors if @errors.nil?
         | 
| 50 | 
            +
                    self
         | 
| 42 51 | 
             
                  end
         | 
| 43 52 | 
             
                end
         | 
| 44 53 |  | 
| 45 | 
            -
                class AmbiguousNodeError <  | 
| 46 | 
            -
                  def  | 
| 47 | 
            -
                     | 
| 54 | 
            +
                class AmbiguousNodeError < Error
         | 
| 55 | 
            +
                  def self.from_command(command)
         | 
| 56 | 
            +
                    new("Cluster client doesn't know which node the #{command} command should be sent to.")
         | 
| 48 57 | 
             
                  end
         | 
| 49 58 | 
             
                end
         | 
| 50 59 |  | 
| 51 | 
            -
                class NodeMightBeDown <  | 
| 60 | 
            +
                class NodeMightBeDown < Error
         | 
| 52 61 | 
             
                  def initialize(_ = '')
         | 
| 53 62 | 
             
                    super(
         | 
| 54 63 | 
             
                      'The client is trying to fetch the latest cluster state ' \
         | 
| @@ -28,7 +28,7 @@ class RedisClient | |
| 28 28 | 
             
                  private_constant :USE_CHAR_ARRAY_SLOT, :SLOT_SIZE, :MIN_SLOT, :MAX_SLOT,
         | 
| 29 29 | 
             
                                   :DEAD_FLAGS, :ROLE_FLAGS, :EMPTY_ARRAY, :EMPTY_HASH
         | 
| 30 30 |  | 
| 31 | 
            -
                  ReloadNeeded = Class.new(::RedisClient::Error)
         | 
| 31 | 
            +
                  ReloadNeeded = Class.new(::RedisClient::Cluster::Error)
         | 
| 32 32 |  | 
| 33 33 | 
             
                  Info = Struct.new(
         | 
| 34 34 | 
             
                    'RedisClusterNode',
         | 
| @@ -148,7 +148,7 @@ class RedisClient | |
| 148 148 |  | 
| 149 149 | 
             
                    raise ReloadNeeded if errors.values.any?(::RedisClient::ConnectionError)
         | 
| 150 150 |  | 
| 151 | 
            -
                    raise ::RedisClient::Cluster::ErrorCollection | 
| 151 | 
            +
                    raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors)
         | 
| 152 152 | 
             
                  end
         | 
| 153 153 |  | 
| 154 154 | 
             
                  def clients_for_scanning(seed: nil)
         | 
| @@ -267,7 +267,7 @@ class RedisClient | |
| 267 267 | 
             
                    result_values, errors = call_multiple_nodes(clients, method, command, args, &block)
         | 
| 268 268 | 
             
                    return result_values if errors.nil? || errors.empty?
         | 
| 269 269 |  | 
| 270 | 
            -
                    raise ::RedisClient::Cluster::ErrorCollection | 
| 270 | 
            +
                    raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors)
         | 
| 271 271 | 
             
                  end
         | 
| 272 272 |  | 
| 273 273 | 
             
                  def try_map(clients, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
         | 
| @@ -334,7 +334,7 @@ class RedisClient | |
| 334 334 |  | 
| 335 335 | 
             
                    work_group.close
         | 
| 336 336 |  | 
| 337 | 
            -
                    raise ::RedisClient::Cluster::InitialSetupError | 
| 337 | 
            +
                    raise ::RedisClient::Cluster::InitialSetupError.from_errors(errors) if node_info_list.nil?
         | 
| 338 338 |  | 
| 339 339 | 
             
                    grouped = node_info_list.compact.group_by do |info_list|
         | 
| 340 340 | 
             
                      info_list.sort_by!(&:id)
         | 
| @@ -15,10 +15,7 @@ class RedisClient | |
| 15 15 | 
             
                    slot = find_slot(keys)
         | 
| 16 16 | 
             
                    raise ::RedisClient::Cluster::Transaction::ConsistencyError, "unsafe watch: #{keys.join(' ')}" if slot.nil?
         | 
| 17 17 |  | 
| 18 | 
            -
                     | 
| 19 | 
            -
                    # redirections freely initially (i.e. for the first WATCH call)
         | 
| 20 | 
            -
                    node = @router.find_primary_node_by_slot(slot)
         | 
| 21 | 
            -
                    handle_redirection(node, retry_count: 1) do |nd|
         | 
| 18 | 
            +
                    handle_redirection(slot, retry_count: 1) do |nd|
         | 
| 22 19 | 
             
                      nd.with do |c|
         | 
| 23 20 | 
             
                        c.ensure_connected_cluster_scoped(retryable: false) do
         | 
| 24 21 | 
             
                          c.call('ASKING') if @asking
         | 
| @@ -45,10 +42,22 @@ class RedisClient | |
| 45 42 |  | 
| 46 43 | 
             
                  private
         | 
| 47 44 |  | 
| 48 | 
            -
                  def handle_redirection( | 
| 49 | 
            -
                     | 
| 45 | 
            +
                  def handle_redirection(slot, retry_count: 1, &blk)
         | 
| 46 | 
            +
                    # We have not yet selected a node for this transaction, initially, which means we can handle
         | 
| 47 | 
            +
                    # redirections freely initially (i.e. for the first WATCH call)
         | 
| 48 | 
            +
                    node = @router.find_primary_node_by_slot(slot)
         | 
| 49 | 
            +
                    times_block_executed = 0
         | 
| 50 | 
            +
                    @router.handle_redirection(node, nil, retry_count: retry_count) do |nd|
         | 
| 51 | 
            +
                      times_block_executed += 1
         | 
| 50 52 | 
             
                      handle_asking_once(nd, &blk)
         | 
| 51 53 | 
             
                    end
         | 
| 54 | 
            +
                  rescue ::RedisClient::ConnectionError
         | 
| 55 | 
            +
                    # Deduct the number of retries that happened _inside_ router#handle_redirection from our remaining
         | 
| 56 | 
            +
                    # _external_ retries. Always deduct at least one in case handle_redirection raises without trying the block.
         | 
| 57 | 
            +
                    retry_count -= [times_block_executed, 1].min
         | 
| 58 | 
            +
                    raise if retry_count < 0
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    retry
         | 
| 52 61 | 
             
                  end
         | 
| 53 62 |  | 
| 54 63 | 
             
                  def handle_asking_once(node)
         | 
| @@ -108,13 +108,13 @@ class RedisClient | |
| 108 108 | 
             
                    end
         | 
| 109 109 | 
             
                  end
         | 
| 110 110 |  | 
| 111 | 
            -
                  ReplySizeError = Class.new(::RedisClient::Error)
         | 
| 111 | 
            +
                  ReplySizeError = Class.new(::RedisClient::Cluster::Error)
         | 
| 112 112 |  | 
| 113 | 
            -
                  class StaleClusterState < ::RedisClient::Error
         | 
| 113 | 
            +
                  class StaleClusterState < ::RedisClient::Cluster::Error
         | 
| 114 114 | 
             
                    attr_accessor :replies, :first_exception
         | 
| 115 115 | 
             
                  end
         | 
| 116 116 |  | 
| 117 | 
            -
                  class RedirectionNeeded < ::RedisClient::Error
         | 
| 117 | 
            +
                  class RedirectionNeeded < ::RedisClient::Cluster::Error
         | 
| 118 118 | 
             
                    attr_accessor :replies, :indices, :first_exception
         | 
| 119 119 | 
             
                  end
         | 
| 120 120 |  | 
| @@ -204,7 +204,7 @@ class RedisClient | |
| 204 204 |  | 
| 205 205 | 
             
                    work_group.close
         | 
| 206 206 | 
             
                    @router.renew_cluster_state if cluster_state_errors
         | 
| 207 | 
            -
                    raise ::RedisClient::Cluster::ErrorCollection | 
| 207 | 
            +
                    raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors).with_config(@router.config) unless errors.nil?
         | 
| 208 208 |  | 
| 209 209 | 
             
                    required_redirections&.each do |node_key, v|
         | 
| 210 210 | 
             
                      raise v.first_exception if v.first_exception
         | 
| @@ -21,10 +21,10 @@ class RedisClient | |
| 21 21 |  | 
| 22 22 | 
             
                  private_constant :ZERO_CURSOR_FOR_SCAN, :TSF
         | 
| 23 23 |  | 
| 24 | 
            +
                  attr_reader :config
         | 
| 25 | 
            +
             | 
| 24 26 | 
             
                  def initialize(config, concurrent_worker, pool: nil, **kwargs)
         | 
| 25 | 
            -
                    @config = config | 
| 26 | 
            -
                    @original_config = config.dup if config.connect_with_original_config
         | 
| 27 | 
            -
                    @connect_with_original_config = config.connect_with_original_config
         | 
| 27 | 
            +
                    @config = config
         | 
| 28 28 | 
             
                    @concurrent_worker = concurrent_worker
         | 
| 29 29 | 
             
                    @pool = pool
         | 
| 30 30 | 
             
                    @client_kwargs = kwargs
         | 
| @@ -32,6 +32,9 @@ class RedisClient | |
| 32 32 | 
             
                    @node.reload!
         | 
| 33 33 | 
             
                    @command = ::RedisClient::Cluster::Command.load(@node.replica_clients.shuffle, slow_command_timeout: config.slow_command_timeout)
         | 
| 34 34 | 
             
                    @command_builder = @config.command_builder
         | 
| 35 | 
            +
                  rescue ::RedisClient::Cluster::InitialSetupError => e
         | 
| 36 | 
            +
                    e.with_config(config)
         | 
| 37 | 
            +
                    raise
         | 
| 35 38 | 
             
                  end
         | 
| 36 39 |  | 
| 37 40 | 
             
                  def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
         | 
| @@ -58,9 +61,9 @@ class RedisClient | |
| 58 61 | 
             
                    when 'flushall', 'flushdb'
         | 
| 59 62 | 
             
                      @node.call_primaries(method, command, args).first.then(&TSF.call(block))
         | 
| 60 63 | 
             
                    when 'readonly', 'readwrite', 'shutdown'
         | 
| 61 | 
            -
                      raise ::RedisClient::Cluster::OrchestrationCommandNotSupported | 
| 64 | 
            +
                      raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(cmd).with_config(@config)
         | 
| 62 65 | 
             
                    when 'discard', 'exec', 'multi', 'unwatch'
         | 
| 63 | 
            -
                      raise ::RedisClient::Cluster::AmbiguousNodeError | 
| 66 | 
            +
                      raise ::RedisClient::Cluster::AmbiguousNodeError.from_command(cmd).with_config(@config)
         | 
| 64 67 | 
             
                    else
         | 
| 65 68 | 
             
                      node = assign_node(command)
         | 
| 66 69 | 
             
                      try_send(node, method, command, args, &block)
         | 
| @@ -69,7 +72,7 @@ class RedisClient | |
| 69 72 | 
             
                    raise
         | 
| 70 73 | 
             
                  rescue ::RedisClient::Cluster::Node::ReloadNeeded
         | 
| 71 74 | 
             
                    renew_cluster_state
         | 
| 72 | 
            -
                    raise ::RedisClient::Cluster::NodeMightBeDown
         | 
| 75 | 
            +
                    raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
         | 
| 73 76 | 
             
                  rescue ::RedisClient::ConnectionError
         | 
| 74 77 | 
             
                    renew_cluster_state
         | 
| 75 78 | 
             
                    raise
         | 
| @@ -77,6 +80,7 @@ class RedisClient | |
| 77 80 | 
             
                    renew_cluster_state if e.message.start_with?('CLUSTERDOWN')
         | 
| 78 81 | 
             
                    raise
         | 
| 79 82 | 
             
                  rescue ::RedisClient::Cluster::ErrorCollection => e
         | 
| 83 | 
            +
                    e.with_config(@config)
         | 
| 80 84 | 
             
                    raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
         | 
| 81 85 |  | 
| 82 86 | 
             
                    renew_cluster_state if e.errors.values.any? do |err|
         | 
| @@ -90,7 +94,7 @@ class RedisClient | |
| 90 94 |  | 
| 91 95 | 
             
                  # @see https://redis.io/docs/reference/cluster-spec/#redirection-and-resharding Redirection and resharding
         | 
| 92 96 | 
             
                  def try_send(node, method, command, args, retry_count: 3, &block)
         | 
| 93 | 
            -
                    handle_redirection(node, retry_count: retry_count) do |on_node|
         | 
| 97 | 
            +
                    handle_redirection(node, command, retry_count: retry_count) do |on_node|
         | 
| 94 98 | 
             
                      if args.empty?
         | 
| 95 99 | 
             
                        # prevent memory allocation for variable-length args
         | 
| 96 100 | 
             
                        on_node.public_send(method, command, &block)
         | 
| @@ -101,12 +105,12 @@ class RedisClient | |
| 101 105 | 
             
                  end
         | 
| 102 106 |  | 
| 103 107 | 
             
                  def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block)
         | 
| 104 | 
            -
                    handle_redirection(node, retry_count: retry_count) do |on_node|
         | 
| 108 | 
            +
                    handle_redirection(node, nil, retry_count: retry_count) do |on_node|
         | 
| 105 109 | 
             
                      on_node.public_send(method, *args, **kwargs, &block)
         | 
| 106 110 | 
             
                    end
         | 
| 107 111 | 
             
                  end
         | 
| 108 112 |  | 
| 109 | 
            -
                  def handle_redirection(node, retry_count:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
         | 
| 113 | 
            +
                  def handle_redirection(node, command, retry_count:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
         | 
| 110 114 | 
             
                    yield node
         | 
| 111 115 | 
             
                  rescue ::RedisClient::CircuitBreaker::OpenCircuitError
         | 
| 112 116 | 
             
                    raise
         | 
| @@ -134,6 +138,17 @@ class RedisClient | |
| 134 138 |  | 
| 135 139 | 
             
                    retry_count -= 1
         | 
| 136 140 | 
             
                    renew_cluster_state
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                    if retry_count >= 0
         | 
| 143 | 
            +
                      # Find the node to use for this command - if this fails for some reason, though, re-use
         | 
| 144 | 
            +
                      # the old node.
         | 
| 145 | 
            +
                      begin
         | 
| 146 | 
            +
                        node = find_node(find_node_key(command)) if command
         | 
| 147 | 
            +
                      rescue StandardError # rubocop:disable Lint/SuppressedException
         | 
| 148 | 
            +
                      end
         | 
| 149 | 
            +
                      retry
         | 
| 150 | 
            +
                    end
         | 
| 151 | 
            +
             | 
| 137 152 | 
             
                    retry if retry_count >= 0
         | 
| 138 153 | 
             
                    raise
         | 
| 139 154 | 
             
                  end
         | 
| @@ -178,7 +193,7 @@ class RedisClient | |
| 178 193 | 
             
                      node_key = primary ? @node.find_node_key_of_primary(slot) : @node.find_node_key_of_replica(slot)
         | 
| 179 194 | 
             
                      if node_key.nil?
         | 
| 180 195 | 
             
                        renew_cluster_state
         | 
| 181 | 
            -
                        raise ::RedisClient::Cluster::NodeMightBeDown
         | 
| 196 | 
            +
                        raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
         | 
| 182 197 | 
             
                      end
         | 
| 183 198 | 
             
                      node_key
         | 
| 184 199 | 
             
                    else
         | 
| @@ -292,7 +307,7 @@ class RedisClient | |
| 292 307 | 
             
                    case subcommand = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
         | 
| 293 308 | 
             
                    when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
         | 
| 294 309 | 
             
                         'reset', 'set-config-epoch', 'setslot'
         | 
| 295 | 
            -
                      raise ::RedisClient::Cluster::OrchestrationCommandNotSupported | 
| 310 | 
            +
                      raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', subcommand]).with_config(@config)
         | 
| 296 311 | 
             
                    when 'saveconfig' then @node.call_all(method, command, args).first.then(&TSF.call(block))
         | 
| 297 312 | 
             
                    when 'getkeysinslot'
         | 
| 298 313 | 
             
                      raise ArgumentError, command.join(' ') if command.size != 4
         | 
| @@ -336,7 +351,10 @@ class RedisClient | |
| 336 351 | 
             
                  end
         | 
| 337 352 |  | 
| 338 353 | 
             
                  def send_watch_command(command)
         | 
| 339 | 
            -
                     | 
| 354 | 
            +
                    unless block_given?
         | 
| 355 | 
            +
                      msg = 'A block required. And you need to use the block argument as a client for the transaction.'
         | 
| 356 | 
            +
                      raise ::RedisClient::Cluster::Transaction::ConsistencyError.new(msg).with_config(@config)
         | 
| 357 | 
            +
                    end
         | 
| 340 358 |  | 
| 341 359 | 
             
                    ::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot, asking|
         | 
| 342 360 | 
             
                      transaction = ::RedisClient::Cluster::Transaction.new(
         | 
| @@ -390,7 +408,7 @@ class RedisClient | |
| 390 408 | 
             
                  def handle_node_reload_error(retry_count: 1)
         | 
| 391 409 | 
             
                    yield
         | 
| 392 410 | 
             
                  rescue ::RedisClient::Cluster::Node::ReloadNeeded
         | 
| 393 | 
            -
                    raise ::RedisClient::Cluster::NodeMightBeDown if retry_count <= 0
         | 
| 411 | 
            +
                    raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config) if retry_count <= 0
         | 
| 394 412 |  | 
| 395 413 | 
             
                    retry_count -= 1
         | 
| 396 414 | 
             
                    renew_cluster_state
         | 
| @@ -1,12 +1,13 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'redis_client'
         | 
| 4 | 
            +
            require 'redis_client/cluster/errors'
         | 
| 4 5 | 
             
            require 'redis_client/cluster/pipeline'
         | 
| 5 6 |  | 
| 6 7 | 
             
            class RedisClient
         | 
| 7 8 | 
             
              class Cluster
         | 
| 8 9 | 
             
                class Transaction
         | 
| 9 | 
            -
                  ConsistencyError = Class.new(::RedisClient::Error)
         | 
| 10 | 
            +
                  ConsistencyError = Class.new(::RedisClient::Cluster::Error)
         | 
| 10 11 |  | 
| 11 12 | 
             
                  MAX_REDIRECTION = 2
         | 
| 12 13 | 
             
                  EMPTY_ARRAY = [].freeze
         | 
| @@ -67,7 +68,7 @@ class RedisClient | |
| 67 68 | 
             
                    @pending_commands.each(&:call)
         | 
| 68 69 |  | 
| 69 70 | 
             
                    return EMPTY_ARRAY if @pipeline._empty?
         | 
| 70 | 
            -
                    raise ConsistencyError | 
| 71 | 
            +
                    raise ConsistencyError.new("couldn't determine the node: #{@pipeline._commands}").with_config(@router.config) if @node.nil?
         | 
| 71 72 |  | 
| 72 73 | 
             
                    commit
         | 
| 73 74 | 
             
                  end
         | 
| @@ -163,7 +164,7 @@ class RedisClient | |
| 163 164 |  | 
| 164 165 | 
             
                  def handle_command_error!(err, redirect:) # rubocop:disable Metrics/AbcSize
         | 
| 165 166 | 
             
                    if err.message.start_with?('CROSSSLOT')
         | 
| 166 | 
            -
                      raise ConsistencyError | 
| 167 | 
            +
                      raise ConsistencyError.new("#{err.message}: #{err.command}").with_config(@router.config)
         | 
| 167 168 | 
             
                    elsif err.message.start_with?('MOVED')
         | 
| 168 169 | 
             
                      node = @router.assign_redirection_node(err.message)
         | 
| 169 170 | 
             
                      send_transaction(node, redirect: redirect - 1)
         | 
| @@ -183,7 +184,7 @@ class RedisClient | |
| 183 184 | 
             
                    return if slots.size == 1 && @watching_slot.nil?
         | 
| 184 185 | 
             
                    return if slots.size == 1 && @watching_slot == slots.first
         | 
| 185 186 |  | 
| 186 | 
            -
                    raise( | 
| 187 | 
            +
                    raise ConsistencyError.new("the transaction should be executed to a slot in a node: #{commands}").with_config(@router.config)
         | 
| 187 188 | 
             
                  end
         | 
| 188 189 |  | 
| 189 190 | 
             
                  def try_asking(node)
         | 
| @@ -3,6 +3,7 @@ | |
| 3 3 | 
             
            require 'uri'
         | 
| 4 4 | 
             
            require 'redis_client'
         | 
| 5 5 | 
             
            require 'redis_client/cluster'
         | 
| 6 | 
            +
            require 'redis_client/cluster/errors'
         | 
| 6 7 | 
             
            require 'redis_client/cluster/node_key'
         | 
| 7 8 | 
             
            require 'redis_client/command_builder'
         | 
| 8 9 |  | 
| @@ -27,10 +28,10 @@ class RedisClient | |
| 27 28 | 
             
                                 :VALID_SCHEMES, :VALID_NODES_KEYS, :MERGE_CONFIG_KEYS, :IGNORE_GENERIC_CONFIG_KEYS,
         | 
| 28 29 | 
             
                                 :MAX_WORKERS, :SLOW_COMMAND_TIMEOUT, :MAX_STARTUP_SAMPLE
         | 
| 29 30 |  | 
| 30 | 
            -
                InvalidClientConfigError = Class.new(::RedisClient::Error)
         | 
| 31 | 
            +
                InvalidClientConfigError = Class.new(::RedisClient::Cluster::Error)
         | 
| 31 32 |  | 
| 32 33 | 
             
                attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
         | 
| 33 | 
            -
                            :connect_with_original_config, :startup_nodes, :max_startup_sample
         | 
| 34 | 
            +
                            :connect_with_original_config, :startup_nodes, :max_startup_sample, :id
         | 
| 34 35 |  | 
| 35 36 | 
             
                def initialize( # rubocop:disable Metrics/ParameterLists
         | 
| 36 37 | 
             
                  nodes: DEFAULT_NODES,
         | 
| @@ -59,6 +60,7 @@ class RedisClient | |
| 59 60 | 
             
                  @client_implementation = client_implementation
         | 
| 60 61 | 
             
                  @slow_command_timeout = slow_command_timeout
         | 
| 61 62 | 
             
                  @max_startup_sample = max_startup_sample
         | 
| 63 | 
            +
                  @id = client_config[:id]
         | 
| 62 64 | 
             
                end
         | 
| 63 65 |  | 
| 64 66 | 
             
                def inspect
         | 
| @@ -92,6 +94,18 @@ class RedisClient | |
| 92 94 | 
             
                  augment_client_config(config)
         | 
| 93 95 | 
             
                end
         | 
| 94 96 |  | 
| 97 | 
            +
                def resolved?
         | 
| 98 | 
            +
                  true
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                def sentinel?
         | 
| 102 | 
            +
                  false
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                def server_url
         | 
| 106 | 
            +
                  nil
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 95 109 | 
             
                private
         | 
| 96 110 |  | 
| 97 111 | 
             
                def merge_concurrency_option(option)
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: redis-cluster-client
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.12.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Taishi Kasuga
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-11-14 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: redis-client
         | 
| @@ -77,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 77 77 | 
             
                - !ruby/object:Gem::Version
         | 
| 78 78 | 
             
                  version: '0'
         | 
| 79 79 | 
             
            requirements: []
         | 
| 80 | 
            -
            rubygems_version: 3.5. | 
| 80 | 
            +
            rubygems_version: 3.5.22
         | 
| 81 81 | 
             
            signing_key: 
         | 
| 82 82 | 
             
            specification_version: 4
         | 
| 83 83 | 
             
            summary: A Redis cluster client for Ruby
         |