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/client.rb
CHANGED
@@ -1,184 +1,69 @@
|
|
1
1
|
module EventMachine::Hiredis
|
2
|
-
class Client
|
3
|
-
PUBSUB_MESSAGES = %w{message pmessage}.freeze
|
4
|
-
|
5
|
-
include EventMachine::Hiredis::EventEmitter
|
6
|
-
include EM::Deferrable
|
7
|
-
|
8
|
-
attr_reader :host, :port, :password, :db
|
9
|
-
|
2
|
+
class Client < BaseClient
|
10
3
|
def self.connect(host = 'localhost', port = 6379)
|
11
4
|
new(host, port).connect
|
12
5
|
end
|
13
6
|
|
14
|
-
def
|
15
|
-
@
|
16
|
-
|
17
|
-
@closing_connection = false
|
7
|
+
def monitor(&blk)
|
8
|
+
@monitoring = true
|
9
|
+
method_missing(:monitor, &blk)
|
18
10
|
end
|
19
11
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@deferred_status = nil
|
28
|
-
@connected = false
|
29
|
-
unless @closing_connection
|
30
|
-
@reconnecting = true
|
31
|
-
reconnect
|
32
|
-
end
|
33
|
-
else
|
34
|
-
unless @closing_connection
|
35
|
-
EM.add_timer(1) { reconnect }
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
@connection.on(:connected) do
|
41
|
-
@connected = true
|
42
|
-
|
43
|
-
auth(@password) if @password
|
44
|
-
select(@db) if @db
|
45
|
-
|
46
|
-
@subs.each { |s| method_missing(:subscribe, s) }
|
47
|
-
@psubs.each { |s| method_missing(:psubscribe, s) }
|
48
|
-
succeed
|
49
|
-
|
50
|
-
if @reconnecting
|
51
|
-
@reconnecting = false
|
52
|
-
emit(:reconnected)
|
12
|
+
def info
|
13
|
+
df = method_missing(:info)
|
14
|
+
df.callback { |response|
|
15
|
+
info = {}
|
16
|
+
response.each_line do |line|
|
17
|
+
key, value = line.split(":", 2)
|
18
|
+
info[key.to_sym] = value.chomp if value
|
53
19
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
deferred = @defs.shift
|
60
|
-
deferred.fail(reply) if deferred
|
61
|
-
else
|
62
|
-
if reply && PUBSUB_MESSAGES.include?(reply[0]) # reply can be nil
|
63
|
-
kind, subscription, d1, d2 = *reply
|
20
|
+
df.succeed(info)
|
21
|
+
}
|
22
|
+
df.callback { |info| yield info } if block_given?
|
23
|
+
df
|
24
|
+
end
|
64
25
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
else
|
76
|
-
raise "Replies out of sync: #{reply.inspect}"
|
77
|
-
end
|
78
|
-
else
|
79
|
-
deferred = @defs.shift
|
80
|
-
deferred.succeed(reply) if deferred
|
26
|
+
def info_commandstats(&blk)
|
27
|
+
hash_processor = lambda do |response|
|
28
|
+
commands = {}
|
29
|
+
response.each_line do |line|
|
30
|
+
command, data = line.split(':')
|
31
|
+
if data
|
32
|
+
c = commands[command.sub('cmdstat_', '').to_sym] = {}
|
33
|
+
data.split(',').each do |d|
|
34
|
+
k, v = d.split('=')
|
35
|
+
c[k.to_sym] = v =~ /\./ ? v.to_f : v.to_i
|
81
36
|
end
|
82
37
|
end
|
83
38
|
end
|
39
|
+
blk.call(commands)
|
84
40
|
end
|
85
|
-
|
86
|
-
@connected = false
|
87
|
-
@reconnecting = false
|
88
|
-
|
89
|
-
return self
|
41
|
+
method_missing(:info, 'commandstats', &hash_processor)
|
90
42
|
end
|
91
43
|
|
92
|
-
#
|
93
|
-
#
|
44
|
+
# Gives access to a richer interface for pubsub subscriptions on a
|
45
|
+
# separate redis connection
|
94
46
|
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
@connected && @defs.size > 0
|
100
|
-
end
|
101
|
-
|
102
|
-
def connected?
|
103
|
-
@connected
|
47
|
+
def pubsub
|
48
|
+
@pubsub ||= begin
|
49
|
+
PubsubClient.new(@host, @port, @password, @db).connect
|
50
|
+
end
|
104
51
|
end
|
105
52
|
|
106
|
-
def subscribe(
|
107
|
-
|
108
|
-
method_missing(:subscribe, channel)
|
53
|
+
def subscribe(*channels)
|
54
|
+
raise "Use pubsub client"
|
109
55
|
end
|
110
56
|
|
111
|
-
def unsubscribe(
|
112
|
-
|
113
|
-
method_missing(:unsubscribe, channel)
|
57
|
+
def unsubscribe(*channels)
|
58
|
+
raise "Use pubsub client"
|
114
59
|
end
|
115
60
|
|
116
61
|
def psubscribe(channel)
|
117
|
-
|
118
|
-
method_missing(:psubscribe, channel)
|
62
|
+
raise "Use pubsub client"
|
119
63
|
end
|
120
64
|
|
121
65
|
def punsubscribe(channel)
|
122
|
-
|
123
|
-
method_missing(:punsubscribe, channel)
|
124
|
-
end
|
125
|
-
|
126
|
-
def select(db, &blk)
|
127
|
-
@db = db
|
128
|
-
method_missing(:select, db, &blk)
|
129
|
-
end
|
130
|
-
|
131
|
-
def auth(password, &blk)
|
132
|
-
@password = password
|
133
|
-
method_missing(:auth, password, &blk)
|
134
|
-
end
|
135
|
-
|
136
|
-
def monitor(&blk)
|
137
|
-
@monitoring = true
|
138
|
-
method_missing(:monitor, &blk)
|
139
|
-
end
|
140
|
-
|
141
|
-
def info(&blk)
|
142
|
-
hash_processor = lambda do |response|
|
143
|
-
info = {}
|
144
|
-
response.each_line do |line|
|
145
|
-
key, value = line.split(":", 2)
|
146
|
-
info[key.to_sym] = value.chomp
|
147
|
-
end
|
148
|
-
blk.call(info)
|
149
|
-
end
|
150
|
-
method_missing(:info, &hash_processor)
|
151
|
-
end
|
152
|
-
|
153
|
-
def close_connection
|
154
|
-
@closing_connection = true
|
155
|
-
@connection.close_connection_after_writing
|
156
|
-
@defs.each
|
157
|
-
end
|
158
|
-
|
159
|
-
private
|
160
|
-
|
161
|
-
def method_missing(sym, *args)
|
162
|
-
deferred = EM::DefaultDeferrable.new
|
163
|
-
# Shortcut for defining the callback case with just a block
|
164
|
-
deferred.callback { |result| yield(result) } if block_given?
|
165
|
-
|
166
|
-
if @connected
|
167
|
-
@connection.send_command(sym, *args)
|
168
|
-
@defs.push(deferred)
|
169
|
-
else
|
170
|
-
callback do
|
171
|
-
@connection.send_command(sym, *args)
|
172
|
-
@defs.push(deferred)
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
deferred
|
177
|
-
end
|
178
|
-
|
179
|
-
def reconnect
|
180
|
-
EventMachine::Hiredis.logger.debug("Trying to reconnect to Redis")
|
181
|
-
@connection.reconnect @host, @port
|
66
|
+
raise "Use pubsub client"
|
182
67
|
end
|
183
68
|
end
|
184
69
|
end
|
@@ -7,10 +7,15 @@ module EventMachine::Hiredis
|
|
7
7
|
def initialize(host, port)
|
8
8
|
super
|
9
9
|
@host, @port = host, port
|
10
|
+
@name = "[em-hiredis #{@host}:#{@port}]"
|
11
|
+
end
|
12
|
+
|
13
|
+
def reconnect(host, port)
|
14
|
+
super
|
15
|
+
@host, @port = host, port
|
10
16
|
end
|
11
17
|
|
12
18
|
def connection_completed
|
13
|
-
EventMachine::Hiredis.logger.info("Connected to Redis")
|
14
19
|
@reader = ::Hiredis::Reader.new
|
15
20
|
emit(:connected)
|
16
21
|
end
|
@@ -23,12 +28,15 @@ module EventMachine::Hiredis
|
|
23
28
|
end
|
24
29
|
|
25
30
|
def unbind
|
26
|
-
EventMachine::Hiredis.logger.info("Disconnected from Redis")
|
27
31
|
emit(:closed)
|
28
32
|
end
|
29
33
|
|
30
|
-
def send_command(
|
31
|
-
send_data(command(
|
34
|
+
def send_command(command, args)
|
35
|
+
send_data(command(command, *args))
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
@name
|
32
40
|
end
|
33
41
|
|
34
42
|
protected
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module EM::Hiredis
|
2
|
+
# Distributed lock built on redis
|
3
|
+
class Lock
|
4
|
+
# Register an callback which will be called 1s before the lock expires
|
5
|
+
def onexpire(&blk); @onexpire = blk; end
|
6
|
+
|
7
|
+
def initialize(redis, key, timeout)
|
8
|
+
@redis, @key, @timeout = redis, key, timeout
|
9
|
+
@locked = false
|
10
|
+
@expiry = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
# Acquire the lock
|
14
|
+
#
|
15
|
+
# It is ok to call acquire again before the lock expires, which will attempt to extend the existing lock.
|
16
|
+
#
|
17
|
+
# Returns a deferrable which either succeeds if the lock can be acquired, or fails if it cannot. In both cases the expiry timestamp is returned (for the new lock or for the expired one respectively)
|
18
|
+
def acquire
|
19
|
+
df = EM::DefaultDeferrable.new
|
20
|
+
expiry = new_expiry
|
21
|
+
@redis.setnx(@key, expiry).callback { |setnx|
|
22
|
+
if setnx == 1
|
23
|
+
lock_acquired(expiry)
|
24
|
+
EM::Hiredis.logger.debug "#{to_s} Acquired new lock"
|
25
|
+
df.succeed(expiry)
|
26
|
+
else
|
27
|
+
attempt_to_acquire_existing_lock(df)
|
28
|
+
end
|
29
|
+
}.errback { |e|
|
30
|
+
df.fail(e)
|
31
|
+
}
|
32
|
+
return df
|
33
|
+
end
|
34
|
+
|
35
|
+
# Release the lock
|
36
|
+
#
|
37
|
+
# Returns a deferrable
|
38
|
+
def unlock
|
39
|
+
EM.cancel_timer(@expire_timer) if @expire_timer
|
40
|
+
|
41
|
+
unless active
|
42
|
+
df = EM::DefaultDeferrable.new
|
43
|
+
df.fail Error.new("Cannot unlock, lock not active")
|
44
|
+
return df
|
45
|
+
end
|
46
|
+
|
47
|
+
@redis.del(@key)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Lock has been acquired and we're within it's expiry time
|
51
|
+
def active
|
52
|
+
@locked && Time.now.to_i < @expiry
|
53
|
+
end
|
54
|
+
|
55
|
+
# This should not be used in normal operation - force clear
|
56
|
+
def clear
|
57
|
+
@redis.del(@key)
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
"[lock #{@key}]"
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def attempt_to_acquire_existing_lock(df)
|
67
|
+
@redis.get(@key) { |expiry_1|
|
68
|
+
expiry_1 = expiry_1.to_i
|
69
|
+
if expiry_1 == @expiry || expiry_1 < Time.now.to_i
|
70
|
+
# Either the lock was ours or the lock has already expired
|
71
|
+
expiry = new_expiry
|
72
|
+
@redis.getset(@key, expiry) { |expiry_2|
|
73
|
+
expiry_2 = expiry_2.to_i
|
74
|
+
if expiry_2 == @expiry || expiry_2 < Time.now.to_i
|
75
|
+
lock_acquired(expiry)
|
76
|
+
EM::Hiredis.logger.debug "#{to_s} Acquired existing lock"
|
77
|
+
df.succeed(expiry)
|
78
|
+
else
|
79
|
+
# Another client got there first
|
80
|
+
EM::Hiredis.logger.debug "#{to_s} Could not acquire - another process acquired while we were in the process of acquiring"
|
81
|
+
df.fail(expiry_2)
|
82
|
+
end
|
83
|
+
}
|
84
|
+
else
|
85
|
+
# Someone else has an active lock
|
86
|
+
EM::Hiredis.logger.debug "#{to_s} Could not acquire - held by another process"
|
87
|
+
df.fail(expiry_1)
|
88
|
+
end
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def new_expiry
|
93
|
+
Time.now.to_i + @timeout + 1
|
94
|
+
end
|
95
|
+
|
96
|
+
def lock_acquired(expiry)
|
97
|
+
@locked = true
|
98
|
+
@expiry = expiry
|
99
|
+
EM.cancel_timer(@expire_timer) if @expire_timer
|
100
|
+
@expire_timer = EM.add_timer(@timeout) {
|
101
|
+
EM::Hiredis.logger.debug "#{to_s} Expires in 1s"
|
102
|
+
@onexpire.call if @onexpire
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,77 @@
|
|
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
|
+
@lock = EM::Hiredis::Lock.new(redis, key, @timeout)
|
25
|
+
@lock.onexpire {
|
26
|
+
# When the lock is about to expire, extend (called 1s before expiry)
|
27
|
+
acquire()
|
28
|
+
}
|
29
|
+
@locked = false
|
30
|
+
EM.next_tick {
|
31
|
+
@running = true
|
32
|
+
acquire
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Acquire the lock (called automatically by initialize)
|
37
|
+
def acquire
|
38
|
+
return unless @running
|
39
|
+
|
40
|
+
@lock.acquire.callback {
|
41
|
+
if !@locked
|
42
|
+
@onlocked.call if @onlocked
|
43
|
+
@locked = true
|
44
|
+
end
|
45
|
+
}.errback { |e|
|
46
|
+
if @locked
|
47
|
+
# We were previously locked
|
48
|
+
@onunlocked.call if @onunlocked
|
49
|
+
@locked = false
|
50
|
+
end
|
51
|
+
|
52
|
+
if e.kind_of?(EM::Hiredis::RedisError)
|
53
|
+
err = e.redis_error
|
54
|
+
EM::Hiredis.logger.warn "Unexpected error acquiring #{@lock} #{err}"
|
55
|
+
end
|
56
|
+
|
57
|
+
EM.add_timer(@retry_timeout) {
|
58
|
+
acquire() unless @locked
|
59
|
+
}
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def stop
|
64
|
+
@running = false
|
65
|
+
if @locked
|
66
|
+
# We were previously locked
|
67
|
+
@onunlocked.call if @onunlocked
|
68
|
+
@locked = false
|
69
|
+
end
|
70
|
+
@lock.unlock
|
71
|
+
end
|
72
|
+
|
73
|
+
def locked?
|
74
|
+
@locked
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module EventMachine::Hiredis
|
2
|
+
class PubsubClient < BaseClient
|
3
|
+
PUBSUB_MESSAGES = %w{message pmessage subscribe unsubscribe psubscribe punsubscribe}.freeze
|
4
|
+
|
5
|
+
def initialize(host='localhost', port='6379', password=nil, db=nil)
|
6
|
+
@subs, @psubs = [], []
|
7
|
+
@pubsub_defs = Hash.new { |h,k| h[k] = [] }
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def connect
|
12
|
+
@sub_callbacks = Hash.new { |h, k| h[k] = [] }
|
13
|
+
@psub_callbacks = Hash.new { |h, k| h[k] = [] }
|
14
|
+
|
15
|
+
# Resubsubscribe to channels on reconnect
|
16
|
+
on(:reconnected) {
|
17
|
+
raw_send_command(:subscribe, @subs) if @subs.any?
|
18
|
+
raw_send_command(:psubscribe, @psubs) if @psubs.any?
|
19
|
+
}
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
# Subscribe to a pubsub channel
|
25
|
+
#
|
26
|
+
# If an optional proc / block is provided then it will be called when a
|
27
|
+
# message is received on this channel
|
28
|
+
#
|
29
|
+
# @return [Deferrable] Redis subscribe call
|
30
|
+
#
|
31
|
+
def subscribe(channel, proc = nil, &block)
|
32
|
+
if cb = proc || block
|
33
|
+
@sub_callbacks[channel] << cb
|
34
|
+
end
|
35
|
+
@subs << channel
|
36
|
+
raw_send_command(:subscribe, [channel])
|
37
|
+
return pubsub_deferrable(channel)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Unsubscribe all callbacks for a given channel
|
41
|
+
#
|
42
|
+
# @return [Deferrable] Redis unsubscribe call
|
43
|
+
#
|
44
|
+
def unsubscribe(channel)
|
45
|
+
@sub_callbacks.delete(channel)
|
46
|
+
@subs.delete(channel)
|
47
|
+
raw_send_command(:unsubscribe, [channel])
|
48
|
+
return pubsub_deferrable(channel)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Unsubscribe a given callback from a channel. Will unsubscribe from redis
|
52
|
+
# if there are no remaining subscriptions on this channel
|
53
|
+
#
|
54
|
+
# @return [Deferrable] Succeeds when the unsubscribe has completed or
|
55
|
+
# fails if callback could not be found. Note that success may happen
|
56
|
+
# immediately in the case that there are other callbacks for the same
|
57
|
+
# channel (and therefore no unsubscription from redis is necessary)
|
58
|
+
#
|
59
|
+
def unsubscribe_proc(channel, proc)
|
60
|
+
df = EM::DefaultDeferrable.new
|
61
|
+
if @sub_callbacks[channel].delete(proc)
|
62
|
+
if @sub_callbacks[channel].any?
|
63
|
+
# Succeed deferrable immediately - no need to unsubscribe
|
64
|
+
df.succeed
|
65
|
+
else
|
66
|
+
unsubscribe(channel).callback { |_|
|
67
|
+
df.succeed
|
68
|
+
}
|
69
|
+
end
|
70
|
+
else
|
71
|
+
df.fail
|
72
|
+
end
|
73
|
+
return df
|
74
|
+
end
|
75
|
+
|
76
|
+
# Pattern subscribe to a pubsub channel
|
77
|
+
#
|
78
|
+
# If an optional proc / block is provided then it will be called (with the
|
79
|
+
# channel name and message) when a message is received on a matching
|
80
|
+
# channel
|
81
|
+
#
|
82
|
+
# @return [Deferrable] Redis psubscribe call
|
83
|
+
#
|
84
|
+
def psubscribe(pattern, proc = nil, &block)
|
85
|
+
if cb = proc || block
|
86
|
+
@psub_callbacks[pattern] << cb
|
87
|
+
end
|
88
|
+
@psubs << pattern
|
89
|
+
raw_send_command(:psubscribe, [pattern])
|
90
|
+
return pubsub_deferrable(pattern)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Pattern unsubscribe all callbacks for a given pattern
|
94
|
+
#
|
95
|
+
# @return [Deferrable] Redis punsubscribe call
|
96
|
+
#
|
97
|
+
def punsubscribe(pattern)
|
98
|
+
@psub_callbacks.delete(pattern)
|
99
|
+
@psubs.delete(pattern)
|
100
|
+
raw_send_command(:punsubscribe, [pattern])
|
101
|
+
return pubsub_deferrable(pattern)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Unsubscribe a given callback from a pattern. Will unsubscribe from redis
|
105
|
+
# if there are no remaining subscriptions on this pattern
|
106
|
+
#
|
107
|
+
# @return [Deferrable] Succeeds when the punsubscribe has completed or
|
108
|
+
# fails if callback could not be found. Note that success may happen
|
109
|
+
# immediately in the case that there are other callbacks for the same
|
110
|
+
# pattern (and therefore no punsubscription from redis is necessary)
|
111
|
+
#
|
112
|
+
def punsubscribe_proc(pattern, proc)
|
113
|
+
df = EM::DefaultDeferrable.new
|
114
|
+
if @psub_callbacks[pattern].delete(proc)
|
115
|
+
if @psub_callbacks[pattern].any?
|
116
|
+
# Succeed deferrable immediately - no need to punsubscribe
|
117
|
+
df.succeed
|
118
|
+
else
|
119
|
+
punsubscribe(pattern).callback { |_|
|
120
|
+
df.succeed
|
121
|
+
}
|
122
|
+
end
|
123
|
+
else
|
124
|
+
df.fail
|
125
|
+
end
|
126
|
+
return df
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
# Send a command to redis without adding a deferrable for it. This is
|
132
|
+
# useful for commands for which replies work or need to be treated
|
133
|
+
# differently
|
134
|
+
def raw_send_command(sym, args)
|
135
|
+
if @connected
|
136
|
+
@connection.send_command(sym, args)
|
137
|
+
else
|
138
|
+
callback do
|
139
|
+
@connection.send_command(sym, args)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
return nil
|
143
|
+
end
|
144
|
+
|
145
|
+
def pubsub_deferrable(channel)
|
146
|
+
df = EM::DefaultDeferrable.new
|
147
|
+
@pubsub_defs[channel].push(df)
|
148
|
+
df
|
149
|
+
end
|
150
|
+
|
151
|
+
def handle_reply(reply)
|
152
|
+
if reply && PUBSUB_MESSAGES.include?(reply[0]) # reply can be nil
|
153
|
+
# Note: pmessage is the only message with 4 arguments
|
154
|
+
kind, subscription, d1, d2 = *reply
|
155
|
+
|
156
|
+
case kind.to_sym
|
157
|
+
when :message
|
158
|
+
if @sub_callbacks.has_key?(subscription)
|
159
|
+
@sub_callbacks[subscription].each { |cb| cb.call(d1) }
|
160
|
+
end
|
161
|
+
# Arguments are channel, message payload
|
162
|
+
emit(:message, subscription, d1)
|
163
|
+
when :pmessage
|
164
|
+
if @psub_callbacks.has_key?(subscription)
|
165
|
+
@psub_callbacks[subscription].each { |cb| cb.call(d1, d2) }
|
166
|
+
end
|
167
|
+
# Arguments are original pattern, channel, message payload
|
168
|
+
emit(:pmessage, subscription, d1, d2)
|
169
|
+
else
|
170
|
+
if @pubsub_defs[subscription].any?
|
171
|
+
df = @pubsub_defs[subscription].shift
|
172
|
+
df.succeed(d1)
|
173
|
+
# Cleanup empty arrays
|
174
|
+
if @pubsub_defs[subscription].empty?
|
175
|
+
@pubsub_defs.delete(subscription)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Also emit the event, as an alternative to using the deferrables
|
180
|
+
emit(kind.to_sym, subscription, d1)
|
181
|
+
end
|
182
|
+
else
|
183
|
+
super
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|