]> git.saurik.com Git - redis.git/commitdiff
client libraries synched in git
authorantirez <antirez@gmail.com>
Tue, 26 May 2009 16:10:50 +0000 (18:10 +0200)
committerantirez <antirez@gmail.com>
Tue, 26 May 2009 16:10:50 +0000 (18:10 +0200)
18 files changed:
Changelog
client-libraries/cpp/TODO
client-libraries/cpp/redisclient.cpp
client-libraries/cpp/redisclient.h
client-libraries/lua/redis.lua
client-libraries/ruby/.gitignore
client-libraries/ruby/README.markdown
client-libraries/ruby/Rakefile
client-libraries/ruby/benchmarking/suite.rb [new file with mode: 0644]
client-libraries/ruby/benchmarking/worker.rb [new file with mode: 0644]
client-libraries/ruby/lib/pipeline.rb
client-libraries/ruby/lib/redis.rb
client-libraries/ruby/lib/server.rb
client-libraries/ruby/redis-rb.gemspec
client-libraries/ruby/spec/redis_spec.rb
client-libraries/ruby/spec/server_spec.rb [new file with mode: 0644]
client-libraries/ruby/tasks/redis.tasks.rb
redis.c

index 4a26c5a223cb77edbc364ca81a214c9a00d874a1..94d43a3dd95a09cae920b8d03ca256146e573367 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,6 @@
+2009-05-26 ignore gcc warning about write() return code not checked. It is esplicitily this way since the "max number of clients reached" is a best-effort error
+2009-05-26 max bytes of a received command enlarged from 1k to 16k
+2009-05-26 RubyRedis: set TCP_NODELAY TCP socket option to to disable the neagle algorithm. Makes a huge difference under some OS, notably Linux
 2009-05-25 maxclients implemented, see redis.conf for details
 2009-05-25 INFO command now reports replication info
 2009-05-25 minor fix to RubyRedis about bulk commands sent without arguments
index 55967cf40884e3332959dfab697e9a208770d5ef..3e84754d86ce7d1ac9800f88d6b9cc1f0906d748 100644 (file)
@@ -1,9 +1,16 @@
-+ finish command implementations 
-= finish unit tests
-  Only a few left, to test the SORT command's edge cases (e.g. BY pattern)
-+ determine if we should not use bool return values and instead throw redis_error. (latter).
-+ maybe more fine-grained exceptions (not just redis_error but operation_not_permitted_error, etc.)
-- benchmarking
+command handlers:
+- support DEL as vararg
+- support MLLEN and MSCARD
+
+unit tests:
+- sort with limit
+- sort lexicographically
+- sort with pattern and weights
+
+extras:
+- benchmarking "test" app
 - consistent hashing?
-- make all string literals constants so they can be easily changed (minor)
+
+maybe/someday:
+- make all string literals constants so they can be easily changed
 - add conveniences that store a std::set in its entirety (same for std::list, std::vector)
index 75e6e8781bbb3fe8f03250ca786920b72cd6b5a6..beaa876a0e517844830d5f474d8b1a495175f70c 100644 (file)
@@ -663,16 +663,24 @@ namespace redis
                                 const client::string_type & by_pattern, 
                                 client::int_type limit_start, 
                                 client::int_type limit_end, 
-                                const client::string_type & get_pattern
+                                const client::string_vector & get_patterns
                                 client::sort_order order,
                                 bool lexicographically)
   {
-    send_(makecmd("SORT") << key 
-             << " BY "    << by_pattern
-             << " LIMIT " << limit_start << ' ' << limit_end
-             << " GET "   << get_pattern
-             << (order == sort_order_ascending ? " ASC" : " DESC")
-             << (lexicographically ? " ALPHA" : ""));
+    makecmd m("SORT");
+
+    m << key 
+      << " BY "    << by_pattern
+      << " LIMIT " << limit_start << ' ' << limit_end;
+
+    client::string_vector::const_iterator it = get_patterns.begin();
+    for ( ; it != get_patterns.end(); ++it) 
+      m << " GET " << *it;
+
+    m << (order == sort_order_ascending ? " ASC" : " DESC")
+      << (lexicographically ? " ALPHA" : "");
+
+    send_(m);
 
     return recv_multi_bulk_reply_(out);
   }
@@ -681,7 +689,7 @@ namespace redis
   {
     send_(makecmd("SAVE", true));
     recv_ok_reply_();
-  e.g. }
+  }
 
   void client::bgsave()
   {
index b1fbb582fb1a76cdcfbdec4a19a0e40e2c3b55a9..d3fee780a67e268b4c4413866bf49dc69d86c29a 100644 (file)
@@ -421,7 +421,7 @@ namespace redis
                   const string_type & by_pattern, 
                   int_type limit_start, 
                   int_type limit_end, 
-                  const string_type & get_pattern
+                  const string_vector & get_patterns
                   sort_order order = sort_order_ascending,
                   bool lexicographically = false);
 
