redis-cluster-client 0.3.15 → 0.4.1
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
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 3edb3d83fd54184b6e3069f156944fb1e51459dea9f72f6a149a2fba1fe1cfd8
         | 
| 4 | 
            +
              data.tar.gz: 02fbd7531bcfcca5363351b3be60c832c43df320748f8641a39073ef6c08490a
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 9a7aece18852c1b31fad6f4d3fdd43d0201496439ce2bd5a1eea96682d916cfa99217983e96067e29dfc41001c0d35690c2c8054e9dbf299520a564d80568470
         | 
| 7 | 
            +
              data.tar.gz: 8ba145cc9c9d017c768ae15c44483512d033ac24256bdb46953ec25a9d28d404a6d9a3297cdb31445448ee0d82cda2036328d674db6dc35fab8fe14037ebced1
         | 
| @@ -43,7 +43,7 @@ class RedisClient | |
| 43 43 | 
             
                      clients.each_slice(::RedisClient::Cluster::Node::MAX_THREADS).each_with_object({}) do |chuncked_clients, acc|
         | 
| 44 44 | 
             
                        threads = chuncked_clients.map do |k, v|
         | 
| 45 45 | 
             
                          Thread.new(k, v) do |node_key, client|
         | 
| 46 | 
            -
                            Thread.current | 
| 46 | 
            +
                            Thread.current[:node_key] = node_key
         | 
| 47 47 |  | 
| 48 48 | 
             
                            min = DUMMY_LATENCY_NSEC
         | 
| 49 49 | 
             
                            MEASURE_ATTEMPT_COUNT.times do
         | 
| @@ -53,15 +53,15 @@ class RedisClient | |
| 53 53 | 
             
                              min = duration if duration < min
         | 
| 54 54 | 
             
                            end
         | 
| 55 55 |  | 
| 56 | 
            -
                            Thread.current | 
| 56 | 
            +
                            Thread.current[:latency] = min
         | 
| 57 57 | 
             
                          rescue StandardError
         | 
| 58 | 
            -
                            Thread.current | 
| 58 | 
            +
                            Thread.current[:latency] = DUMMY_LATENCY_NSEC
         | 
| 59 59 | 
             
                          end
         | 
| 60 60 | 
             
                        end
         | 
| 61 61 |  | 
| 62 62 | 
             
                        threads.each do |t|
         | 
| 63 63 | 
             
                          t.join
         | 
