memcache-client 1.7.0 → 1.7.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.
- data/FAQ.rdoc +24 -0
- data/History.rdoc +14 -0
- data/README.rdoc +7 -2
- data/lib/continuum_native.rb +41 -0
- data/lib/memcache.rb +40 -69
- data/test/test_mem_cache.rb +4 -3
- metadata +4 -2
    
        data/FAQ.rdoc
    ADDED
    
    | @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            = Memcache-client FAQ
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            == Does memcache-client work with Ruby 1.9?
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Yes, Ruby 1.9 is supported.  The test suite should pass completely on 1.8.6 and 1.9.1.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
             | 
| 8 | 
            +
            == I'm seeing "execution expired" or "time's up!" errors, what's that all about?
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            memcache-client 1.6.x+ now has socket operations timed out by default.  This is to prevent
         | 
| 11 | 
            +
            the Ruby process from hanging if memcached or starling get into a bad state, which has been
         | 
| 12 | 
            +
            seen in production by both 37signals and FiveRuns.  The default timeout is 0.5 seconds, which
         | 
| 13 | 
            +
            should be more than enough time under normal circumstances.  It's possible to hit a storm of
         | 
| 14 | 
            +
            concurrent events which cause this timer to expire: a large Ruby VM can cause the GC to take
         | 
| 15 | 
            +
            a while, while also storing a large (500k-1MB value), for example.
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            You can increase the timeout or disable them completely with the following configuration:
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              Rails:
         | 
| 20 | 
            +
              config.cache_store = :mem_cache_store, 'server1', 'server2', { :timeout => nil } # no timeout
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              native:
         | 
| 23 | 
            +
              MemCache.new ['server1', 'server2'], { :timeout => 1.0 } # 1 second timeout
         | 
| 24 | 
            +
             | 
    
        data/History.rdoc
    CHANGED
    
    | @@ -1,3 +1,17 @@ | |
| 1 | 
            +
            = 1.7.1
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * Performance optimizations: 
         | 
| 4 | 
            +
              * Rely on higher performance operating system socket timeouts for low-level socket 
         | 
| 5 | 
            +
                read/writes where possible, instead of the (slower) SystemTimer or (slowest,
         | 
| 6 | 
            +
                unreliable) Timeout libraries.
         | 
| 7 | 
            +
              * the native binary search is back!  The recent performance tuning made the binary search 
         | 
| 8 | 
            +
                a bottleneck again so it had to return.  It uses RubyInline to compile the native extension and 
         | 
| 9 | 
            +
                silently falls back to pure Ruby if anything fails.  Make sure you run:
         | 
| 10 | 
            +
                `gem install RubyInline` if you want ultimate performance.
         | 
| 11 | 
            +
              * the changes make memcache-client 100% faster than 1.7.0 in my performance test on Ruby 1.8.6: 
         | 
| 12 | 
            +
                15 sec -> 8 sec.
         | 
| 13 | 
            +
            * Fix several logging issues.
         | 
| 14 | 
            +
             | 
| 1 15 | 
             
            = 1.7.0 (2009-03-08)
         | 
| 2 16 |  | 
| 3 17 | 
             
            * Go through the memcached protocol document and implement any commands not already implemented:
         | 
    
        data/README.rdoc
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            = memcache-client
         | 
| 2 2 |  | 
| 3 | 
            -
            A  | 
| 3 | 
            +
            A ruby library for accessing memcached.
         | 
| 4 4 |  | 
| 5 5 | 
             
            Source:
         | 
| 6 6 |  | 
| @@ -26,7 +26,8 @@ Or with multiple servers: | |
| 26 26 | 
             
            == Tuning memcache-client
         | 
| 27 27 |  | 
| 28 28 | 
             
            The MemCache.new method takes a number of options which can be useful at times.  Please
         | 
| 29 | 
            -
            read the source comments there for an overview.
         | 
| 29 | 
            +
            read the source comments there for an overview.  If you are using Ruby 1.8.x and using
         | 
| 30 | 
            +
            multiple memcached servers, you should install the RubyInline gem for ultimate performance.
         | 
| 30 31 |  | 
| 31 32 |  | 
| 32 33 | 
             
            == Using memcache-client with Rails
         | 
| @@ -43,3 +44,7 @@ Eric Hodel and the seattle.rb crew. | |
| 43 44 | 
             
            Email::        mailto:mperham@gmail.com
         | 