index 2455ceb3d17b8093aa03c3a2dd77cc8c6070f469..87351745a366467f18e0e400c7c4ab92778032ae 100644 (file)
@@ -316,7 +316,7 @@ redis_commands = {
         function(client, command) 
             -- let's fire and forget! the connection is closed as soon 
             -- as the SHUTDOWN command is received by the server.
-            network.write(command .. protocol.newline)
+            network.write(client, command .. protocol.newline)
         end
     ), 
 
index 10d0977b0fe6e46381ec7c522d2327b2bdf91015..2fd676b3cf965a7c1b7935da34d427cbb3204dff 100644 (file)
@@ -1,4 +1,5 @@
 nohup.out
 redis/*
 rdsrv
-pkg/*
\ No newline at end of file
+pkg/*
+.idea
index 2518c4212515260caef1cc623745aa320868aeca..b36633d62fcc7b5643514851a46fb327ea37859a 100644 (file)
@@ -12,7 +12,10 @@ See [redis on code.google.com](http://code.google.com/p/redis/wiki/README) for m
 
 ## Dependencies
 
-1. redis - 
+1. rspec - 
+               sudo gem install rspec
+
+2. redis - 
 
                rake redis:install
 
@@ -20,7 +23,7 @@ See [redis on code.google.com](http://code.google.com/p/redis/wiki/README) for m
 
                rake dtach:install
 
-3. svn - git is the new black, but we need it for the google codes.
+3. git - git is the new black.
 
 ## Setup
 
index bdc9f373b87a27d201e4e91988471eb9ecbd31c3..9bed311a3e675a25989e1c997fde695b6217f58d 100644 (file)
@@ -8,10 +8,10 @@ require 'tasks/redis.tasks'
 
 GEM = 'redis'
 GEM_NAME = 'redis'
-GEM_VERSION = '0.0.3.3'
+GEM_VERSION = '0.0.3.4'
 AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley', 'Matthew Clark']
-EMAIL = "matt.clark@punchstock.com"
-HOMEPAGE = "http://github.com/winescout/redis-rb"
+EMAIL = "ez@engineyard.com"
+HOMEPAGE = "http://github.com/ezmobius/redis-rb"
 SUMMARY = "Ruby client library for redis key value storage server"
 
 spec = Gem::Specification.new do |s|
@@ -25,10 +25,7 @@ spec = Gem::Specification.new do |s|
   s.authors = AUTHORS
   s.email = EMAIL
   s.homepage = HOMEPAGE
-  
-  # Uncomment this to add a dependency
-  # s.add_dependency "foo"
-  
+  s.add_dependency "rspec"
   s.require_path = 'lib'
   s.autorequire = GEM
   s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,spec}/**/*")
diff --git a/client-libraries/ruby/benchmarking/suite.rb b/client-libraries/ruby/benchmarking/suite.rb
new file mode 100644 (file)
index 0000000..f03694b
--- /dev/null
@@ -0,0 +1,24 @@
+require 'fileutils'
+
+def run_in_background(command)
+  fork { system command }
+end
+
+def with_all_segments(&block)
+  0.upto(9) do |segment_number|
+    block_size = 100000
+    start_index = segment_number * block_size
+    end_index = start_index + block_size - 1
+    block.call(start_index, end_index)
+  end
+end
+
+#with_all_segments do |start_index, end_index|
+#  puts "Initializing keys from #{start_index} to #{end_index}"
+#  system "ruby worker.rb initialize #{start_index} #{end_index} 0"
+#end
+
+with_all_segments do |start_index, end_index|
+  run_in_background "ruby worker.rb write #{start_index} #{end_index} 10"
+  run_in_background "ruby worker.rb read #{start_index} #{end_index} 1"
+end
\ No newline at end of file
diff --git a/client-libraries/ruby/benchmarking/worker.rb b/client-libraries/ruby/benchmarking/worker.rb
new file mode 100644 (file)
index 0000000..836d03b
--- /dev/null
@@ -0,0 +1,71 @@
+BENCHMARK_ROOT = File.dirname(__FILE__)
+REDIS_ROOT = File.join(BENCHMARK_ROOT, "..", "lib")
+
+$: << REDIS_ROOT
+require 'redis'
+require 'benchmark'
+
+def show_usage
+  puts <<-EOL
+    Usage: worker.rb [read:write] <start_index> <end_index> <sleep_msec>
+  EOL
+end
+
+def shift_from_argv
+  value = ARGV.shift
+  unless value
+    show_usage
+    exit -1
+  end
+  value
+end
+
+operation = shift_from_argv.to_sym
+start_index = shift_from_argv.to_i
+end_index = shift_from_argv.to_i
+sleep_msec = shift_from_argv.to_i
+sleep_duration = sleep_msec/1000.0
+
+redis = Redis.new
+
+case operation
+  when :initialize
+
+    start_index.upto(end_index) do |i|
+      redis[i] = 0
+    end
+
+  when :clear
+
+    start_index.upto(end_index) do |i|
+      redis.delete(i)
+    end
+
+  when :read, :write
+
+    puts "Starting to #{operation} at segment #{end_index + 1}"
+
+    loop do
+      t1 = Time.now
+      start_index.upto(end_index) do |i|
+        case operation
+          when :read
+            redis.get(i)
+          when :write
+            redis.incr(i)
+          else
+            raise "Unknown operation: #{operation}"
+        end
+        sleep sleep_duration
+      end
+      t2 = Time.now
+
+      requests_processed = end_index - start_index
+      time = t2 - t1
+      puts "#{t2.strftime("%H:%M")} [segment #{end_index + 1}] : Processed #{requests_processed} requests in #{time} seconds - #{(requests_processed/time).round} requests/sec"
+    end
+
+  else
+    raise "Unknown operation: #{operation}"
+end
+
index deaedd159547e67318cfb501ab10fadf48061703..f92b96db527f97223c7b54ff20d63d3da4f504dc 100644 (file)
@@ -9,10 +9,7 @@ class Redis
       @commands = []
     end
     
