em-hiredis 0.1.1 → 0.2.0
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.
- 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
|