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
@@ -0,0 +1,29 @@
|
|
1
|
+
module EventMachine::Hiredis
|
2
|
+
module EventEmitter
|
3
|
+
def on(event, &listener)
|
4
|
+
_listeners[event] << listener
|
5
|
+
end
|
6
|
+
|
7
|
+
def emit(event, *args)
|
8
|
+
_listeners[event].each { |l| l.call(*args) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove_listener(event, &listener)
|
12
|
+
_listeners[event].delete(listener)
|
13
|
+
end
|
14
|
+
|
15
|
+
def remove_all_listeners(event)
|
16
|
+
_listeners.delete(event)
|
17
|
+
end
|
18
|
+
|
19
|
+
def listeners(event)
|
20
|
+
_listeners[event]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def _listeners
|
26
|
+
@_listeners ||= Hash.new { |h,k| h[k] = [] }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module EM::Hiredis
|
4
|
+
# Cross-process re-entrant lock, backed by redis
|
5
|
+
class Lock
|
6
|
+
|
7
|
+
EM::Hiredis::Client.load_scripts_from(File.expand_path("../lock_lua", __FILE__))
|
8
|
+
|
9
|
+
# Register a callback which will be called 1s before the lock expires
|
10
|
+
# This is an informational callback, there is no hard guarantee on the timing
|
11
|
+
# of its invocation because the callback firing and lock key expiry are handled
|
12
|
+
# by different clocks (the client process and redis server respectively)
|
13
|
+
def onexpire(&blk); @onexpire = blk; end
|
14
|
+
|
15
|
+
def initialize(redis, key, timeout)
|
16
|
+
unless timeout.kind_of?(Fixnum) && timeout >= 1
|
17
|
+
raise "Timeout must be an integer and >= 1s"
|
18
|
+
end
|
19
|
+
@redis, @key, @timeout = redis, key, timeout
|
20
|
+
@token = SecureRandom.hex
|
21
|
+
end
|
22
|
+
|
23
|
+
# Acquire the lock
|
24
|
+
#
|
25
|
+
# This is a re-entrant lock, re-acquiring will succeed and extend the timeout
|
26
|
+
#
|
27
|
+
# Returns a deferrable which either succeeds if the lock can be acquired, or fails if it cannot.
|
28
|
+
def acquire
|
29
|
+
df = EM::DefaultDeferrable.new
|
30
|
+
@redis.lock_acquire([@key], [@token, @timeout]).callback { |success|
|
31
|
+
if (success)
|
32
|
+
EM::Hiredis.logger.debug "#{to_s} acquired"
|
33
|
+
|
34
|
+
EM.cancel_timer(@expire_timer) if @expire_timer
|
35
|
+
@expire_timer = EM.add_timer(@timeout - 1) {
|
36
|
+
EM::Hiredis.logger.debug "#{to_s} Expires in 1s"
|
37
|
+
@onexpire.call if @onexpire
|
38
|
+
}
|
39
|
+
|
40
|
+
df.succeed
|
41
|
+
else
|
42
|
+
EM::Hiredis.logger.debug "#{to_s} failed to acquire"
|
43
|
+
df.fail("Lock is not available")
|
44
|
+
end
|
45
|
+
}.errback { |e|
|
46
|
+
EM::Hiredis.logger.error "#{to_s} Error acquiring lock #{e}"
|
47
|
+
df.fail(e)
|
48
|
+
}
|
49
|
+
df
|
50
|
+
end
|
51
|
+
|
52
|
+
# Release the lock
|
53
|
+
#
|
54
|
+
# Returns a deferrable
|
55
|
+
def unlock
|
56
|
+
EM.cancel_timer(@expire_timer) if @expire_timer
|
57
|
+
|
58
|
+
df = EM::DefaultDeferrable.new
|
59
|
+
@redis.lock_release([@key], [@token]).callback { |keys_removed|
|
60
|
+
if keys_removed > 0
|
61
|
+
EM::Hiredis.logger.debug "#{to_s} released"
|
62
|
+
df.succeed
|
63
|
+
else
|
64
|
+
EM::Hiredis.logger.debug "#{to_s} could not release, not held"
|
65
|
+
df.fail("Cannot release a lock we do not hold")
|
66
|
+
end
|
67
|
+
}.errback { |e|
|
68
|
+
EM::Hiredis.logger.error "#{to_s} Error releasing lock #{e}"
|
69
|
+
df.fail(e)
|
70
|
+
}
|
71
|
+
df
|
72
|
+
end
|
73
|
+
|
74
|
+
# This should not be used in normal operation.
|
75
|
+
# Force clear without regard to who owns the lock.
|
76
|
+
def clear
|
77
|
+
EM::Hiredis.logger.warn "#{to_s} Force clearing lock (unsafe)"
|
78
|
+
EM.cancel_timer(@expire_timer) if @expire_timer
|
79
|
+
|
80
|
+
@redis.del(@key)
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
"[lock #{@key}]"
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
-- Set key to token with expiry of timeout, if:
|
2
|
+
-- - It doesn't exist
|
3
|
+
-- - It exists and already has value of token (further set extends timeout)
|
4
|
+
-- Used to implement a re-entrant lock.
|
5
|
+
local key = KEYS[1]
|
6
|
+
local token = ARGV[1]
|
7
|
+
local timeout = ARGV[2]
|
8
|
+
|
9
|
+
local value = redis.call('get', key)
|
10
|
+
|
11
|
+
if value == token or not value then
|
12
|
+
-- Great, either we hold the lock or it's free for us to take
|
13
|
+
return redis.call('setex', key, timeout, token)
|
14
|
+
else
|
15
|
+
-- Someone else has it
|
16
|
+
return false
|
17
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module EM::Hiredis
|
2
|
+
# A lock that automatically re-acquires a lock before it loses it
|
3
|
+
#
|
4
|
+
# The lock is configured with the following two parameters
|
5
|
+
#
|
6
|
+
# :lock_timeout - Specifies how long each lock is acquired for. Setting
|
7
|
+
# this low means that locks need to be re-acquired very often, but a long
|
8
|
+
# timout means that a process that fails without cleaning up after itself
|
9
|
+
# (i.e. without releasing it's underlying lock) will block the anther
|
10
|
+
# process from picking up this lock
|
11
|
+
# replaced for a long while
|
12
|
+
# :retry_interval - Specifies how frequently to retry acquiring the lock in
|
13
|
+
# the case that the lock is held by another process, or there's an error
|
14
|
+
# communicating with redis
|
15
|
+
#
|
16
|
+
class PersistentLock
|
17
|
+
def onlocked(&blk); @onlocked = blk; self; end
|
18
|
+
def onunlocked(&blk); @onunlocked = blk; self; end
|
19
|
+
|
20
|
+
def initialize(redis, key, options = {})
|
21
|
+
@redis, @key = redis, key
|
22
|
+
@timeout = options[:lock_timeout] || 100
|
23
|
+
@retry_timeout = options[:retry_interval] || 60
|
24
|
+
|
25
|
+
@lock = EM::Hiredis::Lock.new(redis, key, @timeout)
|
26
|
+
@locked = false
|
27
|
+
EM.next_tick {
|
28
|
+
@running = true
|
29
|
+
acquire
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Acquire the lock (called automatically by initialize)
|
34
|
+
def acquire
|
35
|
+
return unless @running
|
36
|
+
|
37
|
+
@lock.acquire.callback {
|
38
|
+
if !@locked
|
39
|
+
@onlocked.call if @onlocked
|
40
|
+
@locked = true
|
41
|
+
end
|
42
|
+
|
43
|
+
# Re-acquire lock near the end of the period
|
44
|
+
@extend_timer = EM.add_timer(@timeout.to_f * 2 / 3) {
|
45
|
+
acquire()
|
46
|
+
}
|
47
|
+
}.errback { |e|
|
48
|
+
if @locked
|
49
|
+
# We were previously locked
|
50
|
+
@onunlocked.call if @onunlocked
|
51
|
+
@locked = false
|
52
|
+
end
|
53
|
+
|
54
|
+
if e.kind_of?(EM::Hiredis::RedisError)
|
55
|
+
err = e.redis_error
|
56
|
+
EM::Hiredis.logger.warn "Unexpected error acquiring #{@lock} #{err}"
|
57
|
+
end
|
58
|
+
|
59
|
+
@retry_timer = EM.add_timer(@retry_timeout) {
|
60
|
+
acquire() unless @locked
|
61
|
+
}
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def stop
|
66
|
+
@running = false
|
67
|
+
EM.cancel_timer(@extend_timer) if @extend_timer
|
68
|
+
EM.cancel_timer(@retry_timer) if @retry_timer
|
69
|
+
if @locked
|
70
|
+
# We were previously locked
|
71
|
+
@onunlocked.call if @onunlocked
|
72
|
+
@locked = false
|
73
|
+
end
|
74
|
+
@lock.unlock
|
75
|
+
end
|
76
|
+
|
77
|
+
def locked?
|
78
|
+
@locked
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
module EventMachine::Hiredis
|
2
|
+
class PubsubClient < BaseClient
|
3
|
+
PUBSUB_MESSAGES = %w{message pmessage subscribe unsubscribe psubscribe punsubscribe}.freeze
|
4
|
+
|
5
|
+
PING_CHANNEL = '__em-hiredis-ping'
|
6
|
+
|
7
|
+
def initialize(host='localhost', port='6379', password=nil, db=nil)
|
8
|
+
@subs, @psubs = [], []
|
9
|
+
@pubsub_defs = Hash.new { |h,k| h[k] = [] }
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect
|
14
|
+
@sub_callbacks = Hash.new { |h, k| h[k] = [] }
|
15
|
+
@psub_callbacks = Hash.new { |h, k| h[k] = [] }
|
16
|
+
|
17
|
+
# Resubsubscribe to channels on reconnect
|
18
|
+
on(:reconnected) {
|
19
|
+
raw_send_command(:subscribe, @subs) if @subs.any?
|
20
|
+
raw_send_command(:psubscribe, @psubs) if @psubs.any?
|
21
|
+
}
|
22
|
+
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Subscribe to a pubsub channel
|
27
|
+
#
|
28
|
+
# If an optional proc / block is provided then it will be called when a
|
29
|
+
# message is received on this channel
|
30
|
+
#
|
31
|
+
# @return [Deferrable] Redis subscribe call
|
32
|
+
#
|
33
|
+
def subscribe(channel, proc = nil, &block)
|
34
|
+
if cb = proc || block
|
35
|
+
@sub_callbacks[channel] << cb
|
36
|
+
end
|
37
|
+
@subs << channel
|
38
|
+
raw_send_command(:subscribe, [channel])
|
39
|
+
return pubsub_deferrable(channel)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Unsubscribe all callbacks for a given channel
|
43
|
+
#
|
44
|
+
# @return [Deferrable] Redis unsubscribe call
|
45
|
+
#
|
46
|
+
def unsubscribe(channel)
|
47
|
+
@sub_callbacks.delete(channel)
|
48
|
+
@subs.delete(channel)
|
49
|
+
raw_send_command(:unsubscribe, [channel])
|
50
|
+
return pubsub_deferrable(channel)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Unsubscribe a given callback from a channel. Will unsubscribe from redis
|
54
|
+
# if there are no remaining subscriptions on this channel
|
55
|
+
#
|
56
|
+
# @return [Deferrable] Succeeds when the unsubscribe has completed or
|
57
|
+
# fails if callback could not be found. Note that success may happen
|
58
|
+
# immediately in the case that there are other callbacks for the same
|
59
|
+
# channel (and therefore no unsubscription from redis is necessary)
|
60
|
+
#
|
61
|
+
def unsubscribe_proc(channel, proc)
|
62
|
+
df = EM::DefaultDeferrable.new
|
63
|
+
if @sub_callbacks[channel].delete(proc)
|
64
|
+
if @sub_callbacks[channel].any?
|
65
|
+
# Succeed deferrable immediately - no need to unsubscribe
|
66
|
+
df.succeed
|
67
|
+
else
|
68
|
+
unsubscribe(channel).callback { |_|
|
69
|
+
df.succeed
|
70
|
+
}
|
71
|
+
end
|
72
|
+
else
|
73
|
+
df.fail
|
74
|
+
end
|
75
|
+
return df
|
76
|
+
end
|
77
|
+
|
78
|
+
# Pattern subscribe to a pubsub channel
|
79
|
+
#
|
80
|
+
# If an optional proc / block is provided then it will be called (with the
|
81
|
+
# channel name and message) when a message is received on a matching
|
82
|
+
# channel
|
83
|
+
#
|
84
|
+
# @return [Deferrable] Redis psubscribe call
|
85
|
+
#
|
86
|
+
def psubscribe(pattern, proc = nil, &block)
|
87
|
+
if cb = proc || block
|
88
|
+
@psub_callbacks[pattern] << cb
|
89
|
+
end
|
90
|
+
@psubs << pattern
|
91
|
+
raw_send_command(:psubscribe, [pattern])
|
92
|
+
return pubsub_deferrable(pattern)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Pattern unsubscribe all callbacks for a given pattern
|
96
|
+
#
|
97
|
+
# @return [Deferrable] Redis punsubscribe call
|
98
|
+
#
|
99
|
+
def punsubscribe(pattern)
|
100
|
+
@psub_callbacks.delete(pattern)
|
101
|
+
@psubs.delete(pattern)
|
102
|
+
raw_send_command(:punsubscribe, [pattern])
|
103
|
+
return pubsub_deferrable(pattern)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Unsubscribe a given callback from a pattern. Will unsubscribe from redis
|
107
|
+
# if there are no remaining subscriptions on this pattern
|
108
|
+
#
|
109
|
+
# @return [Deferrable] Succeeds when the punsubscribe has completed or
|
110
|
+
# fails if callback could not be found. Note that success may happen
|
111
|
+
# immediately in the case that there are other callbacks for the same
|
112
|
+
# pattern (and therefore no punsubscription from redis is necessary)
|
113
|
+
#
|
114
|
+
def punsubscribe_proc(pattern, proc)
|
115
|
+
df = EM::DefaultDeferrable.new
|
116
|
+
if @psub_callbacks[pattern].delete(proc)
|
117
|
+
if @psub_callbacks[pattern].any?
|
118
|
+
# Succeed deferrable immediately - no need to punsubscribe
|
119
|
+
df.succeed
|
120
|
+
else
|
121
|
+
punsubscribe(pattern).callback { |_|
|
122
|
+
df.succeed
|
123
|
+
}
|
124
|
+
end
|
125
|
+
else
|
126
|
+
df.fail
|
127
|
+
end
|
128
|
+
return df
|
129
|
+
end
|
130
|
+
|
131
|
+
# Pubsub connections to not support even the PING command, but it is useful,
|
132
|
+
# especially with read-only connections like pubsub, to be able to check that
|
133
|
+
# the TCP connection is still usefully alive.
|
134
|
+
#
|
135
|
+
# This is not particularly elegant, but it's probably the best we can do
|
136
|
+
# for now. Ping support for pubsub connections is being considerred:
|
137
|
+
# https://github.com/antirez/redis/issues/420
|
138
|
+
def ping
|
139
|
+
subscribe(PING_CHANNEL).callback {
|
140
|
+
unsubscribe(PING_CHANNEL)
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# Send a command to redis without adding a deferrable for it. This is
|
147
|
+
# useful for commands for which replies work or need to be treated
|
148
|
+
# differently
|
149
|
+
def raw_send_command(sym, args)
|
150
|
+
if @connected
|
151
|
+
@connection.send_command(sym, args)
|
152
|
+
else
|
153
|
+
callback do
|
154
|
+
@connection.send_command(sym, args)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
return nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def pubsub_deferrable(channel)
|
161
|
+
df = EM::DefaultDeferrable.new
|
162
|
+
@pubsub_defs[channel].push(df)
|
163
|
+
df
|
164
|
+
end
|
165
|
+
|
166
|
+
def handle_reply(reply)
|
167
|
+
if reply && PUBSUB_MESSAGES.include?(reply[0]) # reply can be nil
|
168
|
+
# Note: pmessage is the only message with 4 arguments
|
169
|
+
kind, subscription, d1, d2 = *reply
|
170
|
+
|
171
|
+
case kind.to_sym
|
172
|
+
when :message
|
173
|
+
if @sub_callbacks.has_key?(subscription)
|
174
|
+
@sub_callbacks[subscription].each { |cb| cb.call(d1) }
|
175
|
+
end
|
176
|
+
# Arguments are channel, message payload
|
177
|
+
emit(:message, subscription, d1)
|
178
|
+
when :pmessage
|
179
|
+
if @psub_callbacks.has_key?(subscription)
|
180
|
+
@psub_callbacks[subscription].each { |cb| cb.call(d1, d2) }
|
181
|
+
end
|
182
|
+
# Arguments are original pattern, channel, message payload
|
183
|
+
emit(:pmessage, subscription, d1, d2)
|
184
|
+
else
|
185
|
+
if @pubsub_defs[subscription].any?
|
186
|
+
df = @pubsub_defs[subscription].shift
|
187
|
+
df.succeed(d1)
|
188
|
+
# Cleanup empty arrays
|
189
|
+
if @pubsub_defs[subscription].empty?
|
190
|
+
@pubsub_defs.delete(subscription)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Also emit the event, as an alternative to using the deferrables
|
195
|
+
emit(kind.to_sym, subscription, d1)
|
196
|
+
end
|
197
|
+
else
|
198
|
+
super
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,118 @@
|
|
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
|
+
msg = "WRONGTYPE Operation against a key holding the wrong kind of value"
|
109
|
+
e.message.should == msg
|
110
|
+
# This is the wrapped error from redis:
|
111
|
+
e.redis_error.class.should == RuntimeError
|
112
|
+
e.redis_error.message.should == msg
|
113
|
+
done
|
114
|
+
}
|
115
|
+
}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|