mirror of
https://github.com/fluencelabs/redis
synced 2025-03-18 16:40:50 +00:00
client libraries synched in git
This commit is contained in:
parent
e083d75262
commit
d7fc9edb18
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
),
|
||||
|
||||
|
3
client-libraries/ruby/.gitignore
vendored
3
client-libraries/ruby/.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
nohup.out
|
||||
redis/*
|
||||
rdsrv
|
||||
pkg/*
|
||||
pkg/*
|
||||
.idea
|
||||
|
@ -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
|
||||
|
||||
|
@ -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}/**/*")
|
||||
|
24
client-libraries/ruby/benchmarking/suite.rb
Normal file
24
client-libraries/ruby/benchmarking/suite.rb
Normal file
@ -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
|
71
client-libraries/ruby/benchmarking/worker.rb
Normal file
71
client-libraries/ruby/benchmarking/worker.rb
Normal file
@ -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
|
||||
|
@ -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
|
||||
end
|
||||
|
@ -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
|
||||
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
|
||||
end
|
||||
|
||||
def quit
|
||||
write "QUIT\r\n"
|
||||
execute_command("QUIT\r\n", true)
|
||||
end
|
||||
|
||||
|
||||
def ping
|
||||
execute_command("PING\r\n") == PONG
|
||||
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,6 +87,231 @@ class Redis
|
||||
info
|
||||
end
|
||||
|
||||
def keys(glob)
|
||||
execute_command("KEYS #{glob}\r\n").split(' ')
|
||||
end
|
||||
|
||||
def rename!(oldkey, newkey)
|
||||
execute_command("RENAME #{oldkey} #{newkey}\r\n")
|
||||
end
|
||||
|
||||
def rename(oldkey, newkey)
|
||||
case execute_command("RENAMENX #{oldkey} #{newkey}\r\n")
|
||||
when -1
|
||||
raise RedisRenameError, "source key: #{oldkey} does not exist"
|
||||
when 0
|
||||
raise RedisRenameError, "target key: #{oldkey} already exists"
|
||||
when -3
|
||||
raise RedisRenameError, "source and destination keys are the same"
|
||||
when 1
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
execute_command("EXISTS #{key}\r\n") == 1
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
execute_command("DEL #{key}\r\n") == 1
|
||||
end
|
||||
|
||||
def [](key)
|
||||
get(key)
|
||||
end
|
||||
|
||||
def get(key)
|
||||
execute_command("GET #{key}\r\n")
|
||||
end
|
||||
|
||||
def mget(*keys)
|
||||
execute_command("MGET #{keys.join(' ')}\r\n")
|
||||
end
|
||||
|
||||
def incr(key, increment=nil)
|
||||
if increment
|
||||
execute_command("INCRBY #{key} #{increment}\r\n")
|
||||
else
|
||||
execute_command("INCR #{key}\r\n")
|
||||
end
|
||||
end
|
||||
|
||||
def decr(key, decrement=nil)
|
||||
if decrement
|
||||
execute_command("DECRBY #{key} #{decrement}\r\n")
|
||||
else
|
||||
execute_command("DECR #{key}\r\n")
|
||||
end
|
||||
end
|
||||
|
||||
def randkey
|
||||
execute_command("RANDOMKEY\r\n")
|
||||
end
|
||||
|
||||
def list_length(key)
|
||||
case i = execute_command("LLEN #{key}\r\n")
|
||||
when -2
|
||||
raise RedisError, "key: #{key} does not hold a list value"
|
||||
else
|
||||
i
|
||||
end
|
||||
end
|
||||
|
||||
def type?(key)
|
||||
execute_command("TYPE #{key}\r\n")
|
||||
end
|
||||
|
||||
def push_tail(key, val)
|
||||
execute_command("RPUSH #{key} #{value_to_wire(val)}\r\n")
|
||||
end
|
||||
|
||||
def push_head(key, val)
|
||||
execute_command("LPUSH #{key} #{value_to_wire(val)}\r\n")
|
||||
end
|
||||
|
||||
def pop_head(key)
|
||||
execute_command("LPOP #{key}\r\n")
|
||||
end
|
||||
|
||||
def pop_tail(key)
|
||||
execute_command("RPOP #{key}\r\n")
|
||||
end
|
||||
|
||||
def list_set(key, index, val)
|
||||
execute_command("LSET #{key} #{index} #{value_to_wire(val)}\r\n") == OK
|
||||
end
|
||||
|
||||
def list_range(key, start, ending)
|
||||
execute_command("LRANGE #{key} #{start} #{ending}\r\n")
|
||||
end
|
||||
|
||||
def list_trim(key, start, ending)
|
||||
execute_command("LTRIM #{key} #{start} #{ending}\r\n")
|
||||
end
|
||||
|
||||
def list_index(key, index)
|
||||
execute_command("LINDEX #{key} #{index}\r\n")
|
||||
end
|
||||
|
||||
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
|
||||
raise RedisError, "key: #{key} does not hold a list value"
|
||||
else
|
||||
num
|
||||
end
|
||||
end
|
||||
|
||||
def set_add(key, member)
|
||||
case execute_command("SADD #{key} #{value_to_wire(member)}\r\n")
|
||||
when 1
|
||||
true
|
||||
when 0
|
||||
false
|
||||
when -2
|
||||
raise RedisError, "key: #{key} contains a non set value"
|
||||
end
|
||||
end
|
||||
|
||||
def set_delete(key, member)
|
||||
case execute_command("SREM #{key} #{value_to_wire(member)}\r\n")
|
||||
when 1
|
||||
true
|
||||
when 0
|
||||
false
|
||||
when -2
|
||||
raise RedisError, "key: #{key} contains a non set value"
|
||||
end
|
||||
end
|
||||
|
||||
def set_count(key)
|
||||
case i = execute_command("SCARD #{key}\r\n")
|
||||
when -2
|
||||
raise RedisError, "key: #{key} contains a non set value"
|
||||
else
|
||||
i
|
||||
end
|
||||
end
|
||||
|
||||
def set_member?(key, member)
|
||||
case execute_command("SISMEMBER #{key} #{value_to_wire(member)}\r\n")
|
||||
when 1
|
||||
true
|
||||
when 0
|
||||
false
|
||||
when -2
|
||||
raise RedisError, "key: #{key} contains a non set value"
|
||||
end
|
||||
end
|
||||
|
||||
def set_members(key)
|
||||
Set.new(execute_command("SMEMBERS #{key}\r\n"))
|
||||
end
|
||||
|
||||
def set_intersect(*keys)
|
||||
Set.new(execute_command("SINTER #{keys.join(' ')}\r\n"))
|
||||
end
|
||||
|
||||
def set_inter_store(destkey, *keys)
|
||||
execute_command("SINTERSTORE #{destkey} #{keys.join(' ')}\r\n")
|
||||
end
|
||||
|
||||
def set_union(*keys)
|
||||
Set.new(execute_command("SUNION #{keys.join(' ')}\r\n"))
|
||||
end
|
||||
|
||||
def set_union_store(destkey, *keys)
|
||||
execute_command("SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n")
|
||||
end
|
||||
|
||||
def set_diff(*keys)
|
||||
Set.new(execute_command("SDIFF #{keys.join(' ')}\r\n"))
|
||||
end
|
||||
|
||||
def set_diff_store(destkey, *keys)
|
||||
execute_command("SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n")
|
||||
end
|
||||
|
||||
def set_move(srckey, destkey, member)
|
||||
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]].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"
|
||||
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
|
||||
@ -134,265 +325,15 @@ class Redis
|
||||
end
|
||||
|
||||
def write(data)
|
||||
with_socket_management(@server) do |socket|
|
||||
puts "writing: #{data}" if $debug
|
||||
socket.write(data)
|
||||
end
|
||||
puts "writing: #{data}" if $debug
|
||||
@socket.write(data)
|
||||
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(' ')
|
||||
end
|
||||
|
||||
def rename!(oldkey, newkey)
|
||||
write "RENAME #{oldkey} #{newkey}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def rename(oldkey, newkey)
|
||||
write "RENAMENX #{oldkey} #{newkey}\r\n"
|
||||
case get_response
|
||||
when -1
|
||||
raise RedisRenameError, "source key: #{oldkey} does not exist"
|
||||
when 0
|
||||
raise RedisRenameError, "target key: #{oldkey} already exists"
|
||||
when -3
|
||||
raise RedisRenameError, "source and destination keys are the same"
|
||||
when 1
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
write "EXISTS #{key}\r\n"
|
||||
get_response == 1
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
write "DEL #{key}\r\n"
|
||||
get_response == 1
|
||||
end
|
||||
|
||||
def [](key)
|
||||
get(key)
|
||||
end
|
||||
|
||||
def get(key)
|
||||
write "GET #{key}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def mget(*keys)
|
||||
write "MGET #{keys.join(' ')}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def incr(key, increment=nil)
|
||||
if increment
|
||||
write "INCRBY #{key} #{increment}\r\n"
|
||||
else
|
||||
write "INCR #{key}\r\n"
|
||||
end
|
||||
get_response
|
||||
end
|
||||
|
||||
def decr(key, decrement=nil)
|
||||
if decrement
|
||||
write "DECRBY #{key} #{decrement}\r\n"
|
||||
else
|
||||
write "DECR #{key}\r\n"
|
||||
end
|
||||
get_response
|
||||
end
|
||||
|
||||
def randkey
|
||||
write "RANDOMKEY\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def list_length(key)
|
||||
write "LLEN #{key}\r\n"
|
||||
case i = get_response
|
||||
when -2
|
||||
raise RedisError, "key: #{key} does not hold a list value"
|
||||
else
|
||||
i
|
||||
end
|
||||
end
|
||||
|
||||
def type?(key)
|
||||
write "TYPE #{key}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def push_tail(key, string)
|
||||
write "RPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def push_head(key, string)
|
||||
write "LPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def pop_head(key)
|
||||
write "LPOP #{key}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def pop_tail(key)
|
||||
write "RPOP #{key}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def list_set(key, index, val)
|
||||
write "LSET #{key} #{index} #{val.to_s.size}\r\n#{val}\r\n"
|
||||
get_response == OK
|
||||
end
|
||||
|
||||
def list_range(key, start, ending)
|
||||
write "LRANGE #{key} #{start} #{ending}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def list_trim(key, start, ending)
|
||||
write "LTRIM #{key} #{start} #{ending}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def list_index(key, index)
|
||||
write "LINDEX #{key} #{index}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def list_rm(key, count, value)
|
||||
write "LREM #{key} #{count} #{value.to_s.size}\r\n#{value}\r\n"
|
||||
case num = get_response
|
||||
when -1
|
||||
raise RedisError, "key: #{key} does not exist"
|
||||
when -2
|
||||
raise RedisError, "key: #{key} does not hold a list value"
|
||||
else
|
||||
num
|
||||
end
|
||||
end
|
||||
|
||||
def set_add(key, member)
|
||||
write "SADD #{key} #{member.to_s.size}\r\n#{member}\r\n"
|
||||
case get_response
|
||||
when 1
|
||||
true
|
||||
when 0
|
||||
false
|
||||
when -2
|
||||
raise RedisError, "key: #{key} contains a non set value"
|
||||
end
|
||||
end
|
||||
|
||||
def set_delete(key, member)
|
||||
write "SREM #{key} #{member.to_s.size}\r\n#{member}\r\n"
|
||||
case get_response
|
||||
when 1
|
||||
true
|
||||
when 0
|
||||
false
|
||||
when -2
|
||||
raise RedisError, "key: #{key} contains a non set value"
|
||||
end
|
||||
end
|
||||
|
||||
def set_count(key)
|
||||
write "SCARD #{key}\r\n"
|
||||
case i = get_response
|
||||
when -2
|
||||
raise RedisError, "key: #{key} contains a non set value"
|
||||
else
|
||||
i
|
||||
end
|
||||
end
|
||||
|
||||
def set_member?(key, member)
|
||||
write "SISMEMBER #{key} #{member.to_s.size}\r\n#{member}\r\n"
|
||||
case get_response
|
||||
when 1
|
||||
true
|
||||
when 0
|
||||
false
|
||||
when -2
|
||||
raise RedisError, "key: #{key} contains a non set value"
|
||||
end
|
||||
end
|
||||
|
||||
def set_members(key)
|
||||
write "SMEMBERS #{key}\r\n"
|
||||
Set.new(get_response)
|
||||
end
|
||||
|
||||
def set_intersect(*keys)
|
||||
write "SINTER #{keys.join(' ')}\r\n"
|
||||
Set.new(get_response)
|
||||
end
|
||||
|
||||
def set_inter_store(destkey, *keys)
|
||||
write "SINTERSTORE #{destkey} #{keys.join(' ')}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def set_union(*keys)
|
||||
write "SUNION #{keys.join(' ')}\r\n"
|
||||
Set.new(get_response)
|
||||
end
|
||||
|
||||
def set_union_store(destkey, *keys)
|
||||
write "SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def set_diff(*keys)
|
||||
write "SDIFF #{keys.join(' ')}\r\n"
|
||||
Set.new(get_response)
|
||||
end
|
||||
|
||||
def set_diff_store(destkey, *keys)
|
||||
write "SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n"
|
||||
get_response
|
||||
end
|
||||
|
||||
def set_move(srckey, destkey, member)
|
||||
write "SMOVE #{srckey} #{destkey} #{member.to_s.size}\r\n#{member}\r\n"
|
||||
get_response == 1
|
||||
end
|
||||
|
||||
def sort(key, opts={})
|
||||
cmd = "SORT #{key}"
|
||||
cmd << " BY #{opts[:by]}" if opts[:by]
|
||||
cmd << " GET #{opts[: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
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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"]
|
||||
|
@ -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
|
||||
|
22
client-libraries/ruby/spec/server_spec.rb
Normal file
22
client-libraries/ruby/spec/server_spec.rb
Normal file
@ -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
|
@ -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/"
|
||||
|
2
redis.c
2
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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user