-    def get_response
-    end
-    
-    def write(data)
+    def execute_command(data)
       @commands << data
       write_and_read if @commands.size >= BUFFER_SIZE
     end
@@ -22,10 +19,10 @@ class Redis
     end
     
     def write_and_read
-      @redis.write @commands.join
+      @redis.execute_command(@commands.join, true)
       @redis.read_socket
       @commands.clear
     end
     
   end
-end
\ No newline at end of file
+end
index b27918bd4b8815e28d2aa53e601c1cb4b29f9518..b10c42bf35497d5bc91f34bf865d799f56bfefea 100644 (file)
@@ -3,14 +3,15 @@ require 'set'
 require File.join(File.dirname(__FILE__),'server')
 require File.join(File.dirname(__FILE__),'pipeline')
 
-
 class RedisError < StandardError
 end
 class RedisRenameError < StandardError
 end
+
 class Redis
   ERR = "-".freeze
   OK = 'OK'.freeze
+  PONG = 'PONG'.freeze
   SINGLE = '+'.freeze
   BULK   = '$'.freeze
   MULTI  = '*'.freeze
@@ -18,22 +19,21 @@ class Redis
   
   attr_reader :server
   
-  
   def initialize(opts={})
     @opts = {:host => 'localhost', :port => '6379', :db => 0}.merge(opts)
     $debug = @opts[:debug]
     @db = @opts[:db]
     @server = Server.new(@opts[:host], @opts[:port], (@opts[:timeout]||10))
   end
-  
+
   def pipelined
     pipeline = Pipeline.new(self)
     yield pipeline
     pipeline.finish
   end
-  
+
   def to_s
-    "#{host}:#{port}"
+    "#{host}:#{port} -> #{@db}"
   end
   
   def port
@@ -44,76 +44,42 @@ class Redis
     @opts[:host]
   end
   
-  def with_socket_management(server, &block)
-    begin
-      socket = server.socket
-      block.call(socket)
-    #Timeout or server down
-    rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED, Timeout::Error => e
-      server.close
-      puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug
-      retry
-    #Server down
-    rescue NoMethodError => e
-      puts "Client (#{server.inspect}) tryin server that is down: #{e.inspect}\n Dying!" if $debug
-      raise Errno::ECONNREFUSED
-      #exit
-    end
+  def quit
+    execute_command("QUIT\r\n", true)
   end
 
-  def monitor
-    with_socket_management(@server) do |socket|
-      trap("INT") { puts "\nGot ^C! Dying!"; exit }
-      write "MONITOR\r\n"
-      puts "Now Monitoring..."
-      socket.read(12)
-      loop do
-        x = socket.gets
-        puts x unless x.nil?
-      end
-    end
+  def ping
+    execute_command("PING\r\n") == PONG
   end
 
-  def quit
-    write "QUIT\r\n"
-  end
-  
   def select_db(index)
     @db = index
-    write "SELECT #{index}\r\n"
-    get_response
+    execute_command("SELECT #{index}\r\n")
   end
   
   def flush_db
-    write "FLUSHDB\r\n"
-    get_response == OK
+    execute_command("FLUSHDB\r\n") == OK
   end    
 
   def flush_all
-    ensure_retry do
-      puts "Warning!\nFlushing *ALL* databases!\n5 Seconds to Hit ^C!"
-      trap('INT') {quit; return false}
-      sleep 5
-      write "FLUSHALL\r\n"
-      get_response == OK
-    end
+    puts "Warning!\nFlushing *ALL* databases!\n5 Seconds to Hit ^C!"
+    trap('INT') {quit; return false}
+    sleep 5
+    execute_command("FLUSHALL\r\n") == OK
   end
 
   def last_save
-    write "LASTSAVE\r\n"
-    get_response.to_i
+    execute_command("LASTSAVE\r\n").to_i
   end
   
   def bgsave
