redis 4.1.2 → 4.2.2
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/CHANGELOG.md +39 -1
- data/README.md +14 -5
- data/lib/redis.rb +385 -343
- data/lib/redis/client.rb +66 -69
- data/lib/redis/cluster.rb +13 -4
- data/lib/redis/cluster/node.rb +3 -0
- data/lib/redis/cluster/node_key.rb +3 -7
- data/lib/redis/cluster/option.rb +27 -14
- data/lib/redis/cluster/slot.rb +30 -13
- data/lib/redis/cluster/slot_loader.rb +4 -4
- data/lib/redis/connection.rb +2 -0
- data/lib/redis/connection/command_helper.rb +3 -2
- data/lib/redis/connection/hiredis.rb +4 -3
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +47 -58
- data/lib/redis/connection/synchrony.rb +9 -4
- data/lib/redis/distributed.rb +117 -62
- data/lib/redis/errors.rb +2 -0
- data/lib/redis/hash_ring.rb +15 -14
- data/lib/redis/pipeline.rb +16 -3
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- metadata +14 -9
| @@ -1,9 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require_relative "command_helper"
         | 
| 2 4 | 
             
            require_relative "registry"
         | 
| 3 5 | 
             
            require_relative "../errors"
         | 
| 4 6 | 
             
            require "em-synchrony"
         | 
| 5 7 | 
             
            require "hiredis/reader"
         | 
| 6 8 |  | 
| 9 | 
            +
            Kernel.warn(
         | 
| 10 | 
            +
              "The redis synchrony driver is deprecated and will be removed in redis-rb 5.0. " \
         | 
| 11 | 
            +
              "We're looking for people to maintain it as a separate gem, see https://github.com/redis/redis-rb/issues/915"
         | 
| 12 | 
            +
            )
         | 
| 13 | 
            +
             | 
| 7 14 | 
             
            class Redis
         | 
| 8 15 | 
             
              module Connection
         | 
| 9 16 | 
             
                class RedisClient < EventMachine::Connection
         | 
| @@ -46,9 +53,7 @@ class Redis | |
| 46 53 |  | 
| 47 54 | 
             
                  def read
         | 
| 48 55 | 
             
                    @req = EventMachine::DefaultDeferrable.new
         | 
| 49 | 
            -
                    if @timeout > 0
         | 
| 50 | 
            -
                      @req.timeout(@timeout, :timeout)
         | 
| 51 | 
            -
                    end
         | 
| 56 | 
            +
                    @req.timeout(@timeout, :timeout) if @timeout > 0
         | 
| 52 57 | 
             
                    EventMachine::Synchrony.sync @req
         | 
| 53 58 | 
             
                  end
         | 
| 54 59 |  | 
| @@ -105,7 +110,7 @@ class Redis | |
| 105 110 | 
             
                  end
         | 
| 106 111 |  | 
| 107 112 | 
             
                  def connected?
         | 
| 108 | 
            -
                    @connection | 
| 113 | 
            +
                    @connection&.connected?
         | 
| 109 114 | 
             
                  end
         | 
| 110 115 |  | 
| 111 116 | 
             
                  def timeout=(timeout)
         | 
    
        data/lib/redis/distributed.rb
    CHANGED
    
    | @@ -1,15 +1,17 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require_relative "hash_ring"
         | 
| 2 4 |  | 
| 3 5 | 
             
            class Redis
         | 
| 4 6 | 
             
              class Distributed
         | 
| 5 | 
            -
             | 
| 6 7 | 
             
                class CannotDistribute < RuntimeError
         | 
| 7 8 | 
             
                  def initialize(command)
         | 
| 8 9 | 
             
                    @command = command
         | 
| 9 10 | 
             
                  end
         | 
| 10 11 |  | 
| 11 12 | 
             
                  def message
         | 
| 12 | 
            -
                    "#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need  | 
| 13 | 
            +
                    "#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need " \
         | 
| 14 | 
            +
                      "to be on the same server or because we cannot guarantee that the operation will be atomic."
         | 
| 13 15 | 
             
                  end
         | 