| 44 45 | 
             
            Twitter::      mperham[http://twitter.com/mperham]
         | 
| 45 46 | 
             
            WWW::          http://mikeperham.com
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            If my work on memcache-client is something you support, please take a moment to
         | 
| 49 | 
            +
            recommend me at WWR[http://workingwithrails.com/person/10797-mike-perham].  I'm not
         | 
| 50 | 
            +
            asking for money, just a electronic "thumbs up".
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            module Continuum
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              class << self
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                # Native extension to perform the binary search within the continuum
         | 
| 6 | 
            +
                # space.  There's a pure ruby version in memcache.rb so this is purely
         | 
| 7 | 
            +
                # optional for performance and only necessary if you are using multiple
         | 
| 8 | 
            +
                # memcached servers.
         | 
| 9 | 
            +
                begin
         | 
| 10 | 
            +
                  require 'inline'
         | 
| 11 | 
            +
                  inline do |builder|
         | 
| 12 | 
            +
                    builder.c <<-EOM
         | 
| 13 | 
            +
                    int binary_search(VALUE ary, unsigned int r) {
         | 
| 14 | 
            +
                        int upper = RARRAY_LEN(ary) - 1;
         | 
| 15 | 
            +
                        int lower = 0;
         | 
| 16 | 
            +
                        int idx = 0;
         | 
| 17 | 
            +
                        ID value = rb_intern("value");
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                        while (lower <= upper) {
         | 
| 20 | 
            +
                            idx = (lower + upper) / 2;
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                            VALUE continuumValue = rb_funcall(RARRAY_PTR(ary)[idx], value, 0);
         | 
| 23 | 
            +
                            unsigned int l = NUM2UINT(continuumValue);
         | 
| 24 | 
            +
                            if (l == r) {
         | 
| 25 | 
            +
                                return idx;
         | 
| 26 | 
            +
                            }
         | 
| 27 | 
            +
                            else if (l > r) {
         | 
| 28 | 
            +
                                upper = idx - 1;
         | 
| 29 | 
            +
                            }
         | 
| 30 | 
            +
                            else {
         | 
| 31 | 
            +
                                lower = idx + 1;
         | 
| 32 | 
            +
                            }
         | 
| 33 | 
            +
                        }
         | 
| 34 | 
            +
                        return upper;
         | 
| 35 | 
            +
                    }
         | 
| 36 | 
            +
                    EOM
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                rescue Exception => e
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
    
        data/lib/memcache.rb
    CHANGED
    
    | @@ -33,7 +33,7 @@ class MemCache | |
| 33 33 | 
             
              ##
         | 
| 34 34 | 
             
              # The version of MemCache you are using.
         | 
| 35 35 |  | 
| 36 | 
            -
              VERSION = '1.7. | 
| 36 | 
            +
              VERSION = '1.7.1'
         | 
| 37 37 |  | 
| 38 38 | 
             
              ##
         | 
| 39 39 | 
             
              # Default options for the cache object.
         | 
| @@ -223,7 +223,7 @@ class MemCache | |
| 223 223 |  | 
| 224 224 | 
             
              def get(key, raw = false)
         | 
| 225 225 | 
             
                with_server(key) do |server, cache_key|
         | 
| 226 | 
            -
                  logger.debug { "get #{key} from #{server.inspect} | 
| 226 | 
            +
                  logger.debug { "get #{key} from #{server.inspect}" } if logger
         | 
| 227 227 | 
             
                  value = cache_get server, cache_key
         | 
| 228 228 | 
             
                  return nil if value.nil?
         | 
| 229 229 | 
             
                  value = Marshal.load value unless raw
         | 
| @@ -333,12 +333,11 @@ class MemCache | |
| 333 333 | 
             
                with_server(key) do |server, cache_key|
         | 
| 334 334 |  | 
| 335 335 | 
             
                  value = Marshal.dump value unless raw
         | 
| 336 | 
            -
                   | 
| 337 | 
            -
                  logger.debug { "set #{key} to #{server.inspect}: #{data.size}" } if logger
         | 
| 336 | 
            +
                  logger.debug { "set #{key} to #{server.inspect}: #{value.to_s.size}" } if logger
         | 
| 338 337 |  | 
| 339 | 
            -
                  raise MemCacheError, "Value too large, memcached can only store 1MB of data per key" if  | 
| 338 | 
            +
                  raise MemCacheError, "Value too large, memcached can only store 1MB of data per key" if value.to_s.size > ONE_MB
         | 
| 340 339 |  | 
| 341 | 
            -
                  command = "set #{cache_key} 0 #{expiry} #{ | 
| 340 | 
            +
                  command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}#{noreply}\r\n#{value}\r\n"
         | 
| 342 341 |  | 
| 343 342 | 
             
                  with_socket_management(server) do |socket|
         | 
| 344 343 | 
             
                    socket.write command
         | 
| @@ -380,11 +379,10 @@ class MemCache | |
| 380 379 | 
             
                updated = yield value
         | 
| 381 380 |  | 
| 382 381 | 
             
                with_server(key) do |server, cache_key|
         | 
| 383 | 
            -
                  logger.debug { "cas #{key} to #{server.inspect}: #{data.size}" } if logger
         | 
| 384 382 |  | 
| 385 383 | 
             
                  value = Marshal.dump updated unless raw
         | 
| 386 | 
            -
                   | 
| 387 | 
            -
                  command = "cas #{cache_key} 0 #{expiry} #{value.size} #{token}#{noreply}\r\n#{value}\r\n"
         | 
| 384 | 
            +
                  logger.debug { "cas #{key} to #{server.inspect}: #{value.to_s.size}" } if logger
         | 
| 385 | 
            +
                  command = "cas #{cache_key} 0 #{expiry} #{value.to_s.size} #{token}#{noreply}\r\n#{value}\r\n"
         | 
| 388 386 |  | 
| 389 387 | 
             
                  with_socket_management(server) do |socket|
         | 
| 390 388 | 
             
                    socket.write command
         | 
| @@ -720,7 +718,7 @@ class MemCache | |
| 720 718 |  | 
| 721 719 | 
             
              def gets(key, raw = false)
         | 
| 722 720 | 
             
                with_server(key) do |server, cache_key|
         | 
| 723 | 
            -
                  logger.debug { "gets #{key} from #{server.inspect} | 
| 721 | 
            +
                  logger.debug { "gets #{key} from #{server.inspect}" } if logger
         | 
| 724 722 | 
             
                  result = with_socket_management(server) do |socket|
         | 
| 725 723 | 
             
                    socket.write "gets #{cache_key}\r\n"
         | 
| 726 724 | 
             
                    keyline = socket.gets # "VALUE <key> <flags> <bytes> <cas token>\r\n"
         | 
| @@ -822,7 +820,7 @@ class MemCache | |
| 822 820 |  | 
| 823 821 | 
             
                  block.call(socket)
         | 
| 824 822 |  | 
| 825 | 
            -
                rescue SocketError, Timeout::Error => err
         | 
| 823 | 
            +
                rescue SocketError, Errno::EAGAIN, Timeout::Error => err
         | 
| 826 824 | 
             
                  logger.warn { "Socket failure: #{err.message}" } if logger
         | 
| 827 825 | 
             
                  server.mark_dead(err)
         | 
| 828 826 | 
             
                  handle_error(server, err)
         | 
| @@ -921,13 +919,6 @@ class MemCache | |
| 921 919 |  | 
| 922 920 | 
             
              class Server
         | 
| 923 921 |  | 
| 924 | 
            -
                ##
         | 
| 925 | 
            -
                # The amount of time to wait to establish a connection with a memcached
         | 
| 926 | 
            -
                # server.  If a connection cannot be established within this time limit,
         | 
| 927 | 
            -
                # the server will be marked as down.
         | 
| 928 | 
            -
             | 
| 929 | 
            -
                CONNECT_TIMEOUT = 0.25
         | 
| 930 | 
            -
             | 
| 931 922 | 
             
                ##
         | 
| 932 923 | 
             
                # The amount of time to wait before attempting to re-establish a
         | 
| 933 924 | 
             
                # connection with a server that is marked dead.
         | 
| @@ -1011,14 +1002,11 @@ class MemCache | |
| 1011 1002 |  | 
| 1012 1003 | 
             
                  # Attempt to connect if not already connected.
         | 
| 1013 1004 | 
             
                  begin
         | 
| 1014 | 
            -
                    @sock =  | 
| 1015 | 
            -
             | 
| 1016 | 
            -
                    if Socket.constants.include? 'TCP_NODELAY' then
         | 
| 1017 | 
            -
                      @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
         | 
| 1018 | 
            -
                    end
         | 
| 1005 | 
            +
                    @sock = connect_to(@host, @port, @timeout)
         | 
| 1006 | 
            +
                    @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
         | 
| 1019 1007 | 
             
                    @retry  = nil
         | 
| 1020 1008 | 
             
                    @status = 'CONNECTED'
         | 
| 1021 | 
            -
                  rescue SocketError, SystemCallError, IOError | 
| 1009 | 
            +
                  rescue SocketError, SystemCallError, IOError => err
         | 
| 1022 1010 | 
             
                    logger.warn { "Unable to open socket: #{err.class.name}, #{err.message}" } if logger
         | 
| 1023 1011 | 
             
                    mark_dead err
         | 
| 1024 1012 | 
             
                  end
         | 
| @@ -1026,6 +1014,33 @@ class MemCache | |
| 1026 1014 | 
             
                  return @sock
         | 
| 1027 1015 | 
             
                end
         | 
| 1028 1016 |  | 
| 1017 | 
            +
                def connect_to(host, port, timeout=nil)
         | 
| 1018 | 
            +
                  addr = Socket.getaddrinfo(host, nil)
         | 
| 1019 | 
            +
                  sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
         | 
| 1020 | 
            +
             | 
| 1021 | 
            +
                  if timeout
         | 
| 1022 | 
            +
                    secs = Integer(timeout)
         | 
| 1023 | 
            +
                    usecs = Integer((timeout - secs) * 1_000_000)
         | 
| 1024 | 
            +
                    optval = [secs, usecs].pack("l_2")
         | 
| 1025 | 
            +
                    sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
         | 
| 1026 | 
            +
                    sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
         | 
| 1027 | 
            +
             | 
| 1028 | 
            +
                    # Socket timeouts don't work for more complex IO operations
         | 
| 1029 | 
            +
                    # like gets which lay on top of read.  We need to fall back to
         | 
| 1030 | 
            +
                    # the standard Timeout mechanism.
         | 
| 1031 | 
            +
                    sock.instance_eval <<-EOR
         | 
| 1032 | 
            +
                      alias :blocking_gets :gets
         | 
| 1033 | 
            +
                      def gets
         | 
| 1034 | 
            +
                        MemCacheTimer.timeout(#{timeout}) do
         | 
| 1035 | 
            +
                          self.blocking_gets
         | 
| 1036 | 
            +
                        end
         | 
| 1037 | 
            +
                      end
         | 
| 1038 | 
            +
                    EOR
         | 
| 1039 | 
            +
                  end
         | 
| 1040 | 
            +
                  sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
         | 
| 1041 | 
            +
                  sock
         | 
| 1042 | 
            +
                end
         | 
| 1043 | 
            +
             | 
| 1029 1044 | 
             
                ##
         | 
| 1030 1045 | 
             
                # Close the connection to the memcached server targeted by this
         | 
| 1031 1046 | 
             
                # object.  The server is not considered dead.
         | 
| @@ -1059,51 +1074,6 @@ class MemCache | |
| 1059 1074 |  | 
| 1060 1075 | 
             
            end
         | 
| 1061 1076 |  | 
| 1062 | 
            -
            # TCPSocket facade class which implements timeouts.
         | 
| 1063 | 
            -
            class TCPTimeoutSocket
         | 
| 1064 | 
            -
              
         | 
| 1065 | 
            -
              def initialize(host, port, timeout)
         | 
| 1066 | 
            -
                MemCacheTimer.timeout(MemCache::Server::CONNECT_TIMEOUT) do
         | 
| 1067 | 
            -
                  @sock = TCPSocket.new(host, port)
         | 
| 1068 | 
            -
                  @len = timeout
         | 
| 1069 | 
            -
                end
         | 
| 1070 | 
            -
              end
         | 
| 1071 | 
            -
              
         | 
| 1072 | 
            -
              def write(*args)
         | 
| 1073 | 
            -
                MemCacheTimer.timeout(@len) do
         | 
| 1074 | 
            -
                  @sock.write(*args)
         | 
| 1075 | 
            -
                end
         | 
| 1076 | 
            -
              end
         | 
| 1077 | 
            -
              
         | 
| 1078 | 
            -
              def gets(*args)
         | 
| 1079 | 
            -
                MemCacheTimer.timeout(@len) do
         | 
| 1080 | 
            -
                  @sock.gets(*args)
         | 
| 1081 | 
            -
                end
         | 
| 1082 | 
            -
              end
         | 
| 1083 | 
            -
              
         | 
| 1084 | 
            -
              def read(*args)
         | 
| 1085 | 
            -
                MemCacheTimer.timeout(@len) do
         | 
| 1086 | 
            -
                  @sock.read(*args)
         | 
| 1087 | 
            -
                end
         | 
| 1088 | 
            -
              end
         | 
| 1089 | 
            -
              
         | 
| 1090 | 
            -
              def _socket
         | 
| 1091 | 
            -
                @sock
         | 
| 1092 | 
            -
              end
         | 
| 1093 | 
            -
              
         | 
| 1094 | 
            -
              def method_missing(meth, *args)
         | 
| 1095 | 
            -
                @sock.__send__(meth, *args)
         | 
| 1096 | 
            -
              end
         | 
| 1097 | 
            -
              
         | 
| 1098 | 
            -
              def closed?
         | 
| 1099 | 
            -
                @sock.closed?
         | 
| 1100 | 
            -
              end
         | 
| 1101 | 
            -
              
         | 
| 1102 | 
            -
              def close
         | 
| 1103 | 
            -
                @sock.close
         | 
| 1104 | 
            -
              end
         | 
| 1105 | 
            -
            end
         | 
| 1106 | 
            -
             | 
| 1107 1077 | 
             
            module Continuum
         | 
| 1108 1078 | 
             
              POINTS_PER_SERVER = 160 # this is the default in libmemcached
         | 
| 1109 1079 |  | 
| @@ -1142,3 +1112,4 @@ module Continuum | |
| 1142 1112 | 
             
                end
         | 
| 1143 1113 | 
             
              end
         | 
| 1144 1114 | 
             
            end
         | 
| 1115 | 
            +
            require 'continuum_native'
         | 
    
        data/test/test_mem_cache.rb
    CHANGED
    
    | @@ -1076,7 +1076,8 @@ class TestMemCache < Test::Unit::TestCase | |
| 1076 1076 | 
             
              def test_crazy_multithreaded_access
         | 
| 1077 1077 | 
             
                requirement(memcached_running?, 'A real memcached server must be running for performance testing') do
         | 
| 1078 1078 |  | 
| 1079 | 
            -
                   | 
| 1079 | 
            +
                  # Use a null logger to verify logging doesn't blow up at runtime
         | 
| 1080 | 
            +
                  cache = MemCache.new(['localhost:11211', '127.0.0.1:11211'], :logger => Logger.new('/dev/null'))
         | 
| 1080 1081 | 
             
                  cache.flush_all
         | 
| 1081 1082 | 
             
                  workers = []
         | 
| 1082 1083 |  | 
| @@ -1111,9 +1112,9 @@ class TestMemCache < Test::Unit::TestCase | |
| 1111 1112 | 
             
                        assert cache.decr('c', 5) > 14
         | 
| 1112 1113 | 
             
                        assert_equal 11, cache.get('b')
         | 
| 1113 1114 | 
             
                        d = cache.get('d', true)
         | 
| 1114 | 
            -
                        assert_match /\Aab | 
| 1115 | 
            +
                        assert_match /\Aab*\Z/, d
         | 
| 1115 1116 | 
             
                        e = cache.get('e', true)
         | 
| 1116 | 
            -
                        assert_match /\Ay | 
| 1117 | 
            +
                        assert_match /\Ay*x\Z/, e
         | 
| 1117 1118 | 
             
                      end
         | 
| 1118 1119 | 
             
                    end
         | 
| 1119 1120 | 
             
                  end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: memcache-client
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              version: 1.7. | 
| 4 | 
            +
              version: 1.7.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors: 
         | 
| 7 7 | 
             
            - Eric Hodel
         | 
| @@ -11,7 +11,7 @@ autorequire: | |
| 11 11 | 
             
            bindir: bin
         | 
| 12 12 | 
             
            cert_chain: []
         | 
| 13 13 |  | 
| 14 | 
            -
            date: 2009-03- | 
| 14 | 
            +
            date: 2009-03-28 00:00:00 -05:00
         | 
| 15 15 | 
             
            default_executable: 
         | 
| 16 16 | 
             
            dependencies: []
         | 
| 17 17 |  | 
| @@ -24,11 +24,13 @@ extensions: [] | |
| 24 24 | 
             
            extra_rdoc_files: []
         | 
| 25 25 |  | 
| 26 26 | 
             
            files: 
         | 
| 27 | 
            +
            - FAQ.rdoc
         | 
| 27 28 | 
             
            - README.rdoc
         | 
| 28 29 | 
             
            - LICENSE.txt
         | 
| 29 30 | 
             
            - History.rdoc
         | 
| 30 31 | 
             
            - Rakefile
         | 
| 31 32 | 
             
            - lib/memcache.rb
         | 
| 33 | 
            +
            - lib/continuum_native.rb
         | 
| 32 34 | 
             
            has_rdoc: false
         | 
| 33 35 | 
             
            homepage: http://github.com/mperham/memcache-client
         | 
| 34 36 | 
             
            post_install_message: 
         |