-    write "BGSAVE\r\n"
-    get_response == OK
+    execute_command("BGSAVE\r\n") == OK
   end  
     
   def info
    info = {}
-   write("INFO\r\n")
-   x = get_response
-   x.each do |kv|
+   x = execute_command("INFO\r\n")
+   x.each_line do |kv|
      k,v = kv.split(':', 2)
      k,v = k.chomp, v = v.chomp
      info[k.to_sym] = v
@@ -121,55 +87,16 @@ class Redis
    info
   end
   
-  
-  def bulk_reply
-    begin
-      x = read
-      puts "bulk_reply read value is #{x.inspect}" if $debug
-      return x
-    rescue => e
-      puts "error in bulk_reply #{e}" if $debug
-      nil
-    end
-  end
-  
-  def write(data)
-    with_socket_management(@server) do |socket|
-      puts "writing: #{data}" if $debug
-      socket.write(data)
-    end
-  end
-  
-  def fetch(len)
-    with_socket_management(@server) do |socket|
-      len = [0, len.to_i].max
-      res = socket.read(len + 2)
-      res = res.chomp if res
-      res
-    end
-  end
-  
-  def read(length = read_proto)
-    with_socket_management(@server) do |socket|
-      res = socket.read(length)
-      puts "read is #{res.inspect}" if $debug
-      res
-    end
-  end
-
   def keys(glob)
-    write "KEYS #{glob}\r\n"
-    get_response.split(' ')
+    execute_command("KEYS #{glob}\r\n").split(' ')
   end
 
   def rename!(oldkey, newkey)
-    write "RENAME #{oldkey} #{newkey}\r\n"
-    get_response
+    execute_command("RENAME #{oldkey} #{newkey}\r\n")
   end  
   
   def rename(oldkey, newkey)
-    write "RENAMENX #{oldkey} #{newkey}\r\n"
-    case get_response
+    case execute_command("RENAMENX #{oldkey} #{newkey}\r\n")
     when -1
       raise RedisRenameError, "source key: #{oldkey} does not exist"
     when 0
@@ -182,13 +109,11 @@ class Redis
   end  
   
   def key?(key)
-    write "EXISTS #{key}\r\n"
-    get_response == 1
+    execute_command("EXISTS #{key}\r\n") == 1
   end  
   
   def delete(key)
-    write "DEL #{key}\r\n"
-    get_response == 1
+    execute_command("DEL #{key}\r\n") == 1
   end  
   
   def [](key)
@@ -196,41 +121,35 @@ class Redis
   end
 
   def get(key)
-    write "GET #{key}\r\n"
-    get_response
+    execute_command("GET #{key}\r\n")
   end
   
   def mget(*keys)
-    write "MGET #{keys.join(' ')}\r\n"
-    get_response
+    execute_command("MGET #{keys.join(' ')}\r\n")
   end
 
   def incr(key, increment=nil)
     if increment
-      write "INCRBY #{key} #{increment}\r\n"
+      execute_command("INCRBY #{key} #{increment}\r\n")
     else
-      write "INCR #{key}\r\n"
+      execute_command("INCR #{key}\r\n")
     end    
-    get_response
   end
 
   def decr(key, decrement=nil)
     if decrement
-      write "DECRBY #{key} #{decrement}\r\n"
+      execute_command("DECRBY #{key} #{decrement}\r\n")
     else
-      write "DECR #{key}\r\n"
+      execute_command("DECR #{key}\r\n")
     end    
-    get_response
   end
   
   def randkey
-    write "RANDOMKEY\r\n"
-    get_response
+    execute_command("RANDOMKEY\r\n")
   end
 
   def list_length(key)
-    write "LLEN #{key}\r\n"
-    case i = get_response
+    case i = execute_command("LLEN #{key}\r\n")
     when -2
       raise RedisError, "key: #{key} does not hold a list value"
     else
@@ -239,53 +158,43 @@ class Redis
   end
 
   def type?(key)
-    write "TYPE #{key}\r\n"
-    get_response
+    execute_command("TYPE #{key}\r\n")
   end
   
-  def push_tail(key, string)
-    write "RPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
-    get_response
+  def push_tail(key, val)
+    execute_command("RPUSH #{key} #{value_to_wire(val)}\r\n")
   end      
 
-  def push_head(key, string)
-    write "LPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
-    get_response
+  def push_head(key, val)
+    execute_command("LPUSH #{key} #{value_to_wire(val)}\r\n")
   end
   
   def pop_head(key)
-    write "LPOP #{key}\r\n"
-    get_response
+    execute_command("LPOP #{key}\r\n")
   end
 
   def pop_tail(key)
-    write "RPOP #{key}\r\n"
-    get_response
+    execute_command("RPOP #{key}\r\n")
   end    
 
   def list_set(key, index, val)
-    write "LSET #{key} #{index} #{val.to_s.size}\r\n#{val}\r\n"
-    get_response == OK
+    execute_command("LSET #{key} #{index} #{value_to_wire(val)}\r\n") == OK
   end
 
   def list_range(key, start, ending)
