em-hiredis 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +19 -0
- data/README.md +80 -27
- data/examples/getting_started.rb +14 -0
- data/examples/pubsub_basics.rb +24 -0
- data/examples/pubsub_more.rb +51 -0
- data/examples/pubsub_raw.rb +25 -0
- data/lib/em-hiredis.rb +23 -3
- data/lib/em-hiredis/base_client.rb +200 -0
- data/lib/em-hiredis/client.rb +40 -155
- data/lib/em-hiredis/connection.rb +12 -4
- data/lib/em-hiredis/lock.rb +106 -0
- data/lib/em-hiredis/persistent_lock.rb +77 -0
- data/lib/em-hiredis/pubsub_client.rb +187 -0
- data/lib/em-hiredis/version.rb +1 -1
- data/spec/base_client_spec.rb +116 -0
- data/spec/connection_spec.rb +1 -1
- data/spec/live_redis_protocol_spec.rb +1 -1
- data/spec/pubsub_spec.rb +314 -0
- data/spec/redis_commands_spec.rb +29 -8
- data/spec/spec_helper.rb +3 -0
- data/spec/support/connection_helper.rb +5 -3
- data/spec/url_param_spec.rb +3 -3
- metadata +45 -22
data/lib/em-hiredis/version.rb
CHANGED
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EM::Hiredis::BaseClient do
|
4
|
+
it "should be able to connect to redis (required for all tests!)" do
|
5
|
+
em {
|
6
|
+
redis = EM::Hiredis.connect
|
7
|
+
redis.callback {
|
8
|
+
done
|
9
|
+
}
|
10
|
+
redis.errback {
|
11
|
+
puts "CHECK THAT THE REDIS SERVER IS RUNNING ON PORT 6379"
|
12
|
+
fail
|
13
|
+
}
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should emit an event on reconnect failure, with the retry count" do
|
18
|
+
# Assumes there is no redis server on 9999
|
19
|
+
connect(1, "redis://localhost:9999/") do |redis|
|
20
|
+
expected = 1
|
21
|
+
redis.on(:reconnect_failed) { |count|
|
22
|
+
count.should == expected
|
23
|
+
expected += 1
|
24
|
+
done if expected == 3
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should emit disconnected when the connection closes" do
|
30
|
+
connect do |redis|
|
31
|
+
redis.on(:disconnected) {
|
32
|
+
done
|
33
|
+
}
|
34
|
+
redis.close_connection
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should fail the client deferrable after 4 unsuccessful attempts" do
|
39
|
+
connect(1, "redis://localhost:9999/") do |redis|
|
40
|
+
events = []
|
41
|
+
redis.on(:reconnect_failed) { |count|
|
42
|
+
events << count
|
43
|
+
}
|
44
|
+
redis.errback { |error|
|
45
|
+
error.class.should == EM::Hiredis::Error
|
46
|
+
error.message.should == 'Could not connect after 4 attempts'
|
47
|
+
events.should == [1,2,3,4]
|
48
|
+
done
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should fail commands immediately when in failed state" do
|
54
|
+
connect(1, "redis://localhost:9999/") do |redis|
|
55
|
+
redis.fail
|
56
|
+
redis.get('foo').errback { |error|
|
57
|
+
error.class.should == EM::Hiredis::Error
|
58
|
+
error.message.should == 'Redis connection in failed state'
|
59
|
+
done
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should fail queued commands when entering failed state" do
|
65
|
+
connect(1, "redis://localhost:9999/") do |redis|
|
66
|
+
redis.get('foo').errback { |error|
|
67
|
+
error.class.should == EM::Hiredis::Error
|
68
|
+
error.message.should == 'Redis connection in failed state'
|
69
|
+
done
|
70
|
+
}
|
71
|
+
redis.fail
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should allow reconfiguring the client at runtime" do
|
76
|
+
connect(1, "redis://localhost:9999/") do |redis|
|
77
|
+
redis.on(:reconnect_failed) {
|
78
|
+
redis.configure("redis://localhost:6379/9")
|
79
|
+
redis.info {
|
80
|
+
done
|
81
|
+
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should allow connection to be reconnected" do
|
87
|
+
connect do |redis|
|
88
|
+
redis.on(:reconnected) {
|
89
|
+
done
|
90
|
+
}
|
91
|
+
# Wait for first connection to complete
|
92
|
+
redis.callback {
|
93
|
+
redis.reconnect_connection
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should wrap error responses returned by redis" do
|
99
|
+
connect do |redis|
|
100
|
+
redis.sadd('foo', 'bar') {
|
101
|
+
df = redis.get('foo')
|
102
|
+
df.callback {
|
103
|
+
fail "Should have received error response from redis"
|
104
|
+
}
|
105
|
+
df.errback { |e|
|
106
|
+
e.class.should == EM::Hiredis::RedisError
|
107
|
+
e.should be_kind_of(EM::Hiredis::Error)
|
108
|
+
e.message.should == 'Error reply from redis (wrapped in redis_error)'
|
109
|
+
# This is the wrapped error from redis:
|
110
|
+
e.redis_error.message.should == 'ERR Operation against a key holding the wrong kind of value'
|
111
|
+
done
|
112
|
+
}
|
113
|
+
}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/spec/connection_spec.rb
CHANGED
@@ -494,7 +494,7 @@ end
|
|
494
494
|
|
495
495
|
describe EventMachine::Hiredis, "when reconnecting" do
|
496
496
|
it "select previously selected dataset" do
|
497
|
-
connect do |redis|
|
497
|
+
connect(3) do |redis|
|
498
498
|
#simulate disconnect
|
499
499
|
redis.set('foo', 'a') { redis.close_connection_after_writing }
|
500
500
|
|
data/spec/pubsub_spec.rb
ADDED
@@ -0,0 +1,314 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EventMachine::Hiredis::PubsubClient, '(un)subscribe' do
|
4
|
+
describe "subscribing" do
|
5
|
+
it "should return deferrable which succeeds with subscribe call result" do
|
6
|
+
connect do |redis|
|
7
|
+
df = redis.pubsub.subscribe("channel") { }
|
8
|
+
df.should be_kind_of(EventMachine::DefaultDeferrable)
|
9
|
+
df.callback { |subscription_count|
|
10
|
+
# Subscribe response from redis - indicates that subscription has
|
11
|
+
# succeeded and that the current connection has a single
|
12
|
+
# subscription
|
13
|
+
subscription_count.should == 1
|
14
|
+
done
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should run the passed block when message received" do
|
20
|
+
connect do |redis|
|
21
|
+
redis.pubsub.subscribe("channel") { |message|
|
22
|
+
message.should == 'hello'
|
23
|
+
done
|
24
|
+
}.callback {
|
25
|
+
redis.publish('channel', 'hello')
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should run the passed proc when message received on channel" do
|
31
|
+
connect do |redis|
|
32
|
+
proc = Proc.new { |message|
|
33
|
+
message.should == 'hello'
|
34
|
+
done
|
35
|
+
}
|
36
|
+
redis.pubsub.subscribe("channel", proc).callback {
|
37
|
+
redis.publish('channel', 'hello')
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "unsubscribing" do
|
44
|
+
it "should allow unsubscribing a single callback without unsubscribing from redis" do
|
45
|
+
connect do |redis|
|
46
|
+
proc1 = Proc.new { |message| fail }
|
47
|
+
proc2 = Proc.new { |message|
|
48
|
+
message.should == 'hello'
|
49
|
+
done
|
50
|
+
}
|
51
|
+
redis.pubsub.subscribe("channel", proc1)
|
52
|
+
redis.pubsub.subscribe("channel", proc2).callback {
|
53
|
+
redis.pubsub.unsubscribe_proc("channel", proc1)
|
54
|
+
redis.publish("channel", "hello")
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should unsubscribe from redis on last proc unsubscription" do
|
60
|
+
connect do |redis|
|
61
|
+
proc = Proc.new { |message| }
|
62
|
+
redis.pubsub.subscribe("channel", proc).callback { |subs_count|
|
63
|
+
subs_count.should == 1
|
64
|
+
redis.pubsub.unsubscribe_proc("channel", proc).callback {
|
65
|
+
# Slightly awkward way to check that unsubscribe happened:
|
66
|
+
redis.pubsub.subscribe('channel2').callback { |count|
|
67
|
+
# If count is 1 this implies that channel unsubscribed
|
68
|
+
count.should == 1
|
69
|
+
done
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should allow unsubscribing from redis channel, including all callbacks, and return deferrable for redis unsubscribe" do
|
77
|
+
connect do |redis|
|
78
|
+
# Raw pubsub event
|
79
|
+
redis.pubsub.on('message') { |channel, message| fail }
|
80
|
+
# Block subscription
|
81
|
+
redis.pubsub.subscribe("channel") { |m| fail } # block
|
82
|
+
# Proc example
|
83
|
+
df = redis.pubsub.subscribe("channel", Proc.new { |m| fail })
|
84
|
+
|
85
|
+
df.callback {
|
86
|
+
redis.pubsub.unsubscribe("channel").callback { |remaining_subs|
|
87
|
+
remaining_subs.should == 0
|
88
|
+
redis.publish("channel", "hello") {
|
89
|
+
done
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should expose raw pubsub events from redis" do
|
98
|
+
channel = "channel"
|
99
|
+
callback_count = 0
|
100
|
+
connect do |redis|
|
101
|
+
redis.pubsub.on(:subscribe) { |channel, subscription_count|
|
102
|
+
# 2. Get subscribe callback
|
103
|
+
callback_count += 1
|
104
|
+
channel.should == channel
|
105
|
+
subscription_count.should == 1
|
106
|
+
|
107
|
+
# 3. Publish on channel
|
108
|
+
redis.publish(channel, 'foo')
|
109
|
+
}
|
110
|
+
|
111
|
+
redis.pubsub.on(:message) { |channel, message|
|
112
|
+
# 4. Get message callback
|
113
|
+
callback_count += 1
|
114
|
+
channel.should == channel
|
115
|
+
message.should == 'foo'
|
116
|
+
|
117
|
+
callback_count.should == 2
|
118
|
+
done
|
119
|
+
}
|
120
|
+
|
121
|
+
# 1. Subscribe to channel
|
122
|
+
redis.pubsub.subscribe(channel)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should resubscribe to all channels on reconnect" do
|
127
|
+
callback_count = 0
|
128
|
+
connect do |redis|
|
129
|
+
# 1. Subscribe to channels
|
130
|
+
redis.pubsub.subscribe('channel1') {
|
131
|
+
callback_count += 1
|
132
|
+
}
|
133
|
+
redis.pubsub.subscribe('channel2') {
|
134
|
+
callback_count += 1
|
135
|
+
EM.next_tick {
|
136
|
+
# 4. Success if both messages have been received
|
137
|
+
callback_count.should == 2
|
138
|
+
done
|
139
|
+
}
|
140
|
+
}.callback { |subscription_count|
|
141
|
+
subscription_count.should == 2
|
142
|
+
# 2. Subscriptions complete. Now force disconnect
|
143
|
+
redis.pubsub.instance_variable_get(:@connection).close_connection
|
144
|
+
|
145
|
+
EM.add_timer(0.1) {
|
146
|
+
# 3. After giving time to reconnect publish to both channels
|
147
|
+
redis.publish('channel1', 'foo')
|
148
|
+
redis.publish('channel2', 'bar')
|
149
|
+
}
|
150
|
+
|
151
|
+
}
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe EventMachine::Hiredis::PubsubClient, 'p(un)subscribe' do
|
158
|
+
describe "psubscribing" do
|
159
|
+
it "should return deferrable which succeeds with psubscribe call result" do
|
160
|
+
connect do |redis|
|
161
|
+
df = redis.pubsub.psubscribe("channel") { }
|
162
|
+
df.should be_kind_of(EventMachine::DefaultDeferrable)
|
163
|
+
df.callback { |subscription_count|
|
164
|
+
# Subscribe response from redis - indicates that subscription has
|
165
|
+
# succeeded and that the current connection has a single
|
166
|
+
# subscription
|
167
|
+
subscription_count.should == 1
|
168
|
+
done
|
169
|
+
}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should run the passed block when message received" do
|
174
|
+
connect do |redis|
|
175
|
+
redis.pubsub.psubscribe("channel:*") { |channel, message|
|
176
|
+
channel.should == 'channel:foo'
|
177
|
+
message.should == 'hello'
|
178
|
+
done
|
179
|
+
}.callback {
|
180
|
+
redis.publish('channel:foo', 'hello')
|
181
|
+
}
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should run the passed proc when message received on channel" do
|
186
|
+
connect do |redis|
|
187
|
+
proc = Proc.new { |channel, message|
|
188
|
+
channel.should == 'channel:foo'
|
189
|
+
message.should == 'hello'
|
190
|
+
done
|
191
|
+
}
|
192
|
+
redis.pubsub.psubscribe("channel:*", proc).callback {
|
193
|
+
redis.publish('channel:foo', 'hello')
|
194
|
+
}
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe "punsubscribing" do
|
200
|
+
it "should allow punsubscribing a single callback without punsubscribing from redis" do
|
201
|
+
connect do |redis|
|
202
|
+
proc1 = Proc.new { |channel, message| fail }
|
203
|
+
proc2 = Proc.new { |channel, message|
|
204
|
+
channel.should == 'channel:foo'
|
205
|
+
message.should == 'hello'
|
206
|
+
done
|
207
|
+
}
|
208
|
+
redis.pubsub.psubscribe("channel:*", proc1)
|
209
|
+
redis.pubsub.psubscribe("channel:*", proc2).callback {
|
210
|
+
redis.pubsub.punsubscribe_proc("channel:*", proc1)
|
211
|
+
redis.publish("channel:foo", "hello")
|
212
|
+
}
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should punsubscribe from redis on last proc punsubscription" do
|
217
|
+
connect do |redis|
|
218
|
+
proc = Proc.new { |message| }
|
219
|
+
redis.pubsub.psubscribe("channel:*", proc).callback { |subs_count|
|
220
|
+
subs_count.should == 1
|
221
|
+
redis.pubsub.punsubscribe_proc("channel:*", proc).callback {
|
222
|
+
# Slightly awkward way to check that unsubscribe happened:
|
223
|
+
redis.pubsub.psubscribe('channel2').callback { |count|
|
224
|
+
# If count is 1 this implies that channel unsubscribed
|
225
|
+
count.should == 1
|
226
|
+
done
|
227
|
+
}
|
228
|
+
}
|
229
|
+
}
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should allow punsubscribing from redis channel, including all callbacks, and return deferrable for redis punsubscribe" do
|
234
|
+
connect do |redis|
|
235
|
+
# Raw pubsub event
|
236
|
+
redis.pubsub.on('pmessage') { |pattern, channel, message| fail }
|
237
|
+
# Block subscription
|
238
|
+
redis.pubsub.psubscribe("channel") { |c, m| fail } # block
|
239
|
+
# Proc example
|
240
|
+
df = redis.pubsub.psubscribe("channel", Proc.new { |c, m| fail })
|
241
|
+
|
242
|
+
df.callback {
|
243
|
+
redis.pubsub.punsubscribe("channel").callback { |remaining_subs|
|
244
|
+
remaining_subs.should == 0
|
245
|
+
redis.publish("channel", "hello") {
|
246
|
+
done
|
247
|
+
}
|
248
|
+
}
|
249
|
+
}
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should expose raw pattern pubsub events from redis" do
|
255
|
+
callback_count = 0
|
256
|
+
connect do |redis|
|
257
|
+
redis.pubsub.on(:psubscribe) { |pattern, subscription_count|
|
258
|
+
# 2. Get subscribe callback
|
259
|
+
callback_count += 1
|
260
|
+
pattern.should == "channel:*"
|
261
|
+
subscription_count.should == 1
|
262
|
+
|
263
|
+
# 3. Publish on channel
|
264
|
+
redis.publish('channel:foo', 'foo')
|
265
|
+
}
|
266
|
+
|
267
|
+
redis.pubsub.on(:pmessage) { |pattern, channel, message|
|
268
|
+
# 4. Get message callback
|
269
|
+
callback_count += 1
|
270
|
+
pattern.should == 'channel:*'
|
271
|
+
channel.should == 'channel:foo'
|
272
|
+
message.should == 'foo'
|
273
|
+
|
274
|
+
callback_count.should == 2
|
275
|
+
done
|
276
|
+
}
|
277
|
+
|
278
|
+
# 1. Subscribe to channel
|
279
|
+
redis.pubsub.psubscribe('channel:*')
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
it "should resubscribe to all pattern subscriptions on reconnect" do
|
284
|
+
callback_count = 0
|
285
|
+
connect do |redis|
|
286
|
+
# 1. Subscribe to channels
|
287
|
+
redis.pubsub.psubscribe('foo:*') { |channel, message|
|
288
|
+
channel.should == 'foo:a'
|
289
|
+
message.should == 'hello foo'
|
290
|
+
callback_count += 1
|
291
|
+
}
|
292
|
+
redis.pubsub.psubscribe('bar:*') { |channel, message|
|
293
|
+
channel.should == 'bar:b'
|
294
|
+
message.should == 'hello bar'
|
295
|
+
callback_count += 1
|
296
|
+
EM.next_tick {
|
297
|
+
# 4. Success if both messages have been received
|
298
|
+
callback_count.should == 2
|
299
|
+
done
|
300
|
+
}
|
301
|
+
}.callback { |subscription_count|
|
302
|
+
subscription_count.should == 2
|
303
|
+
# 2. Subscriptions complete. Now force disconnect
|
304
|
+
redis.pubsub.instance_variable_get(:@connection).close_connection
|
305
|
+
|
306
|
+
EM.add_timer(0.1) {
|
307
|
+
# 3. After giving time to reconnect publish to both channels
|
308
|
+
redis.publish('foo:a', 'hello foo')
|
309
|
+
redis.publish('bar:b', 'hello bar')
|
310
|
+
}
|
311
|
+
}
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|