| 64 | 
            -
                          acc[t | 
| 64 | 
            +
                          acc[t[:node_key]] = t[:latency]
         | 
| 65 65 | 
             
                        end
         | 
| 66 66 | 
             
                      end
         | 
| 67 67 | 
             
                    end
         | 
| @@ -18,7 +18,6 @@ class RedisClient | |
| 18 18 | 
             
                  MAX_STARTUP_SAMPLE = 37
         | 
| 19 19 | 
             
                  MAX_THREADS = Integer(ENV.fetch('REDIS_CLIENT_MAX_THREADS', 5))
         | 
| 20 20 | 
             
                  IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
         | 
| 21 | 
            -
                  SLOT_OPTIMIZATION_STRING = '0' * SLOT_SIZE
         | 
| 22 21 |  | 
| 23 22 | 
             
                  ReloadNeeded = Class.new(::RedisClient::Error)
         | 
| 24 23 |  | 
| @@ -37,27 +36,36 @@ class RedisClient | |
| 37 36 | 
             
                    end
         | 
| 38 37 | 
             
                  end
         | 
| 39 38 |  | 
| 40 | 
            -
                   | 
| 39 | 
            +
                  class CharArray
         | 
| 40 | 
            +
                    BASE = ''
         | 
| 41 | 
            +
                    PADDING = '0'
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    def initialize(size, elements)
         | 
| 44 | 
            +
                      @elements = elements
         | 
| 45 | 
            +
                      @string = String.new(BASE, encoding: Encoding::BINARY, capacity: size)
         | 
| 46 | 
            +
                      size.times { @string << PADDING }
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 41 49 | 
             
                    def [](index)
         | 
| 42 50 | 
             
                      raise IndexError if index < 0
         | 
| 43 | 
            -
                      return if index >= string.bytesize
         | 
| 51 | 
            +
                      return if index >= @string.bytesize
         | 
| 44 52 |  | 
| 45 | 
            -
                      elements[string.getbyte(index)]
         | 
| 53 | 
            +
                      @elements[@string.getbyte(index)]
         | 
| 46 54 | 
             
                    end
         | 
| 47 55 |  | 
| 48 56 | 
             
                    def []=(index, element)
         | 
| 49 57 | 
             
                      raise IndexError if index < 0
         | 
| 50 | 
            -
                      return if index >= string.bytesize
         | 
| 58 | 
            +
                      return if index >= @string.bytesize
         | 
| 51 59 |  | 
| 52 | 
            -
                      pos = elements.find_index(element) # O(N)
         | 
| 60 | 
            +
                      pos = @elements.find_index(element) # O(N)
         | 
| 53 61 | 
             
                      if pos.nil?
         | 
| 54 | 
            -
                        raise(RangeError, 'full of elements') if elements.size >= 256
         | 
| 62 | 
            +
                        raise(RangeError, 'full of elements') if @elements.size >= 256
         | 
| 55 63 |  | 
| 56 | 
            -
                        pos = elements.size
         | 
| 57 | 
            -
                        elements << element
         | 
| 64 | 
            +
                        pos = @elements.size
         | 
| 65 | 
            +
                        @elements << element
         | 
| 58 66 | 
             
                      end
         | 
| 59 67 |  | 
| 60 | 
            -
                      string.setbyte(index, pos)
         | 
| 68 | 
            +
                      @string.setbyte(index, pos)
         | 
| 61 69 | 
             
                    end
         | 
| 62 70 | 
             
                  end
         | 
| 63 71 |  | 
| @@ -85,11 +93,11 @@ class RedisClient | |
| 85 93 | 
             
                      startup_nodes.each_slice(MAX_THREADS).with_index do |chuncked_startup_nodes, chuncked_idx|
         | 
| 86 94 | 
             
                        threads = chuncked_startup_nodes.each_with_index.map do |raw_client, idx|
         | 
| 87 95 | 
             
                          Thread.new(raw_client, (MAX_THREADS * chuncked_idx) + idx) do |cli, i|
         | 
| 88 | 
            -
                            Thread.current | 
| 96 | 
            +
                            Thread.current[:index] = i
         | 
| 89 97 | 
             
                            reply = cli.call('CLUSTER', 'NODES')
         | 
| 90 | 
            -
                            Thread.current | 
| 98 | 
            +
                            Thread.current[:info] = parse_cluster_node_reply(reply)
         | 
| 91 99 | 
             
                          rescue StandardError => e
         | 
| 92 | 
            -
                            Thread.current | 
| 100 | 
            +
                            Thread.current[:error] = e
         | 
| 93 101 | 
             
                          ensure
         | 
| 94 102 | 
             
                            cli&.close
         | 
| 95 103 | 
             
                          end
         | 
| @@ -97,12 +105,12 @@ class RedisClient | |
| 97 105 |  | 
| 98 106 | 
             
                        threads.each do |t|
         | 
| 99 107 | 
             
                          t.join
         | 
| 100 | 
            -
                          if t. | 
| 108 | 
            +
                          if t.key?(:info)
         | 
| 101 109 | 
             
                            node_info_list ||= Array.new(startup_size)
         | 
| 102 | 
            -
                            node_info_list[t | 
| 103 | 
            -
                          elsif t. | 
| 110 | 
            +
                            node_info_list[t[:index]] = t[:info]
         | 
| 111 | 
            +
                          elsif t.key?(:error)
         | 
| 104 112 | 
             
                            errors ||= Array.new(startup_size)
         | 
| 105 | 
            -
                            errors[t | 
| 113 | 
            +
                            errors[t[:index]] = t[:error]
         | 
| 106 114 | 
             
                          end
         | 
| 107 115 | 
             
                        end
         | 
| 108 116 | 
             
                      end
         | 
| @@ -268,10 +276,8 @@ class RedisClient | |
| 268 276 | 
             
                  def make_array_for_slot_node_mappings(node_info_list)
         | 
| 269 277 | 
             
                    return Array.new(SLOT_SIZE) if node_info_list.count(&:primary?) > 256
         | 
| 270 278 |  | 
| 271 | 
            -
                     | 
| 272 | 
            -
             | 
| 273 | 
            -
                      elements: node_info_list.select(&:primary?).map(&:node_key)
         | 
| 274 | 
            -
                    )
         | 
| 279 | 
            +
                    primary_node_keys = node_info_list.select(&:primary?).map(&:node_key)
         | 
| 280 | 
            +
                    ::RedisClient::Cluster::Node::CharArray.new(SLOT_SIZE, primary_node_keys)
         | 
| 275 281 | 
             
                  end
         | 
| 276 282 |  | 
| 277 283 | 
             
                  def build_replication_mappings(node_info_list) # rubocop:disable Metrics/AbcSize
         | 
| @@ -303,22 +309,22 @@ class RedisClient | |
| 303 309 | 
             
                    clients.each_slice(MAX_THREADS) do |chuncked_clients|
         | 
| 304 310 | 
             
                      threads = chuncked_clients.map do |k, v|
         | 
| 305 311 | 
             
                        Thread.new(k, v) do |node_key, client|
         | 
| 306 | 
            -
                          Thread.current | 
| 312 | 
            +
                          Thread.current[:node_key] = node_key
         | 
| 307 313 | 
             
                          reply = yield(node_key, client)
         | 
| 308 | 
            -
                          Thread.current | 
| 314 | 
            +
                          Thread.current[:result] = reply
         | 
| 309 315 | 
             
                        rescue StandardError => e
         | 
| 310 | 
            -
                          Thread.current | 
| 316 | 
            +
                          Thread.current[:error] = e
         | 
| 311 317 | 
             
                        end
         | 
| 312 318 | 
             
                      end
         | 
| 313 319 |  | 
| 314 320 | 
             
                      threads.each do |t|
         | 
| 315 321 | 
             
                        t.join
         | 
| 316 | 
            -
                        if t. | 
| 322 | 
            +
                        if t.key?(:result)
         | 
| 317 323 | 
             
                          results ||= {}
         | 
| 318 | 
            -
                          results[t | 
| 319 | 
            -
                        elsif t. | 
| 324 | 
            +
                          results[t[:node_key]] = t[:result]
         | 
| 325 | 
            +
                        elsif t.key?(:error)
         | 
| 320 326 | 
             
                          errors ||= {}
         | 
| 321 | 
            -
                          errors[t | 
| 327 | 
            +
                          errors[t[:node_key]] = t[:error]
         | 
| 322 328 | 
             
                        end
         | 
| 323 329 | 
             
                      end
         | 
| 324 330 | 
             
                    end
         | 
| @@ -150,34 +150,34 @@ class RedisClient | |
| 150 150 | 
             
                    @pipelines&.each_slice(MAX_THREADS) do |chuncked_pipelines|
         | 
| 151 151 | 
             
                      threads = chuncked_pipelines.map do |node_key, pipeline|
         | 
| 152 152 | 
             
                        Thread.new(node_key, pipeline) do |nk, pl|
         | 
| 153 | 
            -
                          Thread.current | 
| 153 | 
            +
                          Thread.current[:node_key] = nk
         | 
| 154 154 | 
             
                          replies = do_pipelining(@router.find_node(nk), pl)
         | 
| 155 155 | 
             
                          raise ReplySizeError, "commands: #{pl._size}, replies: #{replies.size}" if pl._size != replies.size
         | 
| 156 156 |  | 
| 157 | 
            -
                          Thread.current | 
| 157 | 
            +
                          Thread.current[:replies] = replies
         | 
| 158 158 | 
             
                        rescue ::RedisClient::Cluster::Pipeline::RedirectionNeeded => e
         | 
| 159 | 
            -
                          Thread.current | 
| 159 | 
            +
                          Thread.current[:redirection_needed] = e
         | 
| 160 160 | 
             
                        rescue StandardError => e
         | 
| 161 | 
            -
                          Thread.current | 
| 161 | 
            +
                          Thread.current[:error] = e
         | 
| 162 162 | 
             
                        end
         | 
| 163 163 | 
             
                      end
         | 
| 164 164 |  | 
| 165 165 | 
             
                      threads.each(&:join)
         | 
| 166 166 | 
             
                      threads.each do |t|
         | 
| 167 | 
            -
                        if t. | 
| 167 | 
            +
                        if t.key?(:replies)
         | 
| 168 168 | 
             
                          all_replies ||= Array.new(@size)
         | 
| 169 | 
            -
                          @pipelines[t | 
| 169 | 
            +
                          @pipelines[t[:node_key]]
         | 
| 170 170 | 
             
                            .outer_indices
         | 
| 171 | 
            -
                            .each_with_index { |outer, inner| all_replies[outer] = t | 
| 172 | 
            -
                        elsif t. | 
| 171 | 
            +
                            .each_with_index { |outer, inner| all_replies[outer] = t[:replies][inner] }
         | 
| 172 | 
            +
                        elsif t.key?(:redirection_needed)
         | 
| 173 173 | 
             
                          all_replies ||= Array.new(@size)
         | 
| 174 | 
            -
                          pipeline = @pipelines[t | 
| 175 | 
            -
                          err = t | 
| 174 | 
            +
                          pipeline = @pipelines[t[:node_key]]
         | 
| 175 | 
            +
                          err = t[:redirection_needed]
         | 
| 176 176 | 
             
                          err.indices.each { |i| err.replies[i] = handle_redirection(err.replies[i], pipeline, i) }
         | 
| 177 177 | 
             
                          pipeline.outer_indices.each_with_index { |outer, inner| all_replies[outer] = err.replies[inner] }
         | 
| 178 | 
            -
                        elsif t. | 
| 178 | 
            +
                        elsif t.key?(:error)
         | 
| 179 179 | 
             
                          errors ||= {}
         | 
| 180 | 
            -
                          errors[t | 
| 180 | 
            +
                          errors[t[:node_key]] = t[:error]
         | 
| 181 181 | 
             
                        end
         | 
| 182 182 | 
             
                      end
         | 
| 183 183 | 
             
                    end
         | 
| @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'redis_client'
         | 
| 4 | 
            +
            require 'redis_client/circuit_breaker'
         | 
| 4 5 | 
             
            require 'redis_client/cluster/command'
         | 
| 5 6 | 
             
            require 'redis_client/cluster/errors'
         | 
| 6 7 | 
             
            require 'redis_client/cluster/key_slot_converter'
         | 
| @@ -13,6 +14,7 @@ class RedisClient | |
| 13 14 | 
             
                class Router
         | 
| 14 15 | 
             
                  ZERO_CURSOR_FOR_SCAN = '0'
         | 
| 15 16 | 
             
                  METHODS_FOR_BLOCKING_CMD = %i[blocking_call_v blocking_call].freeze
         | 
| 17 | 
            +
                  TSF = ->(b, s) { b.nil? ? s : b.call(s) }.curry
         | 
| 16 18 |  | 
| 17 19 | 
             
                  attr_reader :node
         | 
| 18 20 |  | 
| @@ -29,38 +31,43 @@ class RedisClient | |
| 29 31 | 
             
                  def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
         | 
| 30 32 | 
             
                    cmd = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
         | 
| 31 33 | 
             
                    case cmd
         | 
| 32 | 
            -
                    when ' | 
| 33 | 
            -
                      @node.call_all(method, command, args, &block).first
         | 
| 34 | 
            -
                    when 'flushall', 'flushdb'
         | 
| 35 | 
            -
                      @node.call_primaries(method, command, args, &block).first
         | 
| 36 | 
            -
                    when 'ping'     then @node.send_ping(method, command, args, &block).first
         | 
| 34 | 
            +
                    when 'ping'     then @node.send_ping(method, command, args).first.then(&TSF.call(block))
         | 
| 37 35 | 
             
                    when 'wait'     then send_wait_command(method, command, args, &block)
         | 
| 38 | 
            -
                    when 'keys'     then @node.call_replicas(method, command, args | 
| 39 | 
            -
                    when 'dbsize'   then @node.call_replicas(method, command, args | 
| 36 | 
            +
                    when 'keys'     then @node.call_replicas(method, command, args).flatten.sort_by(&:to_s).then(&TSF.call(block))
         | 
| 37 | 
            +
                    when 'dbsize'   then @node.call_replicas(method, command, args).select { |e| e.is_a?(Integer) }.sum.then(&TSF.call(block))
         | 
| 40 38 | 
             
                    when 'scan'     then scan(command, seed: 1)
         | 
| 41 | 
            -
                    when 'lastsave' then @node.call_all(method, command, args | 
| 39 | 
            +
                    when 'lastsave' then @node.call_all(method, command, args).sort_by(&:to_i).then(&TSF.call(block))
         | 
| 42 40 | 
             
                    when 'role'     then @node.call_all(method, command, args, &block)
         | 
| 43 41 | 
             
                    when 'config'   then send_config_command(method, command, args, &block)
         | 
| 44 42 | 
             
                    when 'client'   then send_client_command(method, command, args, &block)
         | 
| 45 43 | 
             
                    when 'cluster'  then send_cluster_command(method, command, args, &block)
         | 
| 46 | 
            -
                    when 'readonly', 'readwrite', 'shutdown'
         | 
| 47 | 
            -
                      raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, cmd
         | 
| 48 44 | 
             
                    when 'memory'   then send_memory_command(method, command, args, &block)
         | 
| 49 45 | 
             
                    when 'script'   then send_script_command(method, command, args, &block)
         | 
| 50 46 | 
             
                    when 'pubsub'   then send_pubsub_command(method, command, args, &block)
         | 
| 47 | 
            +
                    when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
         | 
| 48 | 
            +
                      @node.call_all(method, command, args).first.then(&TSF.call(block))
         | 
| 49 | 
            +
                    when 'flushall', 'flushdb'
         | 
| 50 | 
            +
                      @node.call_primaries(method, command, args).first.then(&TSF.call(block))
         | 
| 51 | 
            +
                    when 'readonly', 'readwrite', 'shutdown'
         | 
| 52 | 
            +
                      raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, cmd
         | 
| 51 53 | 
             
                    when 'discard', 'exec', 'multi', 'unwatch'
         | 
| 52 54 | 
             
                      raise ::RedisClient::Cluster::AmbiguousNodeError, cmd
         | 
| 53 55 | 
             
                    else
         | 
| 54 56 | 
             
                      node = assign_node(command)
         | 
| 55 57 | 
             
                      try_send(node, method, command, args, &block)
         | 
| 56 58 | 
             
                    end
         | 
| 59 | 
            +
                  rescue ::RedisClient::CircuitBreaker::OpenCircuitError
         | 
| 60 | 
            +
                    raise
         | 
| 57 61 | 
             
                  rescue ::RedisClient::Cluster::Node::ReloadNeeded
         | 
| 58 62 | 
             
                    update_cluster_info!
         | 
| 59 63 | 
             
                    raise ::RedisClient::Cluster::NodeMightBeDown
         | 
| 60 64 | 
             
                  rescue ::RedisClient::Cluster::ErrorCollection => e
         | 
| 65 | 
            +
                    raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
         | 
| 66 | 
            +
             | 
| 61 67 | 
             
                    update_cluster_info! if e.errors.values.any? do |err|
         | 
| 62 68 | 
             
                      err.message.start_with?('CLUSTERDOWN Hash slot not served')
         | 
| 63 69 | 
             
                    end
         | 
| 70 | 
            +
             | 
| 64 71 | 
             
                    raise
         | 
| 65 72 | 
             
                  end
         | 
| 66 73 |  | 
| @@ -72,6 +79,8 @@ class RedisClient | |
| 72 79 | 
             
                    else
         | 
| 73 80 | 
             
                      node.public_send(method, *args, command, &block)
         | 
| 74 81 | 
             
                    end
         | 
| 82 | 
            +
                  rescue ::RedisClient::CircuitBreaker::OpenCircuitError
         | 
| 83 | 
            +
                    raise
         | 
| 75 84 | 
             
                  rescue ::RedisClient::CommandError => e
         | 
| 76 85 | 
             
                    raise if retry_count <= 0
         | 
| 77 86 |  | 
| @@ -102,6 +111,8 @@ class RedisClient | |
| 102 111 |  | 
| 103 112 | 
             
                  def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/AbcSize
         | 
| 104 113 | 
             
                    node.public_send(method, *args, **kwargs, &block)
         | 
| 114 | 
            +
                  rescue ::RedisClient::CircuitBreaker::OpenCircuitError
         | 
| 115 | 
            +
                    raise
         | 
| 105 116 | 
             
                  rescue ::RedisClient::CommandError => e
         | 
| 106 117 | 
             
                    raise if retry_count <= 0
         | 
| 107 118 |  | 
| @@ -197,9 +208,10 @@ class RedisClient | |
| 197 208 |  | 
| 198 209 | 
             
                  private
         | 
| 199 210 |  | 
| 200 | 
            -
                  def send_wait_command(method, command, args, retry_count: 3, &block)
         | 
| 201 | 
            -
                    @node.call_primaries(method, command, args | 
| 211 | 
            +
                  def send_wait_command(method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/AbcSize
         | 
| 212 | 
            +
                    @node.call_primaries(method, command, args).select { |r| r.is_a?(Integer) }.sum.then(&TSF.call(block))
         | 
| 202 213 | 
             
                  rescue ::RedisClient::Cluster::ErrorCollection => e
         | 
| 214 | 
            +
                    raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
         | 
| 203 215 | 
             
                    raise if retry_count <= 0
         | 
| 204 216 | 
             
                    raise if e.errors.values.none? do |err|
         | 
| 205 217 | 
             
                      err.message.include?('WAIT cannot be used with replica instances')
         | 
| @@ -213,7 +225,7 @@ class RedisClient | |
| 213 225 | 
             
                  def send_config_command(method, command, args, &block)
         | 
| 214 226 | 
             
                    case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
         | 
| 215 227 | 
             
                    when 'resetstat', 'rewrite', 'set'
         | 
| 216 | 
            -
                      @node.call_all(method, command, args | 
| 228 | 
            +
                      @node.call_all(method, command, args).first.then(&TSF.call(block))
         | 
| 217 229 | 
             
                    else assign_node(command).public_send(method, *args, command, &block)
         | 
| 218 230 | 
             
                    end
         | 
| 219 231 | 
             
                  end
         | 
| @@ -221,16 +233,16 @@ class RedisClient | |
| 221 233 | 
             
                  def send_memory_command(method, command, args, &block)
         | 
| 222 234 | 
             
                    case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
         | 
| 223 235 | 
             
                    when 'stats' then @node.call_all(method, command, args, &block)
         | 
| 224 | 
            -
                    when 'purge' then @node.call_all(method, command, args | 
| 236 | 
            +
                    when 'purge' then @node.call_all(method, command, args).first.then(&TSF.call(block))
         | 
| 225 237 | 
             
                    else assign_node(command).public_send(method, *args, command, &block)
         | 
| 226 238 | 
             
                    end
         | 
| 227 239 | 
             
                  end
         | 
| 228 240 |  | 
| 229 241 | 
             
                  def send_client_command(method, command, args, &block)
         | 
| 230 242 | 
             
                    case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
         | 
| 231 | 
            -
                    when 'list' then @node.call_all(method, command, args | 
| 243 | 
            +
                    when 'list' then @node.call_all(method, command, args).flatten.then(&TSF.call(block))
         | 
| 232 244 | 
             
                    when 'pause', 'reply', 'setname'
         | 
| 233 | 
            -
                      @node.call_all(method, command, args | 
| 245 | 
            +
                      @node.call_all(method, command, args).first.then(&TSF.call(block))
         | 
| 234 246 | 
             
                    else assign_node(command).public_send(method, *args, command, &block)
         | 
| 235 247 | 
             
                    end
         | 
| 236 248 | 
             
                  end
         | 
| @@ -240,7 +252,7 @@ class RedisClient | |
| 240 252 | 
             
                    when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
         | 
| 241 253 | 
             
                         'reset', 'set-config-epoch', 'setslot'
         | 
| 242 254 | 
             
                      raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, ['cluster', subcommand]
         | 
| 243 | 
            -
                    when 'saveconfig' then @node.call_all(method, command, args | 
| 255 | 
            +
                    when 'saveconfig' then @node.call_all(method, command, args).first.then(&TSF.call(block))
         | 
| 244 256 | 
             
                    when 'getkeysinslot'
         | 
| 245 257 | 
             
                      raise ArgumentError, command.join(' ') if command.size != 4
         | 
| 246 258 |  | 
| @@ -249,25 +261,25 @@ class RedisClient | |
| 249 261 | 
             
                    end
         | 
| 250 262 | 
             
                  end
         | 
| 251 263 |  | 
| 252 | 
            -
                  def send_script_command(method, command, args, &block)
         | 
| 264 | 
            +
                  def send_script_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize
         | 
| 253 265 | 
             
                    case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
         | 
| 254 266 | 
             
                    when 'debug', 'kill'
         | 
| 255 | 
            -
                      @node.call_all(method, command, args | 
| 267 | 
            +
                      @node.call_all(method, command, args).first.then(&TSF.call(block))
         | 
| 256 268 | 
             
                    when 'flush', 'load'
         | 
| 257 | 
            -
                      @node.call_primaries(method, command, args | 
| 269 | 
            +
                      @node.call_primaries(method, command, args).first.then(&TSF.call(block))
         | 
| 258 270 | 
             
                    when 'exists'
         | 
| 259 | 
            -
                      @node.call_all(method, command, args | 
| 271 | 
            +
                      @node.call_all(method, command, args).transpose.map { |arr| arr.any?(&:zero?) ? 0 : 1 }.then(&TSF.call(block))
         | 
| 260 272 | 
             
                    else assign_node(command).public_send(method, *args, command, &block)
         | 
| 261 273 | 
             
                    end
         | 
| 262 274 | 
             
                  end
         | 
| 263 275 |  | 
| 264 276 | 
             
                  def send_pubsub_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
         | 
| 265 277 | 
             
                    case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
         | 
| 266 | 
            -
                    when 'channels' then @node.call_all(method, command, args | 
| 278 | 
            +
                    when 'channels' then @node.call_all(method, command, args).flatten.uniq.sort_by(&:to_s).then(&TSF.call(block))
         | 
| 267 279 | 
             
                    when 'numsub'
         | 
| 268 | 
            -
                      @node.call_all(method, command, args | 
| 269 | 
            -
                           .reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
         | 
| 270 | 
            -
                    when 'numpat' then @node.call_all(method, command, args | 
| 280 | 
            +
                      @node.call_all(method, command, args).reject(&:empty?).map { |e| Hash[*e] }
         | 
| 281 | 
            +
                           .reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }.then(&TSF.call(block))
         | 
| 282 | 
            +
                    when 'numpat' then @node.call_all(method, command, args).select { |e| e.is_a?(Integer) }.sum.then(&TSF.call(block))
         | 
| 271 283 | 
             
                    else assign_node(command).public_send(method, *args, command, &block)
         | 
| 272 284 | 
             
                    end
         | 
| 273 285 | 
             
                  end
         | 
| @@ -98,7 +98,7 @@ class RedisClient | |
| 98 98 |  | 
| 99 99 | 
             
                def build_node_configs(addrs)
         | 
| 100 100 | 
             
                  configs = Array[addrs].flatten.filter_map { |addr| parse_node_addr(addr) }
         | 
| 101 | 
            -
                  raise InvalidClientConfigError, '`nodes` option is empty' if configs. | 
| 101 | 
            +
                  raise InvalidClientConfigError, '`nodes` option is empty' if configs.empty?
         | 
| 102 102 |  | 
| 103 103 | 
             
                  configs
         | 
| 104 104 | 
             
                end
         | 
| @@ -150,7 +150,7 @@ class RedisClient | |
| 150 150 | 
             
                end
         | 
| 151 151 |  | 
| 152 152 | 
             
                def merge_generic_config(client_config, node_configs)
         | 
| 153 | 
            -
                  return client_config if node_configs. | 
| 153 | 
            +
                  return client_config if node_configs.empty?
         | 
| 154 154 |  | 
| 155 155 | 
             
                  cfg = node_configs.first
         | 
| 156 156 | 
             
                  MERGE_CONFIG_KEYS.each { |k| client_config[k] = cfg[k] if cfg.key?(k) }
         | 
    
        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.4.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Taishi Kasuga
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2023-01-22 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: redis-client
         | 
| @@ -16,14 +16,14 @@ dependencies: | |
| 16 16 | 
             
                requirements:
         | 
| 17 17 | 
             
                - - "~>"
         | 
| 18 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: '0. | 
| 19 | 
            +
                    version: '0.12'
         | 
| 20 20 | 
             
              type: :runtime
         | 
| 21 21 | 
             
              prerelease: false
         | 
| 22 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 23 | 
             
                requirements:
         | 
| 24 24 | 
             
                - - "~>"
         | 
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version: '0. | 
| 26 | 
            +
                    version: '0.12'
         | 
| 27 27 | 
             
            description:
         | 
| 28 28 | 
             
            email:
         | 
| 29 29 | 
             
            - proxy0721@gmail.com
         |