-    write "LRANGE #{key} #{start} #{ending}\r\n"
-    get_response
+    execute_command("LRANGE #{key} #{start} #{ending}\r\n")
   end
 
   def list_trim(key, start, ending)
-    write "LTRIM #{key} #{start} #{ending}\r\n"
-    get_response
+    execute_command("LTRIM #{key} #{start} #{ending}\r\n")
   end
 
   def list_index(key, index)
-    write "LINDEX #{key} #{index}\r\n"
-    get_response
+    execute_command("LINDEX #{key} #{index}\r\n")
   end
 
-  def list_rm(key, count, value)
-    write "LREM #{key} #{count} #{value.to_s.size}\r\n#{value}\r\n"
-    case num = get_response
+  def list_rm(key, count, val)
+    case num = execute_command("LREM #{key} #{count} #{value_to_wire(val)}\r\n")
     when -1
       raise RedisError, "key: #{key} does not exist"
     when -2
@@ -296,8 +205,7 @@ class Redis
   end 
 
   def set_add(key, member)
-    write "SADD #{key} #{member.to_s.size}\r\n#{member}\r\n"
-    case get_response
+    case execute_command("SADD #{key} #{value_to_wire(member)}\r\n")
     when 1
       true
     when 0
@@ -308,8 +216,7 @@ class Redis
   end
 
   def set_delete(key, member)
-    write "SREM #{key} #{member.to_s.size}\r\n#{member}\r\n"
-    case get_response
+    case execute_command("SREM #{key} #{value_to_wire(member)}\r\n")
     when 1
       true
     when 0
@@ -320,8 +227,7 @@ class Redis
   end
 
   def set_count(key)
-    write "SCARD #{key}\r\n"
-    case i = get_response
+    case i = execute_command("SCARD #{key}\r\n")
     when -2
       raise RedisError, "key: #{key} contains a non set value"
     else
@@ -330,8 +236,7 @@ class Redis
   end
 
   def set_member?(key, member)
-    write "SISMEMBER #{key} #{member.to_s.size}\r\n#{member}\r\n"
-    case get_response
+    case execute_command("SISMEMBER #{key} #{value_to_wire(member)}\r\n")
     when 1
       true
     when 0
@@ -342,57 +247,93 @@ class Redis
   end
 
   def set_members(key)
-    write "SMEMBERS #{key}\r\n"
-    Set.new(get_response)
+    Set.new(execute_command("SMEMBERS #{key}\r\n"))
   end
 
   def set_intersect(*keys)
-    write "SINTER #{keys.join(' ')}\r\n"
-    Set.new(get_response)
+    Set.new(execute_command("SINTER #{keys.join(' ')}\r\n"))
   end
 
   def set_inter_store(destkey, *keys)
-    write "SINTERSTORE #{destkey} #{keys.join(' ')}\r\n"
-    get_response
+    execute_command("SINTERSTORE #{destkey} #{keys.join(' ')}\r\n")
   end
   
   def set_union(*keys)
-    write "SUNION #{keys.join(' ')}\r\n"
-    Set.new(get_response)
+    Set.new(execute_command("SUNION #{keys.join(' ')}\r\n"))
   end
 
   def set_union_store(destkey, *keys)
-    write "SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n"
-    get_response
+    execute_command("SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n")
   end
   
   def set_diff(*keys)
-    write "SDIFF #{keys.join(' ')}\r\n"
-    Set.new(get_response)
+    Set.new(execute_command("SDIFF #{keys.join(' ')}\r\n"))
   end
 
   def set_diff_store(destkey, *keys)
-    write "SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n"
-    get_response
+    execute_command("SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n")
   end
 
   def set_move(srckey, destkey, member)
-    write "SMOVE #{srckey} #{destkey} #{member.to_s.size}\r\n#{member}\r\n"
-    get_response == 1
+    execute_command("SMOVE #{srckey} #{destkey} #{value_to_wire(member)}\r\n") == 1
   end
 
   def sort(key, opts={})
     cmd = "SORT #{key}"
     cmd << " BY #{opts[:by]}" if opts[:by]
-    cmd << " GET #{opts[:get]}" if opts[:get]
+    cmd << " GET #{[opts[:get]].flatten * ' GET '}" if opts[:get]
     cmd << " INCR #{opts[:incr]}" if opts[:incr]
     cmd << " DEL #{opts[:del]}" if opts[:del]
     cmd << " DECR #{opts[:decr]}" if opts[:decr]
     cmd << " #{opts[:order]}" if opts[:order]
     cmd << " LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
     cmd << "\r\n"
