redis 2.0.13 → 2.1.0.beta
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/README.markdown +9 -0
- data/lib/redis.rb +6 -5
- data/lib/redis/client.rb +17 -120
- data/lib/redis/connection.rb +134 -0
- metadata +11 -8
- data/lib/redis/url.rb +0 -69
data/README.markdown
CHANGED
@@ -111,6 +111,15 @@ You can use `MULTI/EXEC` to run arbitrary commands in an atomic fashion:
|
|
111
111
|
redis.incr "baz"
|
112
112
|
end
|
113
113
|
|
114
|
+
## Multithreaded Operation
|
115
|
+
|
116
|
+
To use redis safely in a multithreaded environment, be sure to initialize the client with :thread_safe=>true
|
117
|
+
|
118
|
+
Redis.new(:thread_safe=>true)
|
119
|
+
|
120
|
+
See the tests and benchmarks for examples.
|
121
|
+
|
122
|
+
|
114
123
|
## More info
|
115
124
|
|
116
125
|
Check the [Redis Command Reference](http://code.google.com/p/redis/wiki/CommandReference) or check the tests to find out how to use this client.
|
data/lib/redis.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'socket'
|
2
2
|
|
3
3
|
class Redis
|
4
|
-
VERSION = "2.0.
|
4
|
+
VERSION = "2.1.0.beta"
|
5
5
|
|
6
6
|
class ProtocolError < RuntimeError
|
7
7
|
def initialize(reply_type)
|
@@ -669,7 +669,8 @@ private
|
|
669
669
|
|
670
670
|
end
|
671
671
|
|
672
|
-
require
|
673
|
-
require
|
674
|
-
require
|
675
|
-
require
|
672
|
+
require "redis/connection" unless defined?(Redis::Connection)
|
673
|
+
require "redis/client"
|
674
|
+
require "redis/pipeline"
|
675
|
+
require "redis/subscribe"
|
676
|
+
require "redis/compat"
|
data/lib/redis/client.rb
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
class Redis
|
2
2
|
class Client
|
3
|
-
MINUS = "-".freeze
|
4
|
-
PLUS = "+".freeze
|
5
|
-
COLON = ":".freeze
|
6
|
-
DOLLAR = "$".freeze
|
7
|
-
ASTERISK = "*".freeze
|
8
|
-
|
9
3
|
attr_accessor :db, :host, :port, :password, :logger
|
10
4
|
attr :timeout
|
5
|
+
attr :connection
|
11
6
|
|
12
7
|
def initialize(options = {})
|
13
8
|
@host = options[:host] || "127.0.0.1"
|
@@ -16,14 +11,14 @@ class Redis
|
|
16
11
|
@timeout = (options[:timeout] || 5).to_i
|
17
12
|
@password = options[:password]
|
18
13
|
@logger = options[:logger]
|
19
|
-
@
|
14
|
+
@connection = Connection.new
|
20
15
|
end
|
21
16
|
|
22
17
|
def connect
|
23
18
|
connect_to(@host, @port)
|
24
19
|
call(:auth, @password) if @password
|
25
20
|
call(:select, @db) if @db != 0
|
26
|
-
|
21
|
+
self
|
27
22
|
end
|
28
23
|
|
29
24
|
def id
|
@@ -61,25 +56,21 @@ class Redis
|
|
61
56
|
def process(*commands)
|
62
57
|
logging(commands) do
|
63
58
|
ensure_connected do
|
64
|
-
|
59
|
+
commands.each do |command|
|
60
|
+
connection.write(command)
|
61
|
+
end
|
62
|
+
|
65
63
|
yield if block_given?
|
66
64
|
end
|
67
65
|
end
|
68
66
|
end
|
69
67
|
|
70
68
|
def connected?
|
71
|
-
|
69
|
+
connection.connected?
|
72
70
|
end
|
73
71
|
|
74
72
|
def disconnect
|
75
|
-
|
76
|
-
|
77
|
-
begin
|
78
|
-
@sock.close
|
79
|
-
rescue
|
80
|
-
ensure
|
81
|
-
@sock = nil
|
82
|
-
end
|
73
|
+
connection.disconnect if connection.connected?
|
83
74
|
end
|
84
75
|
|
85
76
|
def reconnect
|
@@ -88,23 +79,20 @@ class Redis
|
|
88
79
|
end
|
89
80
|
|
90
81
|
def read
|
91
|
-
# We read the first byte using read() mainly because gets() is
|
92
|
-
# immune to raw socket timeouts.
|
93
82
|
begin
|
94
|
-
|
95
|
-
rescue Errno::EAGAIN
|
83
|
+
connection.read
|
96
84
|
|
85
|
+
rescue Errno::EAGAIN
|
97
86
|
# We want to make sure it reconnects on the next command after the
|
98
87
|
# timeout. Otherwise the server may reply in the meantime leaving
|
99
88
|
# the protocol in a desync status.
|
100
|
-
disconnect
|
89
|
+
connection.disconnect
|
101
90
|
|
102
91
|
raise Errno::EAGAIN, "Timeout reading from the socket"
|
103
|
-
end
|
104
|
-
|
105
|
-
raise Errno::ECONNRESET, "Connection lost" unless reply_type
|
106
92
|
|
107
|
-
|
93
|
+
rescue Errno::ECONNRESET
|
94
|
+
raise Errno::ECONNRESET, "Connection lost"
|
95
|
+
end
|
108
96
|
end
|
109
97
|
|
110
98
|
def without_socket_timeout
|
@@ -120,83 +108,12 @@ class Redis
|
|
120
108
|
|
121
109
|
protected
|
122
110
|
|
123
|
-
def build_command(name, *args)
|
124
|
-
command = []
|
125
|
-
command << "*#{args.size + 1}"
|
126
|
-
command << "$#{string_size name}"
|
127
|
-
command << name
|
128
|
-
|
129
|
-
args.each do |arg|
|
130
|
-
arg = arg.to_s
|
131
|
-
command << "$#{string_size arg}"
|
132
|
-
command << arg
|
133
|
-
end
|
134
|
-
|
135
|
-
command
|
136
|
-
end
|
137
|
-
|
138
111
|
def deprecated(old, new = nil, trace = caller[0])
|
139
112
|
message = "The method #{old} is deprecated and will be removed in 2.0"
|
140
113
|
message << " - use #{new} instead" if new
|
141
114
|
Redis.deprecate(message, trace)
|
142
115
|
end
|
143
116
|
|
144
|
-
COMMAND_DELIMITER = "\r\n"
|
145
|
-
|
146
|
-
def join_commands(commands)
|
147
|
-
commands.map do |command|
|
148
|
-
build_command(*command).join(COMMAND_DELIMITER) + COMMAND_DELIMITER
|
149
|
-
end.join(nil)
|
150
|
-
end
|
151
|
-
|
152
|
-
if "".respond_to?(:bytesize)
|
153
|
-
def string_size(string)
|
154
|
-
string.to_s.bytesize
|
155
|
-
end
|
156
|
-
else
|
157
|
-
def string_size(string)
|
158
|
-
string.to_s.size
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def format_reply(reply_type, line)
|
163
|
-
case reply_type
|
164
|
-
when MINUS then format_error_reply(line)
|
165
|
-
when PLUS then format_status_reply(line)
|
166
|
-
when COLON then format_integer_reply(line)
|
167
|
-
when DOLLAR then format_bulk_reply(line)
|
168
|
-
when ASTERISK then format_multi_bulk_reply(line)
|
169
|
-
else raise ProtocolError.new(reply_type)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
def format_error_reply(line)
|
174
|
-
raise "-" + line.strip
|
175
|
-
end
|
176
|
-
|
177
|
-
def format_status_reply(line)
|
178
|
-
line.strip
|
179
|
-
end
|
180
|
-
|
181
|
-
def format_integer_reply(line)
|
182
|
-
line.to_i
|
183
|
-
end
|
184
|
-
|
185
|
-
def format_bulk_reply(line)
|
186
|
-
bulklen = line.to_i
|
187
|
-
return if bulklen == -1
|
188
|
-
reply = encode(@sock.read(bulklen))
|
189
|
-
@sock.read(2) # Discard CRLF.
|
190
|
-
reply
|
191
|
-
end
|
192
|
-
|
193
|
-
def format_multi_bulk_reply(line)
|
194
|
-
n = line.to_i
|
195
|
-
return if n == -1
|
196
|
-
|
197
|
-
Array.new(n) { read }
|
198
|
-
end
|
199
|
-
|
200
117
|
def logging(commands)
|
201
118
|
return yield unless @logger && @logger.debug?
|
202
119
|
|
@@ -214,11 +131,9 @@ class Redis
|
|
214
131
|
|
215
132
|
def connect_to(host, port)
|
216
133
|
with_timeout(@timeout) do
|
217
|
-
|
134
|
+
connection.connect(host, port)
|
218
135
|
end
|
219
136
|
|
220
|
-
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
221
|
-
|
222
137
|
# If the timeout is set we set the low level socket options in order
|
223
138
|
# to make sure a blocking read will return after the specified number
|
224
139
|
# of seconds. This hack is from memcached ruby client.
|
@@ -229,15 +144,7 @@ class Redis
|
|
229
144
|
end
|
230
145
|
|
231
146
|
def timeout=(timeout)
|
232
|
-
|
233
|
-
usecs = Integer((timeout - secs) * 1_000_000)
|
234
|
-
optval = [secs, usecs].pack("l_2")
|
235
|
-
|
236
|
-
begin
|
237
|
-
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
238
|
-
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
239
|
-
rescue Errno::ENOPROTOOPT
|
240
|
-
end
|
147
|
+
connection.timeout = Integer(timeout * 1_000_000)
|
241
148
|
end
|
242
149
|
|
243
150
|
def ensure_connected
|
@@ -290,15 +197,5 @@ class Redis
|
|
290
197
|
Timeout.timeout(seconds, &block)
|
291
198
|
end
|
292
199
|
end
|
293
|
-
|
294
|
-
if defined?(Encoding::default_external)
|
295
|
-
def encode(string)
|
296
|
-
string.force_encoding(Encoding::default_external)
|
297
|
-
end
|
298
|
-
else
|
299
|
-
def encode(string)
|
300
|
-
string
|
301
|
-
end
|
302
|
-
end
|
303
200
|
end
|
304
201
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
class Redis
|
2
|
+
class Connection
|
3
|
+
MINUS = "-".freeze
|
4
|
+
PLUS = "+".freeze
|
5
|
+
COLON = ":".freeze
|
6
|
+
DOLLAR = "$".freeze
|
7
|
+
ASTERISK = "*".freeze
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@sock = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def connected?
|
14
|
+
!! @sock
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect(host, port)
|
18
|
+
@sock = TCPSocket.new(host, port)
|
19
|
+
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def disconnect
|
23
|
+
@sock.close
|
24
|
+
rescue
|
25
|
+
ensure
|
26
|
+
@sock = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def timeout=(usecs)
|
30
|
+
secs = Integer(usecs / 1_000_000)
|
31
|
+
usecs = Integer(usecs - (secs * 1_000_000)) # 0 - 999_999
|
32
|
+
|
33
|
+
optval = [secs, usecs].pack("l_2")
|
34
|
+
|
35
|
+
begin
|
36
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
37
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
38
|
+
rescue Errno::ENOPROTOOPT
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
COMMAND_DELIMITER = "\r\n"
|
43
|
+
|
44
|
+
def write(command)
|
45
|
+
@sock.write(build_command(*command).join(COMMAND_DELIMITER))
|
46
|
+
@sock.write(COMMAND_DELIMITER)
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_command(name, *args)
|
50
|
+
command = []
|
51
|
+
command << "*#{args.size + 1}"
|
52
|
+
command << "$#{string_size name}"
|
53
|
+
command << name
|
54
|
+
|
55
|
+
args.each do |arg|
|
56
|
+
arg = arg.to_s
|
57
|
+
command << "$#{string_size arg}"
|
58
|
+
command << arg
|
59
|
+
end
|
60
|
+
|
61
|
+
command
|
62
|
+
end
|
63
|
+
|
64
|
+
def read
|
65
|
+
# We read the first byte using read() mainly because gets() is
|
66
|
+
# immune to raw socket timeouts.
|
67
|
+
reply_type = @sock.read(1)
|
68
|
+
|
69
|
+
raise Errno::ECONNRESET unless reply_type
|
70
|
+
|
71
|
+
format_reply(reply_type, @sock.gets)
|
72
|
+
end
|
73
|
+
|
74
|
+
def format_reply(reply_type, line)
|
75
|
+
case reply_type
|
76
|
+
when MINUS then format_error_reply(line)
|
77
|
+
when PLUS then format_status_reply(line)
|
78
|
+
when COLON then format_integer_reply(line)
|
79
|
+
when DOLLAR then format_bulk_reply(line)
|
80
|
+
when ASTERISK then format_multi_bulk_reply(line)
|
81
|
+
else raise ProtocolError.new(reply_type)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def format_error_reply(line)
|
86
|
+
raise "-" + line.strip
|
87
|
+
end
|
88
|
+
|
89
|
+
def format_status_reply(line)
|
90
|
+
line.strip
|
91
|
+
end
|
92
|
+
|
93
|
+
def format_integer_reply(line)
|
94
|
+
line.to_i
|
95
|
+
end
|
96
|
+
|
97
|
+
def format_bulk_reply(line)
|
98
|
+
bulklen = line.to_i
|
99
|
+
return if bulklen == -1
|
100
|
+
reply = encode(@sock.read(bulklen))
|
101
|
+
@sock.read(2) # Discard CRLF.
|
102
|
+
reply
|
103
|
+
end
|
104
|
+
|
105
|
+
def format_multi_bulk_reply(line)
|
106
|
+
n = line.to_i
|
107
|
+
return if n == -1
|
108
|
+
|
109
|
+
Array.new(n) { read }
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
if "".respond_to?(:bytesize)
|
115
|
+
def string_size(string)
|
116
|
+
string.to_s.bytesize
|
117
|
+
end
|
118
|
+
else
|
119
|
+
def string_size(string)
|
120
|
+
string.to_s.size
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
if defined?(Encoding::default_external)
|
125
|
+
def encode(string)
|
126
|
+
string.force_encoding(Encoding::default_external)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
def encode(string)
|
130
|
+
string
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
4
|
+
prerelease: true
|
5
5
|
segments:
|
6
6
|
- 2
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
-
|
9
|
-
version: 2.0.
|
9
|
+
- beta
|
10
|
+
version: 2.1.0.beta
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Ezra Zygmuntowicz
|
@@ -21,7 +22,7 @@ autorequire: redis
|
|
21
22
|
bindir: bin
|
22
23
|
cert_chain: []
|
23
24
|
|
24
|
-
date: 2010-11-
|
25
|
+
date: 2010-11-04 00:00:00 -03:00
|
25
26
|
default_executable:
|
26
27
|
dependencies: []
|
27
28
|
|
@@ -39,11 +40,11 @@ files:
|
|
39
40
|
- Rakefile
|
40
41
|
- lib/redis/client.rb
|
41
42
|
- lib/redis/compat.rb
|
43
|
+
- lib/redis/connection.rb
|
42
44
|
- lib/redis/distributed.rb
|
43
45
|
- lib/redis/hash_ring.rb
|
44
46
|
- lib/redis/pipeline.rb
|
45
47
|
- lib/redis/subscribe.rb
|
46
|
-
- lib/redis/url.rb
|
47
48
|
- lib/redis.rb
|
48
49
|
has_rdoc: true
|
49
50
|
homepage: http://github.com/ezmobius/redis-rb
|
@@ -65,11 +66,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
66
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
67
|
none: false
|
67
68
|
requirements:
|
68
|
-
- - "
|
69
|
+
- - ">"
|
69
70
|
- !ruby/object:Gem::Version
|
70
71
|
segments:
|
71
|
-
-
|
72
|
-
|
72
|
+
- 1
|
73
|
+
- 3
|
74
|
+
- 1
|
75
|
+
version: 1.3.1
|
73
76
|
requirements: []
|
74
77
|
|
75
78
|
rubyforge_project:
|
data/lib/redis/url.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
require "uri/generic"
|
2
|
-
|
3
|
-
module URI
|
4
|
-
class Redis < Generic
|
5
|
-
DEFAULT_PORT = 6379
|
6
|
-
|
7
|
-
COMPONENT = [:scheme, :password, :host, :port, :db].freeze
|
8
|
-
|
9
|
-
def db
|
10
|
-
path[1..-1].to_i
|
11
|
-
end
|
12
|
-
|
13
|
-
alias password user
|
14
|
-
|
15
|
-
protected
|
16
|
-
def check_path(value)
|
17
|
-
if super(value)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
@@schemes["REDIS"] = Redis
|
23
|
-
end
|
24
|
-
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
25
|
-
require "redis/url"
|
26
|
-
|
27
|
-
class RedisURLTest < Test::Unit::TestCase
|
28
|
-
test "default values" do
|
29
|
-
uri = URI.parse("redis://localhost")
|
30
|
-
|
31
|
-
assert_equal "localhost", uri.host
|
32
|
-
assert_equal 6379, uri.port
|
33
|
-
assert_equal 0, uri.db
|
34
|
-
assert_equal nil, uri.password
|
35
|
-
end
|
36
|
-
|
37
|
-
test "password" do
|
38
|
-
uri = URI.parse("redis://secret@localhost")
|
39
|
-
|
40
|
-
assert_equal "localhost", uri.host
|
41
|
-
assert_equal 6379, uri.port
|
42
|
-
assert_equal 0, uri.db
|
43
|
-
assert_equal "secret", uri.password
|
44
|
-
end
|
45
|
-
|
46
|
-
test "db number" do
|
47
|
-
uri = URI.parse("redis://secret@localhost/15")
|
48
|
-
|
49
|
-
assert_equal "localhost", uri.host
|
50
|
-
assert_equal 6379, uri.port
|
51
|
-
assert_equal 15, uri.db
|
52
|
-
assert_equal "secret", uri.password
|
53
|
-
end
|
54
|
-
|
55
|
-
test "port" do
|
56
|
-
uri = URI.parse("redis://localhost:6380")
|
57
|
-
|
58
|
-
assert_equal "localhost", uri.host
|
59
|
-
assert_equal 6380, uri.port
|
60
|
-
assert_equal 0, uri.db
|
61
|
-
assert_equal nil, uri.password
|
62
|
-
end
|
63
|
-
|
64
|
-
test "to_s" do
|
65
|
-
uri = URI.parse("redis://secret@localhost:6380/15")
|
66
|
-
|
67
|
-
assert_equal "redis://secret@localhost:6380/15", uri.to_s
|
68
|
-
end
|
69
|
-
end
|