ezmobius-redis-rb 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/server.rb ADDED
@@ -0,0 +1,128 @@
1
+ ##
2
+ # This class represents a redis server instance.
3
+
4
+ class Server
5
+
6
+ ##
7
+ # The amount of time to wait before attempting to re-establish a
8
+ # connection with a server that is marked dead.
9
+
10
+ RETRY_DELAY = 30.0
11
+
12
+ ##
13
+ # The host the redis server is running on.
14
+
15
+ attr_reader :host
16
+
17
+ ##
18
+ # The port the redis server is listening on.
19
+
20
+ attr_reader :port
21
+
22
+ ##
23
+ #
24
+
25
+ attr_reader :replica
26
+
27
+ ##
28
+ # The time of next retry if the connection is dead.
29
+
30
+ attr_reader :retry
31
+
32
+ ##
33
+ # A text status string describing the state of the server.
34
+
35
+ attr_reader :status
36
+
37
+ ##
38
+ # Create a new Redis::Server object for the redis instance
39
+ # listening on the given host and port.
40
+
41
+ def initialize(host, port = DEFAULT_PORT)
42
+ raise ArgumentError, "No host specified" if host.nil? or host.empty?
43
+ raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero?
44
+
45
+ @host = host
46
+ @port = port.to_i
47
+
48
+ @sock = nil
49
+ @retry = nil
50
+ @status = 'NOT CONNECTED'
51
+ @timeout = 1
52
+ end
53
+
54
+ ##
55
+ # Return a string representation of the server object.
56
+ def inspect
57
+ "<Redis::Server: %s:%d (%s)>" % [@host, @port, @status]
58
+ end
59
+
60
+ ##
61
+ # Try to connect to the redis server targeted by this object.
62
+ # Returns the connected socket object on success or nil on failure.
63
+
64
+ def socket
65
+ return @sock if @sock and not @sock.closed?
66
+
67
+ @sock = nil
68
+
69
+ # If the host was dead, don't retry for a while.
70
+ return if @retry and @retry > Time.now
71
+
72
+ # Attempt to connect if not already connected.
73
+ begin
74
+ @sock = connect_to(@host, @port, @timeout)
75
+ @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
76
+ @retry = nil
77
+ @status = 'CONNECTED'
78
+ rescue Errno::EPIPE, Errno::ECONNREFUSED => e
79
+ puts "Socket died... socket: #{@sock.inspect}\n" if $debug
80
+ @sock.close
81
+ retry
82
+ rescue SocketError, SystemCallError, IOError => err
83
+ puts "Unable to open socket: #{err.class.name}, #{err.message}" if $debug
84
+ mark_dead err
85
+ end
86
+
87
+ return @sock
88
+ end
89
+
90
+ def connect_to(host, port, timeout=nil)
91
+ addrs = Socket.getaddrinfo(host, nil)
92
+ addr = addrs.detect { |ad| ad[0] == 'AF_INET' }
93
+ sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
94
+ if timeout
95
+ secs = Integer(timeout)
96
+ usecs = Integer((timeout - secs) * 1_000_000)
97
+ optval = [secs, usecs].pack("l_2")
98
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
99
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
100
+ end
101
+ sock.connect(Socket.pack_sockaddr_in(port, addr[3]))
102
+ sock
103
+ end
104
+
105
+ ##
106
+ # Close the connection to the redis server targeted by this
107
+ # object. The server is not considered dead.
108
+
109
+ def close
110
+ @sock.close if @sock && !@sock.closed?
111
+ @sock = nil
112
+ @retry = nil
113
+ @status = "NOT CONNECTED"
114
+ end
115
+
116
+ ##
117
+ # Mark the server as dead and close its socket.
118
+ def mark_dead(error)
119
+ @sock.close if @sock && !@sock.closed?
120
+ @sock = nil
121
+ @retry = Time.now #+ RETRY_DELAY
122
+
123
+ reason = "#{error.class.name}: #{error.message}"
124
+ @status = sprintf "%s:%s DEAD (%s), will retry at %s", @host, @port, reason, @retry
125
+ puts @status
126
+ end
127
+
128
+ end
@@ -0,0 +1,322 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class Foo
4
+ attr_accessor :bar
5
+ def initialize(bar)
6
+ @bar = bar
7
+ end
8
+
9
+ def ==(other)
10
+ @bar == other.bar
11
+ end
12
+ end
13
+
14
+ describe "redis" do
15
+ before(:all) do
16
+ @r = Redis.new
17
+ @r.select_db(15) # use database 15 for testing so we dont accidentally step on you real data
18
+ end
19
+
20
+ before(:each) do
21
+ @r['foo'] = 'bar'
22
+ end
23
+
24
+ after(:each) do
25
+ @r.flush_db
26
+ end
27
+
28
+ after(:all) do
29
+ @r.quit
30
+ end
31
+
32
+
33
+ it "should be able to GET a key" do
34
+ @r['foo'].should == 'bar'
35
+ end
36
+
37
+ it "should be able to SET a key" do
38
+ @r['foo'] = 'nik'
39
+ @r['foo'].should == 'nik'
40
+ end
41
+
42
+ it "should be able to SETNX(set_unless_exists)" do
43
+ @r['foo'] = 'nik'
44
+ @r['foo'].should == 'nik'
45
+ @r.set_unless_exists 'foo', 'bar'
46
+ @r['foo'].should == 'nik'
47
+ end
48
+ #
49
+ it "should be able to INCR(increment) a key" do
50
+ @r.delete('counter')
51
+ @r.incr('counter').should == 1
52
+ @r.incr('counter').should == 2
53
+ @r.incr('counter').should == 3
54
+ end
55
+ #
56
+ it "should be able to DECR(decrement) a key" do
57
+ @r.delete('counter')
58
+ @r.incr('counter').should == 1
59
+ @r.incr('counter').should == 2
60
+ @r.incr('counter').should == 3
61
+ @r.decr('counter').should == 2
62
+ @r.decr('counter', 2).should == 0
63
+ end
64
+ #
65
+ it "should be able to RANDKEY(return a random key)" do
66
+ @r.randkey.should_not be_nil
67
+ end
68
+ #
69
+ it "should be able to RENAME a key" do
70
+ @r.delete 'foo'
71
+ @r.delete 'bar'
72
+ @r['foo'] = 'hi'
73
+ @r.rename! 'foo', 'bar'
74
+ @r['bar'].should == 'hi'
75
+ end
76
+ #
77
+ it "should be able to RENAMENX(rename unless the new key already exists) a key" do
78
+ @r.delete 'foo'
79
+ @r.delete 'bar'
80
+ @r['foo'] = 'hi'
81
+ @r['bar'] = 'ohai'
82
+ lambda {@r.rename 'foo', 'bar'}.should raise_error(RedisRenameError)
83
+ @r['bar'].should == 'ohai'
84
+ end
85
+ #
86
+ it "should be able to EXISTS(check if key exists)" do
87
+ @r['foo'] = 'nik'
88
+ @r.key?('foo').should be_true
89
+ @r.delete 'foo'
90
+ @r.key?('foo').should be_false
91
+ end
92
+ #
93
+ it "should be able to KEYS(glob for keys)" do
94
+ @r.keys("f*").each do |key|
95
+ @r.delete key
96
+ end
97
+ @r['f'] = 'nik'
98
+ @r['fo'] = 'nak'
99
+ @r['foo'] = 'qux'
100
+ @r.keys("f*").sort.should == ['f','fo', 'foo'].sort
101
+ end
102
+ #
103
+ it "should be able to check the TYPE of a key" do
104
+ @r['foo'] = 'nik'
105
+ @r.type?('foo').should == "string"
106
+ @r.delete 'foo'
107
+ @r.type?('foo').should == "none"
108
+ end
109
+ #
110
+ it "should be able to push to the head of a list" do
111
+ @r.push_head "list", 'hello'
112
+ @r.push_head "list", 42
113
+ @r.type?('list').should == "list"
114
+ @r.list_length('list').should == 2
115
+ @r.pop_head('list').should == '42'
116
+ @r.delete('list')
117
+ end
118
+ #
119
+ it "should be able to push to the tail of a list" do
120
+ @r.push_tail "list", 'hello'
121
+ @r.type?('list').should == "list"
122
+ @r.list_length('list').should == 1
123
+ @r.delete('list')
124
+ end
125
+ #
126
+ it "should be able to pop the tail of a list" do
127
+ @r.push_tail "list", 'hello'
128
+ @r.push_tail "list", 'goodbye'
129
+ @r.type?('list').should == "list"
130
+ @r.list_length('list').should == 2
131
+ @r.pop_tail('list').should == 'goodbye'
132
+ @r.delete('list')
133
+ end
134
+ #
135
+ it "should be able to pop the head of a list" do
136
+ @r.push_tail "list", 'hello'
137
+ @r.push_tail "list", 'goodbye'
138
+ @r.type?('list').should == "list"
139
+ @r.list_length('list').should == 2
140
+ @r.pop_head('list').should == 'hello'
141
+ @r.delete('list')
142
+ end
143
+ #
144
+ it "should be able to get the length of a list" do
145
+ @r.push_tail "list", 'hello'
146
+ @r.push_tail "list", 'goodbye'
147
+ @r.type?('list').should == "list"
148
+ @r.list_length('list').should == 2
149
+ @r.delete('list')
150
+ end
151
+ #
152
+ it "should be able to get a range of values from a list" do
153
+ @r.push_tail "list", 'hello'
154
+ @r.push_tail "list", 'goodbye'
155
+ @r.push_tail "list", '1'
156
+ @r.push_tail "list", '2'
157
+ @r.push_tail "list", '3'
158
+ @r.type?('list').should == "list"
159
+ @r.list_length('list').should == 5
160
+ @r.list_range('list', 2, -1).should == ['1', '2', '3']
161
+ @r.delete('list')
162
+ end
163
+ #
164
+ it "should be able to trim a list" do
165
+ @r.push_tail "list", 'hello'
166
+ @r.push_tail "list", 'goodbye'
167
+ @r.push_tail "list", '1'
168
+ @r.push_tail "list", '2'
169
+ @r.push_tail "list", '3'
170
+ @r.type?('list').should == "list"
171
+ @r.list_length('list').should == 5
172
+ @r.list_trim 'list', 0, 1
173
+ @r.list_length('list').should == 2
174
+ @r.list_range('list', 0, -1).should == ['hello', 'goodbye']
175
+ @r.delete('list')
176
+ end
177
+ #
178
+ it "should be able to get a value by indexing into a list" do
179
+ @r.push_tail "list", 'hello'
180
+ @r.push_tail "list", 'goodbye'
181
+ @r.type?('list').should == "list"
182
+ @r.list_length('list').should == 2
183
+ @r.list_index('list', 1).should == 'goodbye'
184
+ @r.delete('list')
185
+ end
186
+ #
187
+ it "should be able to set a value by indexing into a list" do
188
+ @r.push_tail "list", 'hello'
189
+ @r.push_tail "list", 'hello'
190
+ @r.type?('list').should == "list"
191
+ @r.list_length('list').should == 2
192
+ @r.list_set('list', 1, 'goodbye').should be_true
193
+ @r.list_index('list', 1).should == 'goodbye'
194
+ @r.delete('list')
195
+ end
196
+ #
197
+ it "should be able to remove values from a list LREM" do
198
+ @r.push_tail "list", 'hello'
199
+ @r.push_tail "list", 'goodbye'
200
+ @r.type?('list').should == "list"
201
+ @r.list_length('list').should == 2
202
+ @r.list_rm('list', 1, 'hello').should == 1
203
+ @r.list_range('list', 0, -1).should == ['goodbye']
204
+ @r.delete('list')
205
+ end
206
+ #
207
+ it "should be able add members to a set" do
208
+ @r.set_add "set", 'key1'
209
+ @r.set_add "set", 'key2'
210
+ @r.type?('set').should == "set"
211
+ @r.set_count('set').should == 2
212
+ @r.set_members('set').sort.should == ['key1', 'key2'].sort
213
+ @r.delete('set')
214
+ end
215
+ #
216
+ it "should be able delete members to a set" do
217
+ @r.set_add "set", 'key1'
218
+ @r.set_add "set", 'key2'
219
+ @r.type?('set').should == "set"
220
+ @r.set_count('set').should == 2
221
+ @r.set_members('set').should == Set.new(['key1', 'key2'])
222
+ @r.set_delete('set', 'key1')
223
+ @r.set_count('set').should == 1
224
+ @r.set_members('set').should == Set.new(['key2'])
225
+ @r.delete('set')
226
+ end
227
+ #
228
+ it "should be able count the members of a set" do
229
+ @r.set_add "set", 'key1'
230
+ @r.set_add "set", 'key2'
231
+ @r.type?('set').should == "set"
232
+ @r.set_count('set').should == 2
233
+ @r.delete('set')
234
+ end
235
+ #
236
+ it "should be able test for set membership" do
237
+ @r.set_add "set", 'key1'
238
+ @r.set_add "set", 'key2'
239
+ @r.type?('set').should == "set"
240
+ @r.set_count('set').should == 2
241
+ @r.set_member?('set', 'key1').should be_true
242
+ @r.set_member?('set', 'key2').should be_true
243
+ @r.set_member?('set', 'notthere').should be_false
244
+ @r.delete('set')
245
+ end
246
+ #
247
+ it "should be able to do set intersection" do
248
+ @r.set_add "set", 'key1'
249
+ @r.set_add "set", 'key2'
250
+ @r.set_add "set2", 'key2'
251
+ @r.set_intersect('set', 'set2').should == Set.new(['key2'])
252
+ @r.delete('set')
253
+ end
254
+ #
255
+ it "should be able to do set intersection and store the results in a key" do
256
+ @r.set_add "set", 'key1'
257
+ @r.set_add "set", 'key2'
258
+ @r.set_add "set2", 'key2'
259
+ @r.set_inter_store('newone', 'set', 'set2')
260
+ @r.set_members('newone').should == Set.new(['key2'])
261
+ @r.delete('set')
262
+ end
263
+ #
264
+ it "should be able to do crazy SORT queries" do
265
+ @r['dog_1'] = 'louie'
266
+ @r.push_tail 'dogs', 1
267
+ @r['dog_2'] = 'lucy'
268
+ @r.push_tail 'dogs', 2
269
+ @r['dog_3'] = 'max'
270
+ @r.push_tail 'dogs', 3
271
+ @r['dog_4'] = 'taj'
272
+ @r.push_tail 'dogs', 4
273
+ @r.sort('dogs', :get => 'dog_*', :limit => [0,1]).should == ['louie']
274
+ @r.sort('dogs', :get => 'dog_*', :limit => [0,1], :order => 'desc alpha').should == ['taj']
275
+ end
276
+ #
277
+ it "should provide info" do
278
+ [: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|
279
+ @r.info.keys.should include(x)
280
+ end
281
+ end
282
+ #
283
+ it "should be able to flush the database" do
284
+ @r['key1'] = 'keyone'
285
+ @r['key2'] = 'keytwo'
286
+ @r.keys('*').sort.should == ['foo', 'key1', 'key2'] #foo from before
287
+ @r.flush_db
288
+ @r.keys('*').should == []
289
+ end
290
+ #
291
+ it "should be able to provide the last save time" do
292
+ savetime = @r.last_save
293
+ Time.at(savetime).class.should == Time
294
+ Time.at(savetime).should <= Time.now
295
+ end
296
+
297
+ it "should be able to MGET keys" do
298
+ @r['foo'] = 1000
299
+ @r['bar'] = 2000
300
+ @r.mget('foo', 'bar').should == ['1000', '2000']
301
+ @r.mget('foo', 'bar', 'baz').should == ['1000', '2000', nil]
302
+ end
303
+
304
+ it "should bgsave" do
305
+ lambda {@r.bgsave}.should_not raise_error(RedisError)
306
+ end
307
+
308
+ it "should handle multiple servers" do
309
+ require 'dist_redis'
310
+ @r = DistRedis.new('localhost:6379', '127.0.0.1:6379')
311
+ @r.select_db(15) # use database 15 for testing so we dont accidentally step on you real data
312
+
313
+ 100.times do |idx|
314
+ @r[idx] = "foo#{idx}"
315
+ end
316
+
317
+ 100.times do |idx|
318
+ @r[idx].should == "foo#{idx}"
319
+ end
320
+ end
321
+
322
+ end