-    write(cmd)
-    get_response
+    execute_command(cmd)
+  end
+  
+  def []=(key, val)
+    set(key,val)
+  end
+  
+  def set(key, val, expiry=nil)
+    s = execute_command("SET #{key} #{value_to_wire(val)}\r\n") == OK
+    return expire(key, expiry) if s && expiry
+    s
+  end
+
+  def dbsize
+    execute_command("DBSIZE\r\n")
+  end
+
+  def expire(key, expiry=nil)
+    execute_command("EXPIRE #{key} #{expiry}\r\n") == 1
+  end
+
+  def set_unless_exists(key, val)
+    execute_command("SETNX #{key} #{value_to_wire(val)}\r\n") == 1
+  end  
+  
+  def bulk_reply
+    begin
+      x = read
+      puts "bulk_reply read value is #{x.inspect}" if $debug
+      return x
+    rescue => e
+      puts "error in bulk_reply #{e}" if $debug
+      nil
+    end
+  end
+  
+  def write(data)
+    puts "writing: #{data}" if $debug
+    @socket.write(data)
+  end
+  
+  def read(length = 0)
+    length = read_proto unless length > 0
+    res = @socket.read(length)
+    puts "read is #{res.inspect}" if $debug
+    res
   end
       
   def multi_bulk
@@ -418,28 +359,6 @@ class Redis
     r
   end
    
-  def []=(key, val)
-    set(key,val)
-  end
-  
-
-  def set(key, val, expiry=nil)
-    write("SET #{key} #{val.to_s.size}\r\n#{val}\r\n")
-    s = get_response == OK
-    return expire(key, expiry) if s && expiry
-    s
-  end
-  
-  def expire(key, expiry=nil)
-    write("EXPIRE #{key} #{expiry}\r\n")
-    get_response == 1
-  end
-
-  def set_unless_exists(key, val)
-    write "SETNX #{key} #{val.to_s.size}\r\n#{val}\r\n"
-    get_response == 1
-  end  
-  
   def status_code_reply
     begin
       res = read_proto  
@@ -452,13 +371,26 @@ class Redis
        raise RedisError
     end
   end
-  
+  def execute_command(command, ignore_response=false)
+    ss = server.socket
+    unless ss.object_id == @socket.object_id
+      @socket = ss
+      puts "Socket changed, selecting DB" if $debug
+      unless command[0..6] == 'SELECT'
+      #BTM - Ugh- DRY but better than infinite recursion
+        write("SELECT #{@db}\r\n") 
+        get_response
+      end
+    end 
+    write(command)
+    get_response unless ignore_response
+  rescue Errno::ECONNRESET, Errno::EPIPE, NoMethodError, Timeout::Error => e
+    raise RedisError, "Connection error"
+  end
+
   def get_response
-    begin
-      rtype = get_reply
-    rescue => e
-      raise RedisError, e.inspect
-    end
+    rtype = get_reply
     puts "reply_type is #{rtype.inspect}" if $debug
     case rtype
     when SINGLE
@@ -512,13 +444,21 @@ class Redis
   end
   
   def read_proto
-    with_socket_management(@server) do |socket|
-      if res = socket.gets
-        x = res.chomp
-        puts "read_proto is #{x.inspect}\n\n" if $debug
-        x.to_i
-      end
+    res = @socket.readline
+    x = res.chomp
+    puts "read_proto is #{x.inspect}\n\n" if $debug
+    x.to_i
+  end
+
+  private
+  def value_to_wire(value)
+    value_str = value.to_s
+    if value_str.respond_to?(:bytesize)
+      value_size = value_str.bytesize
+    else
+      value_size = value_str.size
     end
+    "#{value_size}\r\n#{value_str}"
   end
   
 end
index c5ac808c077b3d91ae353bfaac257b0ccd5eb095..4fb54937fb1d7cbecb7926e78b9e76de1e95956b 100644 (file)
@@ -24,12 +24,6 @@ end
 
 class Server
 
-  ##
-  # The amount of time to wait before attempting to re-establish a
-  # connection with a server that is marked dead.
-
-  RETRY_DELAY = 30.0
-
   ##
   # The host the redis server is running on.
 
@@ -40,16 +34,6 @@ class Server
 
   attr_reader :port
   
-  ##
-  #
-  
-  attr_reader :replica
-
-  ##
-  # The time of next retry if the connection is dead.
-
-  attr_reader :retry
-
   ##
   # A text status string describing the state of the server.
 
@@ -67,7 +51,6 @@ class Server
     @port   = port.to_i
 
     @sock   = nil
-    @retry  = nil
     @status = 'NOT CONNECTED'
     @timeout = timeout
   end
@@ -83,38 +66,31 @@ class Server
   # Returns the connected socket object on success or nil on failure.
 
   def socket
-    return @sock if @sock and not @sock.closed?
-
-    @sock = nil
-
-    # If the host was dead, don't retry for a while.
-    return if @retry and @retry > Time.now
-
+    return @sock if socket_alive?
+    close
     # Attempt to connect if not already connected.
     begin
       @sock = connect_to(@host, @port, @timeout)
       @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
-      @retry  = nil
       @status = 'CONNECTED'
     rescue Errno::EPIPE, Errno::ECONNREFUSED => e
