ezmobius-redis-rb 0.0.3

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/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