gilmour-em-hiredis 0.3.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/.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
|