-      puts "Socket died... socket: #{@sock.inspect}\n" if $debug
-      @sock.close
+      puts "Socket died... : #{e}\n" if $debug
       retry
     rescue SocketError, SystemCallError, IOError => err
       puts "Unable to open socket: #{err.class.name}, #{err.message}" if $debug
-      mark_dead err
     end
     @sock
   end
 
   def connect_to(host, port, timeout=nil)
-    socket = TCPSocket.new(host, port, 0)
+    socket = TCPSocket.new(host, port)
+    socket.set_encoding(Encoding::BINARY) if socket.respond_to?(:set_encoding)
     if timeout
       socket.instance_eval <<-EOR
-        alias :blocking_gets :gets
-        def gets(*args)
+        alias :blocking_readline :readline
+        def readline(*args)
           RedisTimer.timeout(#{timeout}) do
-            self.blocking_gets(*args)
+            self.blocking_readline(*args)
           end
         end
         alias :blocking_read :read
@@ -134,27 +110,22 @@ class Server
     socket
   end
 
-  ##
   # Close the connection to the redis server targeted by this
-  # object.  The server is not considered dead.
+  # object. 
 
   def close
-    @sock.close if @sock && !@sock.closed?
+    @sock.close if !@sock.nil? && !@sock.closed?
     @sock   = nil
-    @retry  = nil
     @status = "NOT CONNECTED"
   end
 
-  ##
-  # Mark the server as dead and close its socket.
-  def mark_dead(error)
-    @sock.close if @sock && !@sock.closed?
-    @sock   = nil
-    @retry  = Time.now #+ RETRY_DELAY
-
-    reason = "#{error.class.name}: #{error.message}"
-    @status = sprintf "%s:%s DEAD (%s), will retry at %s", @host, @port, reason, @retry
-    puts @status
-  end
-
+  private
+    def socket_alive?
+      #BTM - TODO - FileStat is borked under JRuby
+      unless defined?(JRUBY_VERSION)
+        !@sock.nil? &&  !@sock.closed? && @sock.stat.readable?
+      else 
+        !@sock.nil? &&  !@sock.closed?
+      end
+    end
 end
index 5e284b301e80c301e298fdffe02d8456e1be6788..e8d8b6eb9c08f9dff561cbe946eda98da51852da 100644 (file)
@@ -2,7 +2,7 @@
 
 Gem::Specification.new do |s|
   s.name = %q{redis}
-  s.version = "0.0.3.4"
+  s.version = "0.0.3.5"
 
   s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
   s.authors = ["Ezra Zygmuntowicz", "Taylor Weibley", "Matthew Clark"]
index 71a63259bd07ace6cf872b4e4dc7673bd9e91efc..de302f3457dba363033129e56e10155285e9026e 100644 (file)
@@ -13,8 +13,8 @@ end
 
 describe "redis" do
   before(:all) do
-    @r = Redis.new
-    @r.select_db(15) # use database 15 for testing so we dont accidentally step on you real data
+    # use database 15 for testing so we dont accidentally step on you real data
+    @r = Redis.new :db => 15
   end
 
   before(:each) do
@@ -29,6 +29,9 @@ describe "redis" do
     @r.quit
   end  
 
+  it 'should be able to PING' do
+    @r.ping.should == true
+  end
 
   it "should be able to GET a key" do
     @r['foo'].should == 'bar'
@@ -102,7 +105,16 @@ describe "redis" do
     lambda {@r.rename 'foo', 'bar'}.should raise_error(RedisRenameError)
     @r['bar'].should == 'ohai'
   end
-  # 
+  #
+  it "should be able to get DBSIZE of the database" do
+    @r.delete 'foo'
+    dbsize_without_foo = @r.dbsize
+    @r['foo'] = 0
+    dbsize_with_foo = @r.dbsize
+
+    dbsize_with_foo.should == dbsize_without_foo + 1
+  end
+  #
   it "should be able to EXPIRE a key" do
     @r['foo'] = 'bar'
     @r.expire('foo', 1)
@@ -287,6 +299,7 @@ describe "redis" do
     @r.set_inter_store('newone', 'set', 'set2').should == 'OK'
     @r.set_members('newone').should == Set.new(['key2'])
     @r.delete('set')
+    @r.delete('set2')
   end
   #
   it "should be able to do set union" do
@@ -296,6 +309,7 @@ describe "redis" do
     @r.set_add "set2", 'key3'
     @r.set_union('set', 'set2').should == Set.new(['key1','key2','key3'])
     @r.delete('set')
+    @r.delete('set2')
   end
   # 
   it "should be able to do set union and store the results in a key" do
@@ -306,28 +320,29 @@ describe "redis" do
     @r.set_union_store('newone', 'set', 'set2').should == 'OK'
     @r.set_members('newone').should == Set.new(['key1','key2','key3'])
     @r.delete('set')
+    @r.delete('set2')
   end
-  
-  # these don't seem to be implemented in redis head?
-  # it "should be able to do set difference" do
-  #   @r.set_add "set", 'key1'
-  #   @r.set_add "set", 'key2'
-  #   @r.set_add "set2", 'key2'
-  #   @r.set_add "set2", 'key3'
-  #   @r.set_diff('set', 'set2').should == Set.new(['key1','key3'])
-  #   @r.delete('set')
-  # end
-  # 
-  it "should be able to do set difference and store the results in a key" do
-  #   @r.set_add "set", 'key1'
-  #   @r.set_add "set", 'key2'
-  #   @r.set_add "set2", 'key2'
-  #   @r.set_add "set2", 'key3'
-  #   count = @r.set_diff_store('newone', 'set', 'set2')
-  #   count.should == 3
-  #   @r.set_members('newone').should == Set.new(['key1','key3'])
-  #   @r.delete('set')
-  # end
+  # 
+  it "should be able to do set difference" do
+     @r.set_add "set", 'a'
+     @r.set_add "set", 'b'
+     @r.set_add "set2", 'b'
+     @r.set_add "set2", 'c'
+     @r.set_diff('set', 'set2').should == Set.new(['a'])
+     @r.delete('set')
+     @r.delete('set2')
+   end
+  # 
+  it "should be able to do set difference and store the results in a key" do
+     @r.set_add "set", 'a'
+     @r.set_add "set", 'b'
+     @r.set_add "set2", 'b'
+     @r.set_add "set2", 'c'
+     @r.set_diff_store('newone', 'set', 'set2')
+     @r.set_members('newone').should == Set.new(['a'])
+     @r.delete('set')
+     @r.delete('set2')
+   end
   # 
   it "should be able move elements from one set to another" do
     @r.set_add 'set1', 'a'
@@ -350,6 +365,23 @@ describe "redis" do
     @r.sort('dogs', :get => 'dog_*', :limit => [0,1]).should == ['louie']
     @r.sort('dogs', :get => 'dog_*', :limit => [0,1], :order => 'desc alpha').should == ['taj']
   end
+
+  it "should be able to handle array of :get using SORT" do
+    @r['dog:1:name'] = 'louie'
+    @r['dog:1:breed'] = 'mutt'
+    @r.push_tail 'dogs', 1
+    @r['dog:2:name'] = 'lucy'
+    @r['dog:2:breed'] = 'poodle'
+    @r.push_tail 'dogs', 2
+    @r['dog:3:name'] = 'max'
+    @r['dog:3:breed'] = 'hound'
+    @r.push_tail 'dogs', 3
+    @r['dog:4:name'] = 'taj'
+    @r['dog:4:breed'] = 'terrier'
+    @r.push_tail 'dogs', 4
+    @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1]).should == ['louie', 'mutt']
+    @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1], :order => 'desc alpha').should == ['taj', 'terrier']
+  end
   # 
   it "should provide info" do
     [:last_save_time, :redis_version, :total_connections_received, :connected_clients, :total_commands_processed, :connected_slaves, :uptime_in_seconds, :used_memory, :uptime_in_days, :changes_since_last_save].each do |x|
