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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e19e2546bb52eb3e8f9f4a969d5666cd04eb0f39
|
4
|
+
data.tar.gz: 80435303e1cf652b489855c8e8f83228e614918e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aeea4ec51cc1a5ca3f0cf1ccecc5f00513d7840f1af34460ee5c19768e14d832d3460ada718927ac92c467d95a68f9e5f170bb318ea2b19510ef9b9c01de8158
|
7
|
+
data.tar.gz: 15d493eb81dfe7d044c8a55d25b3b269dd2af36d9d129fd5bfbd9d598f30d828a8ca7643a7a7f82a8f264b21d4ca8a2d5f5e43f25c48029792ff8024ba1a8062
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.2.0 (2013-04-05)
|
4
|
+
|
5
|
+
[NEW] Richer interface for pubsub (accessible via `client.pubsub`). See example in `examples/pubsub.rb`.
|
6
|
+
|
7
|
+
[NEW] Better failure handling:
|
8
|
+
|
9
|
+
* Clients now emit the following events: connected, reconnected, disconnected, reconnect_failed (passes the number of consecutive failures)
|
10
|
+
* Client is considered failed after 4 consecutive failures
|
11
|
+
* Fails all queued commands when client failed
|
12
|
+
* Can now reconfiure and reconnect an exising client
|
13
|
+
* Reconnect timeout can be configured (defaults to 0.5s)
|
14
|
+
|
15
|
+
[NEW] Added `EM::Hiredis::Lock` and `EM::Hiredis::PersistentLock`
|
16
|
+
|
17
|
+
[CHANGE] When a redis command fails, the errback is now always passed an `EM::Hiredis::Error`.
|
18
|
+
|
19
|
+
[FIX] Fixed info parsing for Redis 2.6
|
data/README.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# em-hiredis
|
2
|
+
|
3
|
+
## What
|
4
|
+
|
5
|
+
A Redis client for EventMachine designed to be fast and simple.
|
6
|
+
|
7
|
+
## Why
|
8
|
+
|
9
|
+
I wanted a client which:
|
10
|
+
|
11
|
+
* used the C hiredis library to parse redis replies
|
12
|
+
* had a convenient API for pubsub
|
13
|
+
* exposed the state of the underlying redis connections so that custom failover logic could be written outside the library
|
14
|
+
|
15
|
+
Also, <https://github.com/madsimian/em-redis> is no longer maintained.
|
16
|
+
|
17
|
+
## Getting started
|
3
18
|
|
4
19
|
Connect to redis:
|
5
20
|
|
@@ -10,11 +25,9 @@ Or, connect to redis with a redis URL (for a different host, port, password, DB)
|
|
10
25
|
|
11
26
|
redis = EM::Hiredis.connect("redis://:secretpassword@example.com:9000/4")
|
12
27
|
|
13
|
-
|
14
|
-
|
15
|
-
redis.callback { puts "Redis now connected" }
|
28
|
+
Commands may be sent immediately. Any commands sent while connecting to redis will be queued.
|
16
29
|
|
17
|
-
All redis commands are available without any remapping of names
|
30
|
+
All redis commands are available without any remapping of names, and return a deferrable
|
18
31
|
|
19
32
|
redis.set('foo', 'bar').callback {
|
20
33
|
redis.get('foo').callback { |value|
|
@@ -22,40 +35,54 @@ All redis commands are available without any remapping of names
|
|
22
35
|
}
|
23
36
|
}
|
24
37
|
|
38
|
+
If redis replies with an error (for example you called a hash operation against a set or the database is full), or if the redis connection disconnects before the command returns, the deferrable will fail.
|
39
|
+
|
40
|
+
redis.sadd('aset', 'member').callback {
|
41
|
+
response_deferrable = redis.hget('aset', 'member')
|
42
|
+
response_deferrable.errback { |e|
|
43
|
+
p e # => #<EventMachine::Hiredis::RedisError: Error reply from redis (wrapped in redis_error)>
|
44
|
+
p e.redis_error # => #<RuntimeError: ERR Operation against a key holding the wrong kind of value>
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
25
48
|
As a shortcut, if you're only interested in binding to the success case you can simply provide a block to any command
|
26
49
|
|
27
50
|
redis.get('foo') { |value|
|
28
51
|
p [:returned, value]
|
29
52
|
}
|
30
53
|
|
31
|
-
|
32
|
-
----------------
|
54
|
+
## Understanding the state of the connection
|
33
55
|
|
34
|
-
|
56
|
+
When a connection to redis server closes, a `:disconnected` event will be emitted and the connection will be immediately reconnect. If the connection reconnects a `:connected` event will be emitted.
|
35
57
|
|
36
|
-
|
37
|
-
response_deferrable = redis.hget('aset', 'member')
|
38
|
-
response_deferrable.errback { |e|
|
39
|
-
p e # => #<RuntimeError: ERR Operation against a key holding the wrong kind of value>
|
40
|
-
}
|
41
|
-
}
|
58
|
+
If a reconnect fails to connect, a `:reconnect_failed` event will be emitted (rather than `:disconnected`) with the number of consecutive failures, and the connection will be retried after a timeout (defaults to 0.5s, can be set via `EM::Hiredis.reconnect_timeout=`).
|
42
59
|
|
43
|
-
|
44
|
-
------
|
60
|
+
If a client fails to reconnect 4 consecutive times then a `:failed` event will be emitted, and any queued redis commands will be failed (otherwise they would be queued forever waiting for a reconnect).
|
45
61
|
|
46
|
-
|
62
|
+
## Pubsub
|
47
63
|
|
64
|
+
The way pubsub works in redis is that once a subscribe has been made on a connection, it's only possible to send (p)subscribe or (p)unsubscribe commands on that connection. The connection will also receive messages which are not replies to commands.
|
65
|
+
|
66
|
+
The regular `EM::Hiredis::Client` no longer understands pubsub messages - this logic has been moved to `EM::Hiredis::PubsubClient`. The pubsub client can either be initialized directly (see code) or you can get one connected to the same redis server by calling `#pubsub` on an existing `EM::Hiredis::Client` instance.
|
67
|
+
|
68
|
+
Pubsub can either be used in em-hiredis in a close-to-the-metal fashion, or you can use the convenience functionality for binding blocks to subscriptions if you prefer (recommended).
|
69
|
+
|
70
|
+
### Close to the metal
|
71
|
+
|
72
|
+
Basically just bind to `:message` and `:pmessage` events:
|
73
|
+
|
74
|
+
# Create two connections, one will be used for subscribing
|
48
75
|
redis = EM::Hiredis.connect
|
49
|
-
|
76
|
+
pubsub = redis.pubsub
|
50
77
|
|
51
|
-
|
52
|
-
|
78
|
+
pubsub.subscribe('bar.0').callback { puts "Subscribed" }
|
79
|
+
pubsub.psubscribe('bar.*')
|
53
80
|
|
54
|
-
|
81
|
+
pubsub.on(:message) { |channel, message|
|
55
82
|
p [:message, channel, message]
|
56
83
|
}
|
57
84
|
|
58
|
-
|
85
|
+
pubsub.on(:pmessage) { |key, channel, message|
|
59
86
|
p [:pmessage, key, channel, message]
|
60
87
|
}
|
61
88
|
|
@@ -65,17 +92,43 @@ This example should explain things. Once a redis connection is in a pubsub state
|
|
65
92
|
}
|
66
93
|
}
|
67
94
|
|
68
|
-
|
69
|
-
|
95
|
+
### Richer interface to pubsub
|
96
|
+
|
97
|
+
If you pass a block to `subscribe` or `psubscribe`, the passed block will be called whenever a message arrives on that subscription:
|
98
|
+
|
99
|
+
redis = EM::Hiredis.connect
|
100
|
+
|
101
|
+
puts "Subscribing"
|
102
|
+
redis.pubsub.subscribe("foo") { |msg|
|
103
|
+
p [:sub1, msg]
|
104
|
+
}
|
105
|
+
|
106
|
+
redis.pubsub.psubscribe("f*") { |msg|
|
107
|
+
p [:sub2, msg]
|
108
|
+
}
|
109
|
+
|
110
|
+
EM.add_periodic_timer(1) {
|
111
|
+
redis.publish("foo", "Hello")
|
112
|
+
}
|
113
|
+
|
114
|
+
EM.add_timer(5) {
|
115
|
+
puts "Unsubscribing sub1"
|
116
|
+
redis.pubsub.unsubscribe("foo")
|
117
|
+
}
|
118
|
+
|
119
|
+
It's possible to subscribe to the same channel multiple time and just unsubscribe a single callback using `unsubscribe_proc` or `punsubscribe_proc`.
|
120
|
+
|
121
|
+
## Developing
|
70
122
|
|
71
123
|
Hacking on em-hiredis is pretty simple, make sure you have Bundler installed:
|
72
124
|
|
73
125
|
gem install bundler
|
74
126
|
bundle
|
75
127
|
|
76
|
-
|
128
|
+
In order to run the tests you need to have a local redis server running on port 6379. Run all the tests:
|
77
129
|
|
78
|
-
|
130
|
+
# WARNING: The tests call flushdb on db 9 - this clears all keys!
|
131
|
+
bundle exec rake
|
79
132
|
|
80
133
|
To run an individual test:
|
81
134
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
2
|
+
require 'em-hiredis'
|
3
|
+
|
4
|
+
EM.run {
|
5
|
+
redis = EM::Hiredis.connect
|
6
|
+
|
7
|
+
redis.sadd('aset', 'member').callback {
|
8
|
+
response_deferrable = redis.hget('aset', 'member')
|
9
|
+
response_deferrable.errback { |e|
|
10
|
+
p e # => #<RuntimeError: ERR Operation against a key holding the wrong kind of value>
|
11
|
+
p e.redis_error
|
12
|
+
}
|
13
|
+
}
|
14
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
2
|
+
require 'em-hiredis'
|
3
|
+
|
4
|
+
EM.run {
|
5
|
+
redis = EM::Hiredis.connect
|
6
|
+
|
7
|
+
puts "Subscribing"
|
8
|
+
redis.pubsub.subscribe("foo") { |msg|
|
9
|
+
p [:sub1, msg]
|
10
|
+
}
|
11
|
+
|
12
|
+
redis.pubsub.psubscribe("f*") { |msg|
|
13
|
+
p [:sub2, msg]
|
14
|
+
}
|
15
|
+
|
16
|
+
EM.add_periodic_timer(1) {
|
17
|
+
redis.publish("foo", "Hello")
|
18
|
+
}
|
19
|
+
|
20
|
+
EM.add_timer(5) {
|
21
|
+
puts "Unsubscribing sub1"
|
22
|
+
redis.pubsub.unsubscribe("foo")
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
2
|
+
require 'em-hiredis'
|
3
|
+
|
4
|
+
EM.run {
|
5
|
+
redis = EM::Hiredis.connect
|
6
|
+
|
7
|
+
# If you pass a block to subscribe it will be called whenever a message
|
8
|
+
# is received on this channel
|
9
|
+
redis.pubsub.subscribe('foo') { |message|
|
10
|
+
puts "Block received #{message}"
|
11
|
+
}
|
12
|
+
|
13
|
+
# You can also pass any other object which responds to call if you wish
|
14
|
+
callback = Proc.new { |message|
|
15
|
+
"Proc received #{message}"
|
16
|
+
}
|
17
|
+
df = redis.pubsub.subscribe('foo', callback)
|
18
|
+
|
19
|
+
# All calls return a deferrable
|
20
|
+
df.callback { |reply|
|
21
|
+
p [:subscription_succeeded, reply]
|
22
|
+
}
|
23
|
+
|
24
|
+
# Passing such an object is useful if you want to unsubscribe
|
25
|
+
redis.pubsub.unsubscribe_proc('foo', callback)
|
26
|
+
|
27
|
+
# Or if you want to call a method on a certain object
|
28
|
+
class Thing
|
29
|
+
def receive_message(message)
|
30
|
+
puts "Thing received #{message}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
redis.pubsub.subscribe('bar', Thing.new.method(:receive_message))
|
34
|
+
|
35
|
+
# You can also get all the following raw events:
|
36
|
+
# message pmessage subscribe unsubscribe psubscribe punsubscribe
|
37
|
+
redis.pubsub.on(:message) { |channel, message|
|
38
|
+
p [:message_received, channel, message]
|
39
|
+
}
|
40
|
+
redis.pubsub.on(:unsubscribe) { |channel, remaining_subscriptions|
|
41
|
+
p [:unsubscribe_happened, channel, remaining_subscriptions]
|
42
|
+
}
|
43
|
+
|
44
|
+
EM.add_timer(1) {
|
45
|
+
# You can also unsubscribe completely from a channel
|
46
|
+
redis.pubsub.unsubscribe('foo')
|
47
|
+
|
48
|
+
# Publishing events
|
49
|
+
redis.publish('bar', 'Hello')
|
50
|
+
}
|
51
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
2
|
+
require 'em-hiredis'
|
3
|
+
|
4
|
+
EM.run {
|
5
|
+
# Create two connections, one will be used for subscribing
|
6
|
+
redis = EM::Hiredis.connect
|
7
|
+
pubsub = redis.pubsub
|
8
|
+
|
9
|
+
pubsub.subscribe('bar.0').callback { puts "Subscribed" }
|
10
|
+
pubsub.psubscribe('bar.*')
|
11
|
+
|
12
|
+
pubsub.on(:message) { |channel, message|
|
13
|
+
p [:message, channel, message]
|
14
|
+
}
|
15
|
+
|
16
|
+
pubsub.on(:pmessage) { |key, channel, message|
|
17
|
+
p [:pmessage, key, channel, message]
|
18
|
+
}
|
19
|
+
|
20
|
+
EM.add_periodic_timer(1) {
|
21
|
+
redis.publish("bar.#{rand(2)}", "hello").errback { |e|
|
22
|
+
p [:publisherror, e]
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
data/lib/em-hiredis.rb
CHANGED
@@ -1,11 +1,26 @@
|
|
1
1
|
require 'eventmachine'
|
2
|
-
require 'uri'
|
3
2
|
|
4
3
|
module EventMachine
|
5
4
|
module Hiredis
|
5
|
+
# All em-hiredis errors should descend from EM::Hiredis::Error
|
6
|
+
class Error < RuntimeError; end
|
7
|
+
|
8
|
+
# In the case of error responses from Redis, the RuntimeError returned
|
9
|
+
# by ::Hiredis will be wrapped
|
10
|
+
class RedisError < Error
|
11
|
+
attr_accessor :redis_error
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :reconnect_timeout
|
16
|
+
end
|
17
|
+
self.reconnect_timeout = 0.5
|
18
|
+
|
6
19
|
def self.setup(uri = nil)
|
7
|
-
|
8
|
-
Client.new
|
20
|
+
uri = uri || ENV["REDIS_URL"] || "redis://127.0.0.1:6379/0"
|
21
|
+
client = Client.new
|
22
|
+
client.configure(uri)
|
23
|
+
client
|
9
24
|
end
|
10
25
|
|
11
26
|
def self.connect(uri = nil)
|
@@ -26,9 +41,14 @@ module EventMachine
|
|
26
41
|
log
|
27
42
|
end
|
28
43
|
end
|
44
|
+
|
45
|
+
autoload :Lock, 'em-hiredis/lock'
|
46
|
+
autoload :PersistentLock, 'em-hiredis/persistent_lock'
|
29
47
|
end
|
30
48
|
end
|
31
49
|
|
32
50
|
require 'em-hiredis/event_emitter'
|
33
51
|
require 'em-hiredis/connection'
|
52
|
+
require 'em-hiredis/base_client'
|
34
53
|
require 'em-hiredis/client'
|
54
|
+
require 'em-hiredis/pubsub_client'
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module EventMachine::Hiredis
|
4
|
+
# Emits the following events
|
5
|
+
#
|
6
|
+
# * :connected - on successful connection or reconnection
|
7
|
+
# * :reconnected - on successful reconnection
|
8
|
+
# * :disconnected - no longer connected, when previously in connected state
|
9
|
+
# * :reconnect_failed(failure_number) - a reconnect attempt failed
|
10
|
+
# This event is passed number of failures so far (1,2,3...)
|
11
|
+
# * :monitor
|
12
|
+
#
|
13
|
+
class BaseClient
|
14
|
+
include EventEmitter
|
15
|
+
include EM::Deferrable
|
16
|
+
|
17
|
+
attr_reader :host, :port, :password, :db
|
18
|
+
|
19
|
+
def initialize(host='localhost', port='6379', password=nil, db=nil)
|
20
|
+
@host, @port, @password, @db = host, port, password, db
|
21
|
+
@defs = []
|
22
|
+
@command_queue = []
|
23
|
+
|
24
|
+
@closing_connection = false
|
25
|
+
@reconnect_failed_count = 0
|
26
|
+
@reconnect_timer = nil
|
27
|
+
@failed = false
|
28
|
+
|
29
|
+
self.on(:failed) {
|
30
|
+
@failed = true
|
31
|
+
@command_queue.each do |df, _, _|
|
32
|
+
df.fail(Error.new("Redis connection in failed state"))
|
33
|
+
end
|
34
|
+
@command_queue = []
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Configure the redis connection to use
|
39
|
+
#
|
40
|
+
# In usual operation, the uri should be passed to initialize. This method
|
41
|
+
# is useful for example when failing over to a slave connection at runtime
|
42
|
+
#
|
43
|
+
def configure(uri_string)
|
44
|
+
uri = URI(uri_string)
|
45
|
+
@host = uri.host
|
46
|
+
@port = uri.port
|
47
|
+
@password = uri.password
|
48
|
+
path = uri.path[1..-1]
|
49
|
+
@db = path.to_i # Empty path => 0
|
50
|
+
end
|
51
|
+
|
52
|
+
def connect
|
53
|
+
@connection = EM.connect(@host, @port, Connection, @host, @port)
|
54
|
+
|
55
|
+
@connection.on(:closed) do
|
56
|
+
if @connected
|
57
|
+
@defs.each { |d| d.fail(Error.new("Redis disconnected")) }
|
58
|
+
@defs = []
|
59
|
+
@deferred_status = nil
|
60
|
+
@connected = false
|
61
|
+
unless @closing_connection
|
62
|
+
# Next tick avoids reconnecting after for example EM.stop
|
63
|
+
EM.next_tick { reconnect }
|
64
|
+
end
|
65
|
+
emit(:disconnected)
|
66
|
+
EM::Hiredis.logger.info("#{@connection} Disconnected")
|
67
|
+
else
|
68
|
+
unless @closing_connection
|
69
|
+
@reconnect_failed_count += 1
|
70
|
+
@reconnect_timer = EM.add_timer(EM::Hiredis.reconnect_timeout) {
|
71
|
+
@reconnect_timer = nil
|
72
|
+
reconnect
|
73
|
+
}
|
74
|
+
emit(:reconnect_failed, @reconnect_failed_count)
|
75
|
+
EM::Hiredis.logger.info("#{@connection} Reconnect failed")
|
76
|
+
|
77
|
+
if @reconnect_failed_count >= 4
|
78
|
+
emit(:failed)
|
79
|
+
self.fail(Error.new("Could not connect after 4 attempts"))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
@connection.on(:connected) do
|
86
|
+
@connected = true
|
87
|
+
@reconnect_failed_count = 0
|
88
|
+
@failed = false
|
89
|
+
|
90
|
+
select(@db) unless @db == 0
|
91
|
+
auth(@password) if @password
|
92
|
+
|
93
|
+
@command_queue.each do |df, command, args|
|
94
|
+
@connection.send_command(command, args)
|
95
|
+
@defs.push(df)
|
96
|
+
end
|
97
|
+
@command_queue = []
|
98
|
+
|
99
|
+
emit(:connected)
|
100
|
+
EM::Hiredis.logger.info("#{@connection} Connected")
|
101
|
+
succeed
|
102
|
+
|
103
|
+
if @reconnecting
|
104
|
+
@reconnecting = false
|
105
|
+
emit(:reconnected)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
@connection.on(:message) do |reply|
|
110
|
+
if RuntimeError === reply
|
111
|
+
raise "Replies out of sync: #{reply.inspect}" if @defs.empty?
|
112
|
+
deferred = @defs.shift
|
113
|
+
error = RedisError.new("Error reply from redis (wrapped in redis_error)")
|
114
|
+
error.redis_error = reply
|
115
|
+
deferred.fail(error) if deferred
|
116
|
+
else
|
117
|
+
handle_reply(reply)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
@connected = false
|
122
|
+
@reconnecting = false
|
123
|
+
|
124
|
+
return self
|
125
|
+
end
|
126
|
+
|
127
|
+
# Indicates that commands have been sent to redis but a reply has not yet
|
128
|
+
# been received
|
129
|
+
#
|
130
|
+
# This can be useful for example to avoid stopping the
|
131
|
+
# eventmachine reactor while there are outstanding commands
|
132
|
+
#
|
133
|
+
def pending_commands?
|
134
|
+
@connected && @defs.size > 0
|
135
|
+
end
|
136
|
+
|
137
|
+
def connected?
|
138
|
+
@connected
|
139
|
+
end
|
140
|
+
|
141
|
+
def select(db, &blk)
|
142
|
+
@db = db
|
143
|
+
method_missing(:select, db, &blk)
|
144
|
+
end
|
145
|
+
|
146
|
+
def auth(password, &blk)
|
147
|
+
@password = password
|
148
|
+
method_missing(:auth, password, &blk)
|
149
|
+
end
|
150
|
+
|
151
|
+
def close_connection
|
152
|
+
EM.cancel_timer(@reconnect_timer) if @reconnect_timer
|
153
|
+
@closing_connection = true
|
154
|
+
@connection.close_connection_after_writing
|
155
|
+
end
|
156
|
+
|
157
|
+
def reconnect_connection
|
158
|
+
EM.cancel_timer(@reconnect_timer) if @reconnect_timer
|
159
|
+
reconnect
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def method_missing(sym, *args)
|
165
|
+
deferred = EM::DefaultDeferrable.new
|
166
|
+
# Shortcut for defining the callback case with just a block
|
167
|
+
deferred.callback { |result| yield(result) } if block_given?
|
168
|
+
|
169
|
+
if @connected
|
170
|
+
@connection.send_command(sym, args)
|
171
|
+
@defs.push(deferred)
|
172
|
+
elsif @failed
|
173
|
+
deferred.fail(Error.new("Redis connection in failed state"))
|
174
|
+
else
|
175
|
+
@command_queue << [deferred, sym, args]
|
176
|
+
end
|
177
|
+
|
178
|
+
deferred
|
179
|
+
end
|
180
|
+
|
181
|
+
def reconnect
|
182
|
+
@reconnecting = true
|
183
|
+
@connection.reconnect @host, @port
|
184
|
+
EM::Hiredis.logger.info("#{@connection} Reconnecting")
|
185
|
+
end
|
186
|
+
|
187
|
+
def handle_reply(reply)
|
188
|
+
if @defs.empty?
|
189
|
+
if @monitoring
|
190
|
+
emit(:monitor, reply)
|
191
|
+
else
|
192
|
+
raise "Replies out of sync: #{reply.inspect}"
|
193
|
+
end
|
194
|
+
else
|
195
|
+
deferred = @defs.shift
|
196
|
+
deferred.succeed(reply) if deferred
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|