gilmour-em-hiredis 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +35 -0
- data/Gemfile +3 -0
- data/LICENCE +19 -0
- data/README.md +178 -0
- data/Rakefile +11 -0
- data/em-hiredis.gemspec +28 -0
- data/examples/getting_started.rb +14 -0
- data/examples/lua/sum.lua +4 -0
- data/examples/lua_example.rb +35 -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 +65 -0
- data/lib/em-hiredis/base_client.rb +264 -0
- data/lib/em-hiredis/client.rb +110 -0
- data/lib/em-hiredis/connection.rb +69 -0
- data/lib/em-hiredis/event_emitter.rb +29 -0
- data/lib/em-hiredis/lock.rb +88 -0
- data/lib/em-hiredis/lock_lua/lock_acquire.lua +17 -0
- data/lib/em-hiredis/lock_lua/lock_release.lua +9 -0
- data/lib/em-hiredis/persistent_lock.rb +81 -0
- data/lib/em-hiredis/pubsub_client.rb +202 -0
- data/lib/em-hiredis/version.rb +5 -0
- data/spec/base_client_spec.rb +118 -0
- data/spec/connection_spec.rb +56 -0
- data/spec/inactivity_check_spec.rb +66 -0
- data/spec/live_redis_protocol_spec.rb +527 -0
- data/spec/lock_spec.rb +137 -0
- data/spec/pubsub_spec.rb +314 -0
- data/spec/redis_commands_spec.rb +931 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/connection_helper.rb +11 -0
- data/spec/support/inprocess_redis_mock.rb +83 -0
- data/spec/support/redis_mock.rb +65 -0
- data/spec/url_param_spec.rb +43 -0
- metadata +163 -0
data/spec/lock_spec.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EventMachine::Hiredis::Lock do
|
4
|
+
|
5
|
+
def start(timeout = 1)
|
6
|
+
connect(timeout) do |redis|
|
7
|
+
@redis = redis
|
8
|
+
yield
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def new_lock
|
13
|
+
EventMachine::Hiredis::Lock.new(@redis, "test-lock", 2)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can be acquired" do
|
17
|
+
start {
|
18
|
+
new_lock.acquire.callback {
|
19
|
+
done
|
20
|
+
}.errback { |e|
|
21
|
+
fail e
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "is re-entrant" do
|
27
|
+
start {
|
28
|
+
lock = new_lock
|
29
|
+
lock.acquire.callback {
|
30
|
+
lock.acquire.callback {
|
31
|
+
done
|
32
|
+
}.errback { |e|
|
33
|
+
fail e
|
34
|
+
}
|
35
|
+
}.errback { |e|
|
36
|
+
fail e
|
37
|
+
}
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
it "is exclusive" do
|
42
|
+
start {
|
43
|
+
new_lock.acquire.callback {
|
44
|
+
new_lock.acquire.errback {
|
45
|
+
done
|
46
|
+
}.callback {
|
47
|
+
fail "Should not be able to acquire lock from different client"
|
48
|
+
}
|
49
|
+
}.errback { |e|
|
50
|
+
fail e
|
51
|
+
}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
it "can be released and taken by another instance" do
|
56
|
+
start {
|
57
|
+
lock = new_lock
|
58
|
+
lock.acquire.callback {
|
59
|
+
lock.unlock.callback {
|
60
|
+
new_lock.acquire.callback {
|
61
|
+
done
|
62
|
+
}.errback { |e|
|
63
|
+
fail e
|
64
|
+
}
|
65
|
+
}.errback { |e|
|
66
|
+
fail e
|
67
|
+
}
|
68
|
+
}.errback { |e|
|
69
|
+
fail e
|
70
|
+
}
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
it "times out" do
|
75
|
+
start(3) {
|
76
|
+
new_lock.acquire.callback {
|
77
|
+
EM.add_timer(2) {
|
78
|
+
new_lock.acquire.callback {
|
79
|
+
done
|
80
|
+
}.errback { |e|
|
81
|
+
fail e
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}.errback { |e|
|
85
|
+
fail e
|
86
|
+
}
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
it "extends timeout on re-entry" do
|
91
|
+
start(4) {
|
92
|
+
lock = new_lock
|
93
|
+
lock.acquire.callback {
|
94
|
+
EM.add_timer(1) {
|
95
|
+
lock.acquire.callback {
|
96
|
+
EM.add_timer(1.5) {
|
97
|
+
# Check it's still locked by initial instance
|
98
|
+
new_lock.acquire.errback {
|
99
|
+
done
|
100
|
+
}.callback { |e|
|
101
|
+
fail e
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}.errback { |e|
|
105
|
+
fail e
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}.errback { |e|
|
109
|
+
fail e
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
it "fails to release if it has not been taken" do
|
115
|
+
start {
|
116
|
+
new_lock.unlock.errback {
|
117
|
+
done
|
118
|
+
}.callback {
|
119
|
+
fail "Released lock which had not been taken"
|
120
|
+
}
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
it "fails to release if taken by another instance" do
|
125
|
+
start {
|
126
|
+
new_lock.acquire.callback {
|
127
|
+
new_lock.unlock.errback {
|
128
|
+
done
|
129
|
+
}.callback {
|
130
|
+
fail "Released lock belonging to another instance"
|
131
|
+
}
|
132
|
+
}.errback { |e|
|
133
|
+
fail e
|
134
|
+
}
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
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_client.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_client.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_client.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_client.subscribe("channel", proc1)
|
52
|
+
redis.pubsub_client.subscribe("channel", proc2).callback {
|
53
|
+
redis.pubsub_client.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_client.subscribe("channel", proc).callback { |subs_count|
|
63
|
+
subs_count.should == 1
|
64
|
+
redis.pubsub_client.unsubscribe_proc("channel", proc).callback {
|
65
|
+
# Slightly awkward way to check that unsubscribe happened:
|
66
|
+
redis.pubsub_client.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_client.on('message') { |channel, message| fail }
|
80
|
+
# Block subscription
|
81
|
+
redis.pubsub_client.subscribe("channel") { |m| fail } # block
|
82
|
+
# Proc example
|
83
|
+
df = redis.pubsub_client.subscribe("channel", Proc.new { |m| fail })
|
84
|
+
|
85
|
+
df.callback {
|
86
|
+
redis.pubsub_client.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_client.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_client.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_client.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_client.subscribe('channel1') {
|
131
|
+
callback_count += 1
|
132
|
+
}
|
133
|
+
redis.pubsub_client.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_client.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_client.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_client.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_client.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_client.psubscribe("channel:*", proc1)
|
209
|
+
redis.pubsub_client.psubscribe("channel:*", proc2).callback {
|
210
|
+
redis.pubsub_client.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_client.psubscribe("channel:*", proc).callback { |subs_count|
|
220
|
+
subs_count.should == 1
|
221
|
+
redis.pubsub_client.punsubscribe_proc("channel:*", proc).callback {
|
222
|
+
# Slightly awkward way to check that unsubscribe happened:
|
223
|
+
redis.pubsub_client.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_client.on('pmessage') { |pattern, channel, message| fail }
|
237
|
+
# Block subscription
|
238
|
+
redis.pubsub_client.psubscribe("channel") { |c, m| fail } # block
|
239
|
+
# Proc example
|
240
|
+
df = redis.pubsub_client.psubscribe("channel", Proc.new { |c, m| fail })
|
241
|
+
|
242
|
+
df.callback {
|
243
|
+
redis.pubsub_client.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_client.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_client.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_client.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_client.psubscribe('foo:*') { |channel, message|
|
288
|
+
channel.should == 'foo:a'
|
289
|
+
message.should == 'hello foo'
|
290
|
+
callback_count += 1
|
291
|
+
}
|
292
|
+
redis.pubsub_client.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_client.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
|