@@ -407,4 +439,7 @@ describe "redis" do
     @r.pop_head('list').should == '42'
     @r.delete('list')
   end
+  
+  it "should select db on connection"
+  it "should re-select db on reconnection"
 end
diff --git a/client-libraries/ruby/spec/server_spec.rb b/client-libraries/ruby/spec/server_spec.rb
new file mode 100644 (file)
index 0000000..cb2beb5
--- /dev/null
@@ -0,0 +1,22 @@
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe "Server" do
+  before(:each) do
+    @server = Server.new 'localhost', '6379'
+  end
+
+  it "should checkout active connections" do
+    threads = []
+    10.times do
+      threads << Thread.new do
+        lambda {
+          socket = @server.socket
+          socket.close
+          socket.write("INFO\r\n")
+          socket.read(1)
+        }.should_not raise_error(Exception)
+      end
+    end
+  end
+
+end
index 657d248db53ff72260f03aea22b006c4d1237009..580af215884b745b76d91737d307d8777cf456cc 100644 (file)
@@ -64,7 +64,7 @@ namespace :redis do
     RedisRunner.attach
   end
   
-  desc 'Install the lastest redis from svn'
+  desc 'Install the lastest verison of Redis from Github (requires git, duh)'
   task :install => [:about, :download, :make] do
     %w(redis-benchmark redis-cli redis-server).each do |bin|
       sh "sudo cp /tmp/redis/#{bin} /usr/bin/"
diff --git a/redis.c b/redis.c
index 5ced9a29b605ad0819c3e7464731603097dce388..98371ed408ac650815a0c5c23d9997a2fa008950 100644 (file)
--- a/redis.c
+++ b/redis.c
@@ -1528,7 +1528,7 @@ static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
         char *err = "-ERR max number of clients reached\r\n";
 
         /* That's a best effort error message, don't check write errors */
-        (void)write(c->fd,err,strlen(err));
+        (void) write(c->fd,err,strlen(err));
         freeClient(c);
         return;
     }