| 14 16 | 
             
                end
         | 
| 15 17 |  | 
| @@ -22,10 +24,14 @@ class Redis | |
| 22 24 | 
             
                  @default_options = options.dup
         | 
| 23 25 | 
             
                  node_configs.each { |node_config| add_node(node_config) }
         | 
| 24 26 | 
             
                  @subscribed_node = nil
         | 
| 27 | 
            +
                  @watch_key = nil
         | 
| 25 28 | 
             
                end
         | 
| 26 29 |  | 
| 27 30 | 
             
                def node_for(key)
         | 
| 28 | 
            -
                   | 
| 31 | 
            +
                  key = key_tag(key.to_s) || key.to_s
         | 
| 32 | 
            +
                  raise CannotDistribute, :watch if @watch_key && @watch_key != key
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  @ring.get_node(key)
         | 
| 29 35 | 
             
                end
         | 
| 30 36 |  | 
| 31 37 | 
             
                def nodes
         | 
| @@ -33,9 +39,9 @@ class Redis | |
| 33 39 | 
             
                end
         | 
| 34 40 |  | 
| 35 41 | 
             
                def add_node(options)
         | 
| 36 | 
            -
                  options = { : | 
| 42 | 
            +
                  options = { url: options } if options.is_a?(String)
         | 
| 37 43 | 
             
                  options = @default_options.merge(options)
         | 
| 38 | 
            -
                  @ring.add_node Redis.new( | 
| 44 | 
            +
                  @ring.add_node Redis.new(options)
         | 
| 39 45 | 
             
                end
         | 
| 40 46 |  | 
| 41 47 | 
             
                # Change the selected database for the current connection.
         | 
| @@ -144,12 +150,12 @@ class Redis | |
| 144 150 | 
             
                end
         | 
| 145 151 |  | 
| 146 152 | 
             
                # Create a key using the serialized value, previously obtained using DUMP.
         | 
| 147 | 
            -
                def restore(key, ttl, serialized_value, options | 
| 148 | 
            -
                  node_for(key).restore(key, ttl, serialized_value, options)
         | 
| 153 | 
            +
                def restore(key, ttl, serialized_value, **options)
         | 
| 154 | 
            +
                  node_for(key).restore(key, ttl, serialized_value, **options)
         | 
| 149 155 | 
             
                end
         | 
| 150 156 |  | 
| 151 157 | 
             
                # Transfer a key from the connected instance to another instance.
         | 
| 152 | 
            -
                def migrate( | 
| 158 | 
            +
                def migrate(_key, _options)
         | 
| 153 159 | 
             
                  raise CannotDistribute, :migrate
         | 
| 154 160 | 
             
                end
         | 
| 155 161 |  | 
| @@ -170,8 +176,33 @@ class Redis | |
| 170 176 | 
             
                end
         | 
| 171 177 |  | 
| 172 178 | 
             
                # Determine if a key exists.
         | 
| 173 | 
            -
                def exists( | 
| 174 | 
            -
                   | 
| 179 | 
            +
                def exists(*args)
         | 
| 180 | 
            +
                  if !Redis.exists_returns_integer && args.size == 1
         | 
| 181 | 
            +
                    message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
         | 
| 182 | 
            +
                      "use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
         | 
| 183 | 
            +
                      "(#{::Kernel.caller(1, 1).first})\n"
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                    if defined?(::Warning)
         | 
| 186 | 
            +
                      ::Warning.warn(message)
         | 
| 187 | 
            +
                    else
         | 
| 188 | 
            +
                      warn(message)
         | 
| 189 | 
            +
                    end
         | 
| 190 | 
            +
                    exists?(*args)
         | 
| 191 | 
            +
                  else
         | 
| 192 | 
            +
                    keys_per_node = args.group_by { |key| node_for(key) }
         | 
| 193 | 
            +
                    keys_per_node.inject(0) do |sum, (node, keys)|
         | 
| 194 | 
            +
                      sum + node._exists(*keys)
         | 
| 195 | 
            +
                    end
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                # Determine if any of the keys exists.
         | 
| 200 | 
            +
                def exists?(*args)
         | 
| 201 | 
            +
                  keys_per_node = args.group_by { |key| node_for(key) }
         | 
| 202 | 
            +
                  keys_per_node.each do |node, keys|
         | 
| 203 | 
            +
                    return true if node.exists?(*keys)
         | 
| 204 | 
            +
                  end
         | 
| 205 | 
            +
                  false
         | 
| 175 206 | 
             
                end
         | 
| 176 207 |  | 
| 177 208 | 
             
                # Find all keys matching the given pattern.
         | 
| @@ -204,11 +235,11 @@ class Redis | |
| 204 235 | 
             
                end
         | 
| 205 236 |  | 
| 206 237 | 
             
                # Sort the elements in a list, set or sorted set.
         | 
| 207 | 
            -
                def sort(key, options | 
| 238 | 
            +
                def sort(key, **options)
         | 
| 208 239 | 
             
                  keys = [key, options[:by], options[:store], *Array(options[:get])].compact
         | 
| 209 240 |  | 
| 210 241 | 
             
                  ensure_same_node(:sort, keys) do |node|
         | 
| 211 | 
            -
                    node.sort(key, options)
         | 
| 242 | 
            +
                    node.sort(key, **options)
         | 
| 212 243 | 
             
                  end
         | 
| 213 244 | 
             
                end
         | 
| 214 245 |  | 
| @@ -243,8 +274,8 @@ class Redis | |
| 243 274 | 
             
                end
         | 
| 244 275 |  | 
| 245 276 | 
             
                # Set the string value of a key.
         | 
| 246 | 
            -
                def set(key, value, options | 
| 247 | 
            -
                  node_for(key).set(key, value, options)
         | 
| 277 | 
            +
                def set(key, value, **options)
         | 
| 278 | 
            +
                  node_for(key).set(key, value, **options)
         | 
| 248 279 | 
             
                end
         | 
| 249 280 |  | 
| 250 281 | 
             
                # Set the time to live in seconds of a key.
         | 
| @@ -263,20 +294,20 @@ class Redis | |
| 263 294 | 
             
                end
         | 
| 264 295 |  | 
| 265 296 | 
             
                # Set multiple keys to multiple values.
         | 
| 266 | 
            -
                def mset(* | 
| 297 | 
            +
                def mset(*_args)
         | 
| 267 298 | 
             
                  raise CannotDistribute, :mset
         | 
| 268 299 | 
             
                end
         | 
| 269 300 |  | 
| 270 | 
            -
                def mapped_mset( | 
| 301 | 
            +
                def mapped_mset(_hash)
         | 
| 271 302 | 
             
                  raise CannotDistribute, :mapped_mset
         | 
| 272 303 | 
             
                end
         | 
| 273 304 |  | 
| 274 305 | 
             
                # Set multiple keys to multiple values, only if none of the keys exist.
         | 
| 275 | 
            -
                def msetnx(* | 
| 306 | 
            +
                def msetnx(*_args)
         | 
| 276 307 | 
             
                  raise CannotDistribute, :msetnx
         | 
| 277 308 | 
             
                end
         | 
| 278 309 |  | 
| 279 | 
            -
                def mapped_msetnx( | 
| 310 | 
            +
                def mapped_msetnx(_hash)
         | 
| 280 311 | 
             
                  raise CannotDistribute, :mapped_msetnx
         | 
| 281 312 | 
             
                end
         | 
| 282 313 |  | 
| @@ -335,7 +366,7 @@ class Redis | |
| 335 366 | 
             
                end
         | 
| 336 367 |  | 
| 337 368 | 
             
                # Return the position of the first bit set to 1 or 0 in a string.
         | 
| 338 | 
            -
                def bitpos(key, bit, start=nil, stop=nil)
         | 
| 369 | 
            +
                def bitpos(key, bit, start = nil, stop = nil)
         | 
| 339 370 | 
             
                  node_for(key).bitpos(key, bit, start, stop)
         | 
| 340 371 | 
             
                end
         | 
| 341 372 |  | 
| @@ -353,7 +384,7 @@ class Redis | |
| 353 384 | 
             
                  get(key)
         | 
| 354 385 | 
             
                end
         | 
| 355 386 |  | 
| 356 | 
            -
                def []=(key,value)
         | 
| 387 | 
            +
                def []=(key, value)
         | 
| 357 388 | 
             
                  set(key, value)
         | 
| 358 389 | 
             
                end
         | 
| 359 390 |  | 
| @@ -401,13 +432,12 @@ class Redis | |
| 401 432 | 
             
                end
         | 
| 402 433 |  | 
| 403 434 | 
             
                def _bpop(cmd, args)
         | 
| 404 | 
            -
                   | 
| 405 | 
            -
             | 
| 406 | 
            -
                  if args.last.is_a?(Hash)
         | 
| 435 | 
            +
                  timeout = if args.last.is_a?(Hash)
         | 
| 407 436 | 
             
                    options = args.pop
         | 
| 437 | 
            +
                    options[:timeout]
         | 
| 408 438 | 
             
                  elsif args.last.respond_to?(:to_int)
         | 
| 409 439 | 
             
                    # Issue deprecation notice in obnoxious mode...
         | 
| 410 | 
            -
                     | 
| 440 | 
            +
                    args.pop.to_int
         | 
| 411 441 | 
             
                  end
         | 
| 412 442 |  | 
| 413 443 | 
             
                  if args.size > 1
         | 
| @@ -417,7 +447,11 @@ class Redis | |
| 417 447 | 
             
                  keys = args.flatten
         | 
| 418 448 |  | 
| 419 449 | 
             
                  ensure_same_node(cmd, keys) do |node|
         | 
| 420 | 
            -
                     | 
| 450 | 
            +
                    if timeout
         | 
| 451 | 
            +
                      node.__send__(cmd, keys, timeout: timeout)
         | 
| 452 | 
            +
                    else
         | 
| 453 | 
            +
                      node.__send__(cmd, keys)
         | 
| 454 | 
            +
                    end
         | 
| 421 455 | 
             
                  end
         | 
| 422 456 | 
             
                end
         | 
| 423 457 |  | 
| @@ -435,15 +469,9 @@ class Redis | |
| 435 469 |  | 
| 436 470 | 
             
                # Pop a value from a list, push it to another list and return it; or block
         | 
| 437 471 | 
             
                # until one is available.
         | 
| 438 | 
            -
                def brpoplpush(source, destination,  | 
| 439 | 
            -
                  case options
         | 
| 440 | 
            -
                  when Integer
         | 
| 441 | 
            -
                    # Issue deprecation notice in obnoxious mode...
         | 
| 442 | 
            -
                    options = { :timeout => options }
         | 
| 443 | 
            -
                  end
         | 
| 444 | 
            -
             | 
| 472 | 
            +
                def brpoplpush(source, destination, deprecated_timeout = 0, **options)
         | 
| 445 473 | 
             
                  ensure_same_node(:brpoplpush, [source, destination]) do |node|
         | 
| 446 | 
            -
                    node.brpoplpush(source, destination, options)
         | 
| 474 | 
            +
                    node.brpoplpush(source, destination, deprecated_timeout, **options)
         | 
| 447 475 | 
             
                  end
         | 
| 448 476 | 
             
                end
         | 
| 449 477 |  | 
| @@ -520,13 +548,13 @@ class Redis | |
| 520 548 | 
             
                end
         | 
| 521 549 |  | 
| 522 550 | 
             
                # Scan a set
         | 
| 523 | 
            -
                def sscan(key, cursor, options | 
| 524 | 
            -
                  node_for(key).sscan(key, cursor, options)
         | 
| 551 | 
            +
                def sscan(key, cursor, **options)
         | 
| 552 | 
            +
                  node_for(key).sscan(key, cursor, **options)
         | 
| 525 553 | 
             
                end
         | 
| 526 554 |  | 
| 527 555 | 
             
                # Scan a set and return an enumerator
         | 
| 528 | 
            -
                def sscan_each(key, options | 
| 529 | 
            -
                  node_for(key).sscan_each(key, options, &block)
         | 
| 556 | 
            +
                def sscan_each(key, **options, &block)
         | 
| 557 | 
            +
                  node_for(key).sscan_each(key, **options, &block)
         | 
| 530 558 | 
             
                end
         | 
| 531 559 |  | 
| 532 560 | 
             
                # Subtract multiple sets.
         | 
| @@ -581,6 +609,7 @@ class Redis | |
| 581 609 | 
             
                def zadd(key, *args)
         | 
| 582 610 | 
             
                  node_for(key).zadd(key, *args)
         | 
| 583 611 | 
             
                end
         | 
| 612 | 
            +
                ruby2_keywords(:zadd) if respond_to?(:ruby2_keywords, true)
         | 
| 584 613 |  | 
| 585 614 | 
             
                # Increment the score of a member in a sorted set.
         | 
| 586 615 | 
             
                def zincrby(key, increment, member)
         | 
| @@ -598,14 +627,14 @@ class Redis | |
| 598 627 | 
             
                end
         | 
| 599 628 |  | 
| 600 629 | 
             
                # Return a range of members in a sorted set, by index.
         | 
| 601 | 
            -
                def zrange(key, start, stop, options | 
| 602 | 
            -
                  node_for(key).zrange(key, start, stop, options)
         | 
| 630 | 
            +
                def zrange(key, start, stop, **options)
         | 
| 631 | 
            +
                  node_for(key).zrange(key, start, stop, **options)
         | 
| 603 632 | 
             
                end
         | 
| 604 633 |  | 
| 605 634 | 
             
                # Return a range of members in a sorted set, by index, with scores ordered
         | 
| 606 635 | 
             
                # from high to low.
         | 
| 607 | 
            -
                def zrevrange(key, start, stop, options | 
| 608 | 
            -
                  node_for(key).zrevrange(key, start, stop, options)
         | 
| 636 | 
            +
                def zrevrange(key, start, stop, **options)
         | 
| 637 | 
            +
                  node_for(key).zrevrange(key, start, stop, **options)
         | 
| 609 638 | 
             
                end
         | 
| 610 639 |  | 
| 611 640 | 
             
                # Determine the index of a member in a sorted set.
         | 
| @@ -625,14 +654,14 @@ class Redis | |
| 625 654 | 
             
                end
         | 
| 626 655 |  | 
| 627 656 | 
             
                # Return a range of members in a sorted set, by score.
         | 
| 628 | 
            -
                def zrangebyscore(key, min, max, options | 
| 629 | 
            -
                  node_for(key).zrangebyscore(key, min, max, options)
         | 
| 657 | 
            +
                def zrangebyscore(key, min, max, **options)
         | 
| 658 | 
            +
                  node_for(key).zrangebyscore(key, min, max, **options)
         | 
| 630 659 | 
             
                end
         | 
| 631 660 |  | 
| 632 661 | 
             
                # Return a range of members in a sorted set, by score, with scores ordered
         | 
| 633 662 | 
             
                # from high to low.
         | 
| 634 | 
            -
                def zrevrangebyscore(key, max, min, options | 
| 635 | 
            -
                  node_for(key).zrevrangebyscore(key, max, min, options)
         | 
| 663 | 
            +
                def zrevrangebyscore(key, max, min, **options)
         | 
| 664 | 
            +
                  node_for(key).zrevrangebyscore(key, max, min, **options)
         | 
| 636 665 | 
             
                end
         | 
| 637 666 |  | 
| 638 667 | 
             
                # Remove all members in a sorted set within the given scores.
         | 
| @@ -647,16 +676,16 @@ class Redis | |
| 647 676 |  | 
| 648 677 | 
             
                # Intersect multiple sorted sets and store the resulting sorted set in a new
         | 
| 649 678 | 
             
                # key.
         | 
| 650 | 
            -
                def zinterstore(destination, keys, options | 
| 679 | 
            +
                def zinterstore(destination, keys, **options)
         | 
| 651 680 | 
             
                  ensure_same_node(:zinterstore, [destination] + keys) do |node|
         | 
| 652 | 
            -
                    node.zinterstore(destination, keys, options)
         | 
| 681 | 
            +
                    node.zinterstore(destination, keys, **options)
         | 
| 653 682 | 
             
                  end
         | 
| 654 683 | 
             
                end
         | 
| 655 684 |  | 
| 656 685 | 
             
                # Add multiple sorted sets and store the resulting sorted set in a new key.
         | 
| 657 | 
            -
                def zunionstore(destination, keys, options | 
| 686 | 
            +
                def zunionstore(destination, keys, **options)
         | 
| 658 687 | 
             
                  ensure_same_node(:zunionstore, [destination] + keys) do |node|
         | 
| 659 | 
            -
                    node.zunionstore(destination, keys, options)
         | 
| 688 | 
            +
                    node.zunionstore(destination, keys, **options)
         | 
| 660 689 | 
             
                  end
         | 
| 661 690 | 
             
                end
         | 
| 662 691 |  | 
| @@ -665,9 +694,9 @@ class Redis | |
| 665 694 | 
             
                  node_for(key).hlen(key)
         | 
| 666 695 | 
             
                end
         | 
| 667 696 |  | 
| 668 | 
            -
                # Set  | 
| 669 | 
            -
                def hset(key,  | 
| 670 | 
            -
                  node_for(key).hset(key,  | 
| 697 | 
            +
                # Set multiple hash fields to multiple values.
         | 
| 698 | 
            +
                def hset(key, *attrs)
         | 
| 699 | 
            +
                  node_for(key).hset(key, *attrs)
         | 
| 671 700 | 
             
                end
         | 
| 672 701 |  | 
| 673 702 | 
             
                # Set the value of a hash field, only if the field does not exist.
         | 
| @@ -739,7 +768,7 @@ class Redis | |
| 739 768 | 
             
                end
         | 
| 740 769 |  | 
| 741 770 | 
             
                def subscribed?
         | 
| 742 | 
            -
                   | 
| 771 | 
            +
                  !!@subscribed_node
         | 
| 743 772 | 
             
                end
         | 
| 744 773 |  | 
| 745 774 | 
             
                # Listen for messages published to the given channels.
         | 
| @@ -757,7 +786,8 @@ class Redis | |
| 757 786 |  | 
| 758 787 | 
             
                # Stop listening for messages posted to the given channels.
         | 
| 759 788 | 
             
                def unsubscribe(*channels)
         | 
| 760 | 
            -
                  raise  | 
| 789 | 
            +
                  raise "Can't unsubscribe if not subscribed." unless subscribed?
         | 
| 790 | 
            +
             | 
| 761 791 | 
             
                  @subscribed_node.unsubscribe(*channels)
         | 
| 762 792 | 
             
                end
         | 
| 763 793 |  | 
| @@ -773,13 +803,26 @@ class Redis | |
| 773 803 | 
             
                end
         | 
| 774 804 |  | 
| 775 805 | 
             
                # Watch the given keys to determine execution of the MULTI/EXEC block.
         | 
| 776 | 
            -
                def watch(*keys)
         | 
| 777 | 
            -
                   | 
| 806 | 
            +
                def watch(*keys, &block)
         | 
| 807 | 
            +
                  ensure_same_node(:watch, keys) do |node|
         | 
| 808 | 
            +
                    @watch_key = key_tag(keys.first) || keys.first.to_s
         | 
| 809 | 
            +
             | 
| 810 | 
            +
                    begin
         | 
| 811 | 
            +
                      node.watch(*keys, &block)
         | 
| 812 | 
            +
                    rescue StandardError
         | 
| 813 | 
            +
                      @watch_key = nil
         | 
| 814 | 
            +
                      raise
         | 
| 815 | 
            +
                    end
         | 
| 816 | 
            +
                  end
         | 
| 778 817 | 
             
                end
         | 
| 779 818 |  | 
| 780 819 | 
             
                # Forget about all watched keys.
         | 
| 781 820 | 
             
                def unwatch
         | 
| 782 | 
            -
                  raise CannotDistribute, :unwatch
         | 
| 821 | 
            +
                  raise CannotDistribute, :unwatch unless @watch_key
         | 
| 822 | 
            +
             | 
| 823 | 
            +
                  result = node_for(@watch_key).unwatch
         | 
| 824 | 
            +
                  @watch_key = nil
         | 
| 825 | 
            +
                  result
         | 
| 783 826 | 
             
                end
         | 
| 784 827 |  | 
| 785 828 | 
             
                def pipelined
         | 
| @@ -787,18 +830,30 @@ class Redis | |
| 787 830 | 
             
                end
         | 
| 788 831 |  | 
| 789 832 | 
             
                # Mark the start of a transaction block.
         | 
| 790 | 
            -
                def multi
         | 
| 791 | 
            -
                  raise CannotDistribute, :multi
         | 
| 833 | 
            +
                def multi(&block)
         | 
| 834 | 
            +
                  raise CannotDistribute, :multi unless @watch_key
         | 
| 835 | 
            +
             | 
| 836 | 
            +
                  result = node_for(@watch_key).multi(&block)
         | 
| 837 | 
            +
                  @watch_key = nil if block_given?
         | 
| 838 | 
            +
                  result
         | 
| 792 839 | 
             
                end
         | 
| 793 840 |  | 
| 794 841 | 
             
                # Execute all commands issued after MULTI.
         | 
| 795 842 | 
             
                def exec
         | 
| 796 | 
            -
                  raise CannotDistribute, :exec
         | 
| 843 | 
            +
                  raise CannotDistribute, :exec unless @watch_key
         | 
| 844 | 
            +
             | 
| 845 | 
            +
                  result = node_for(@watch_key).exec
         | 
| 846 | 
            +
                  @watch_key = nil
         | 
| 847 | 
            +
                  result
         | 
| 797 848 | 
             
                end
         | 
| 798 849 |  | 
| 799 850 | 
             
                # Discard all commands issued after MULTI.
         | 
| 800 851 | 
             
                def discard
         | 
| 801 | 
            -
                  raise CannotDistribute, :discard
         | 
| 852 | 
            +
                  raise CannotDistribute, :discard unless @watch_key
         | 
| 853 | 
            +
             | 
| 854 | 
            +
                  result = node_for(@watch_key).discard
         | 
| 855 | 
            +
                  @watch_key = nil
         | 
| 856 | 
            +
                  result
         | 
| 802 857 | 
             
                end
         | 
| 803 858 |  | 
| 804 859 | 
             
                # Control remote script registry.
         | 
| @@ -857,7 +912,7 @@ class Redis | |
| 857 912 | 
             
                  self.class.new(@node_configs, @default_options)
         | 
| 858 913 | 
             
                end
         | 
| 859 914 |  | 
| 860 | 
            -
             | 
| 915 | 
            +
                protected
         | 
| 861 916 |  | 
| 862 917 | 
             
                def on_each_node(command, *args)
         | 
| 863 918 | 
             
                  nodes.map do |node|
         | 
    
        data/lib/redis/errors.rb
    CHANGED
    
    
    
        data/lib/redis/hash_ring.rb
    CHANGED
    
    | @@ -1,8 +1,9 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'zlib'
         | 
| 2 4 |  | 
| 3 5 | 
             
            class Redis
         | 
| 4 6 | 
             
              class HashRing
         | 
| 5 | 
            -
             | 
| 6 7 | 
             
                POINTS_PER_SERVER = 160 # this is the default in libmemcached
         | 
| 7 8 |  | 
| 8 9 | 
             
                attr_reader :ring, :sorted_keys, :replicas, :nodes
         | 
| @@ -10,7 +11,7 @@ class Redis | |
| 10 11 | 
             
                # nodes is a list of objects that have a proper to_s representation.
         | 
| 11 12 | 
             
                # replicas indicates how many virtual points should be used pr. node,
         | 
| 12 13 | 
             
                # replicas are required to improve the distribution.
         | 
| 13 | 
            -
                def initialize(nodes=[], replicas=POINTS_PER_SERVER)
         | 
| 14 | 
            +
                def initialize(nodes = [], replicas = POINTS_PER_SERVER)
         | 
| 14 15 | 
             
                  @replicas = replicas
         | 
| 15 16 | 
             
                  @ring = {}
         | 
| 16 17 | 
             
                  @nodes = []
         | 
| @@ -32,11 +33,11 @@ class Redis | |
| 32 33 | 
             
                end
         | 
| 33 34 |  | 
| 34 35 | 
             
                def remove_node(node)
         | 
| 35 | 
            -
                  @nodes.reject!{|n| n.id == node.id}
         | 
| 36 | 
            +
                  @nodes.reject! { |n| n.id == node.id }
         | 
| 36 37 | 
             
                  @replicas.times do |i|
         | 
| 37 38 | 
             
                    key = Zlib.crc32("#{node.id}:#{i}")
         | 
| 38 39 | 
             
                    @ring.delete(key)
         | 
| 39 | 
            -
                    @sorted_keys.reject! {|k| k == key}
         | 
| 40 | 
            +
                    @sorted_keys.reject! { |k| k == key }
         | 
| 40 41 | 
             
                  end
         | 
| 41 42 | 
             
                end
         | 
| 42 43 |  | 
| @@ -46,27 +47,29 @@ class Redis | |
| 46 47 | 
             
                end
         | 
| 47 48 |  | 
| 48 49 | 
             
                def get_node_pos(key)
         | 
| 49 | 
            -
                  return [nil,nil] if @ring. | 
| 50 | 
            +
                  return [nil, nil] if @ring.empty?
         | 
| 51 | 
            +
             | 
| 50 52 | 
             
                  crc = Zlib.crc32(key)
         | 
| 51 53 | 
             
                  idx = HashRing.binary_search(@sorted_keys, crc)
         | 
| 52 | 
            -
                   | 
| 54 | 
            +
                  [@ring[@sorted_keys[idx]], idx]
         | 
| 53 55 | 
             
                end
         | 
| 54 56 |  | 
| 55 57 | 
             
                def iter_nodes(key)
         | 
| 56 | 
            -
                  return [nil,nil] if @ring. | 
| 58 | 
            +
                  return [nil, nil] if @ring.empty?
         | 
| 59 | 
            +
             | 
| 57 60 | 
             
                  _, pos = get_node_pos(key)
         | 
| 58 61 | 
             
                  @ring.size.times do |n|
         | 
| 59 | 
            -
                    yield @ring[@sorted_keys[(pos+n) % @ring.size]]
         | 
| 62 | 
            +
                    yield @ring[@sorted_keys[(pos + n) % @ring.size]]
         | 
| 60 63 | 
             
                  end
         | 
| 61 64 | 
             
                end
         | 
| 62 65 |  | 
| 63 66 | 
             
                # Find the closest index in HashRing with value <= the given value
         | 
| 64 | 
            -
                def self.binary_search(ary, value | 
| 67 | 
            +
                def self.binary_search(ary, value)
         | 
| 65 68 | 
             
                  upper = ary.size - 1
         | 
| 66 69 | 
             
                  lower = 0
         | 
| 67 70 | 
             
                  idx = 0
         | 
| 68 71 |  | 
| 69 | 
            -
                  while | 
| 72 | 
            +
                  while lower <= upper
         | 
| 70 73 | 
             
                    idx = (lower + upper) / 2
         | 
| 71 74 | 
             
                    comp = ary[idx] <=> value
         | 
| 72 75 |  | 
| @@ -79,10 +82,8 @@ class Redis | |
| 79 82 | 
             
                    end
         | 
| 80 83 | 
             
                  end
         | 
| 81 84 |  | 
| 82 | 
            -
                  if upper < 0
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                  end
         | 
| 85 | 
            -
                  return upper
         | 
| 85 | 
            +
                  upper = ary.size - 1 if upper < 0
         | 
| 86 | 
            +
                  upper
         | 
| 86 87 | 
             
                end
         | 
| 87 88 | 
             
              end
         | 
| 88 89 | 
             
            end
         |