em-hiredis 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -1
- data/em-hiredis.gemspec +1 -1
- data/lib/em-hiredis/base_client.rb +43 -1
- data/lib/em-hiredis/pubsub_client.rb +16 -1
- data/lib/em-hiredis/version.rb +1 -1
- data/spec/inactivity_check_spec.rb +66 -0
- data/spec/support/inprocess_redis_mock.rb +83 -0
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9403dec51c7c192ce9f40a3850f1bec4fc8adb6
|
4
|
+
data.tar.gz: bec13c8424a5ac75854dceb0d17144ac16c65b93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 632d2d5f5d0495f52d7638622fa2fc5ea4149872100da8eefbcaee6d6655fd5de6e4d5bed0b4dd2c441b357e648a1646ab65c858770b7dd1c8370fa517358b17
|
7
|
+
data.tar.gz: c4696c3fd994ff092efd11f35da929d5b33704cd7ba6d67c0b470f7e458bac9d80720d8e15ec12816cfc687db10081136fae08d35133cbe965d13ce7bd56be34
|
data/README.md
CHANGED
@@ -103,7 +103,7 @@ If you pass a block to `subscribe` or `psubscribe`, the passed block will be cal
|
|
103
103
|
p [:sub1, msg]
|
104
104
|
}
|
105
105
|
|
106
|
-
redis.pubsub.psubscribe("f*") { |msg|
|
106
|
+
redis.pubsub.psubscribe("f*") { |channel, msg|
|
107
107
|
p [:sub2, msg]
|
108
108
|
}
|
109
109
|
|
@@ -147,6 +147,23 @@ As a final convenience, it is possible to load all lua scripts from a directory
|
|
147
147
|
|
148
148
|
For examples see `examples/lua.rb` or `lib/em-hiredis/lock_lua`.
|
149
149
|
|
150
|
+
## Inactivity checks
|
151
|
+
|
152
|
+
Sometimes a network connection may hang in ways which are difficult to detect or involve very long timeouts before they can be detected from the application layer. This is especially true of Redis Pubsub connections, as they are not request-response driven. It is very difficult for a listening client to descern between a hung connection and a server with nothing to say.
|
153
|
+
|
154
|
+
To start an application layer ping-pong mechanism for testing connection liveness, call the following at any time on a client:
|
155
|
+
|
156
|
+
redis.configure_inactivity_check(5, 3)
|
157
|
+
|
158
|
+
This configures a `PING` command to be sent if 5 seconds elapse without receiving any data from the server, and a reconnection to be triggered if a futher 3 seconds elapse after the `PING` is submitted.
|
159
|
+
|
160
|
+
This configuration is per client, you may choose different value for clients with different expected traffic patterns, or activate it on some and not at all on others.
|
161
|
+
|
162
|
+
### PING and Pubsub
|
163
|
+
|
164
|
+
Because the Redis Pubsub protocol limits the set of valid commands on a connection once it is in "Pubsub" mode, `PING` is not supported in this case (though it may be in future, see https://github.com/antirez/redis/issues/420). In order to create some valid request-response traffic on the connection, a Pubsub connection will issue `SUBSCRIBE "__em-hiredis-ping"`, followed by a corresponding `UNSUBSCRIBE` immediately on success of the subscribe.
|
165
|
+
While less than ideal, this is the case where an application layer inactivity check is most valuable, and so the trade off is reasonable until `PING` is supported correctly on Pubsub connections.
|
166
|
+
|
150
167
|
## Developing
|
151
168
|
|
152
169
|
You need bundler and a local redis server running on port 6379 to run the test suite.
|
data/em-hiredis.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.description = %q{Eventmachine redis client using hiredis native parser}
|
14
14
|
|
15
15
|
s.add_dependency 'eventmachine', '~> 1.0'
|
16
|
-
s.add_dependency 'hiredis', '~> 0.
|
16
|
+
s.add_dependency 'hiredis', '~> 0.6.0'
|
17
17
|
|
18
18
|
s.add_development_dependency 'em-spec', '~> 0.2.5'
|
19
19
|
s.add_development_dependency 'rspec', '~> 2.6.0'
|
@@ -25,6 +25,8 @@ module EventMachine::Hiredis
|
|
25
25
|
@reconnect_timer = nil
|
26
26
|
@failed = false
|
27
27
|
|
28
|
+
@inactive_seconds = 0
|
29
|
+
|
28
30
|
self.on(:failed) {
|
29
31
|
@failed = true
|
30
32
|
@command_queue.each do |df, _, _|
|
@@ -72,6 +74,7 @@ module EventMachine::Hiredis
|
|
72
74
|
@connection = EM.connect(@host, @port, Connection, @host, @port)
|
73
75
|
|
74
76
|
@connection.on(:closed) do
|
77
|
+
cancel_inactivity_checks
|
75
78
|
if @connected
|
76
79
|
@defs.each { |d| d.fail(Error.new("Redis disconnected")) }
|
77
80
|
@defs = []
|
@@ -106,8 +109,8 @@ module EventMachine::Hiredis
|
|
106
109
|
@reconnect_failed_count = 0
|
107
110
|
@failed = false
|
108
111
|
|
109
|
-
select(@db) unless @db == 0
|
110
112
|
auth(@password) if @password
|
113
|
+
select(@db) unless @db == 0
|
111
114
|
|
112
115
|
@command_queue.each do |df, command, args|
|
113
116
|
@connection.send_command(command, args)
|
@@ -115,6 +118,8 @@ module EventMachine::Hiredis
|
|
115
118
|
end
|
116
119
|
@command_queue = []
|
117
120
|
|
121
|
+
schedule_inactivity_checks
|
122
|
+
|
118
123
|
emit(:connected)
|
119
124
|
EM::Hiredis.logger.info("#{@connection} Connected")
|
120
125
|
succeed
|
@@ -133,6 +138,7 @@ module EventMachine::Hiredis
|
|
133
138
|
error.redis_error = reply
|
134
139
|
deferred.fail(error) if deferred
|
135
140
|
else
|
141
|
+
@inactive_seconds = 0
|
136
142
|
handle_reply(reply)
|
137
143
|
end
|
138
144
|
end
|
@@ -181,6 +187,21 @@ module EventMachine::Hiredis
|
|
181
187
|
reconnect
|
182
188
|
end
|
183
189
|
|
190
|
+
# Starts an inactivity checker which will ping redis if nothing has been
|
191
|
+
# heard on the connection for `trigger_secs` seconds and forces a reconnect
|
192
|
+
# after a further `response_timeout` seconds if we still don't hear anything.
|
193
|
+
def configure_inactivity_check(trigger_secs, response_timeout)
|
194
|
+
raise ArgumentError('trigger_secs must be > 0') unless trigger_secs.to_i > 0
|
195
|
+
raise ArgumentError('response_timeout must be > 0') unless response_timeout.to_i > 0
|
196
|
+
|
197
|
+
@inactivity_trigger_secs = trigger_secs.to_i
|
198
|
+
@inactivity_response_timeout = response_timeout.to_i
|
199
|
+
|
200
|
+
# Start the inactivity check now only if we're already conected, otherwise
|
201
|
+
# the connected event will schedule it.
|
202
|
+
schedule_inactivity_checks if @connected
|
203
|
+
end
|
204
|
+
|
184
205
|
private
|
185
206
|
|
186
207
|
def method_missing(sym, *args)
|
@@ -206,6 +227,27 @@ module EventMachine::Hiredis
|
|
206
227
|
EM::Hiredis.logger.info("#{@connection} Reconnecting")
|
207
228
|
end
|
208
229
|
|
230
|
+
def cancel_inactivity_checks
|
231
|
+
EM.cancel_timer(@inactivity_timer) if @inactivity_timer
|
232
|
+
@inactivity_timer = nil
|
233
|
+
end
|
234
|
+
|
235
|
+
def schedule_inactivity_checks
|
236
|
+
if @inactivity_trigger_secs
|
237
|
+
@inactive_seconds = 0
|
238
|
+
@inactivity_timer = EM.add_periodic_timer(1) {
|
239
|
+
@inactive_seconds += 1
|
240
|
+
if @inactive_seconds > @inactivity_trigger_secs + @inactivity_response_timeout
|
241
|
+
EM::Hiredis.logger.error "#{@connection} No response to ping, triggering reconnect"
|
242
|
+
reconnect!
|
243
|
+
elsif @inactive_seconds > @inactivity_trigger_secs
|
244
|
+
EM::Hiredis.logger.debug "#{@connection} Connection inactive, triggering ping"
|
245
|
+
ping
|
246
|
+
end
|
247
|
+
}
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
209
251
|
def handle_reply(reply)
|
210
252
|
if @defs.empty?
|
211
253
|
if @monitoring
|
@@ -2,6 +2,8 @@ module EventMachine::Hiredis
|
|
2
2
|
class PubsubClient < BaseClient
|
3
3
|
PUBSUB_MESSAGES = %w{message pmessage subscribe unsubscribe psubscribe punsubscribe}.freeze
|
4
4
|
|
5
|
+
PING_CHANNEL = '__em-hiredis-ping'
|
6
|
+
|
5
7
|
def initialize(host='localhost', port='6379', password=nil, db=nil)
|
6
8
|
@subs, @psubs = [], []
|
7
9
|
@pubsub_defs = Hash.new { |h,k| h[k] = [] }
|
@@ -125,7 +127,20 @@ module EventMachine::Hiredis
|
|
125
127
|
end
|
126
128
|
return df
|
127
129
|
end
|
128
|
-
|
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
|
+
|
129
144
|
private
|
130
145
|
|
131
146
|
# Send a command to redis without adding a deferrable for it. This is
|
data/lib/em-hiredis/version.rb
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/inprocess_redis_mock'
|
3
|
+
|
4
|
+
def connect_mock(timeout = 10, url = "redis://localhost:6381", server = nil, &blk)
|
5
|
+
em(timeout) do
|
6
|
+
IRedisMock.start
|
7
|
+
redis = EventMachine::Hiredis.connect(url)
|
8
|
+
blk.call(redis)
|
9
|
+
IRedisMock.stop
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe EM::Hiredis::BaseClient do
|
14
|
+
it "should ping after activity timeout reached" do
|
15
|
+
connect_mock do |redis|
|
16
|
+
redis.configure_inactivity_check(2, 1)
|
17
|
+
EM.add_timer(4) {
|
18
|
+
IRedisMock.received.should include("ping")
|
19
|
+
done
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should not ping before activity timeout reached" do
|
25
|
+
connect_mock do |redis|
|
26
|
+
redis.configure_inactivity_check(3, 1)
|
27
|
+
EM.add_timer(2) {
|
28
|
+
IRedisMock.received.should_not include("ping")
|
29
|
+
done
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should ping after timeout reached even though command has been sent (no response)" do
|
35
|
+
connect_mock do |redis|
|
36
|
+
redis.configure_inactivity_check(2, 1)
|
37
|
+
IRedisMock.pause # no responses from now on
|
38
|
+
|
39
|
+
EM.add_timer(1.5) {
|
40
|
+
redis.get "test"
|
41
|
+
}
|
42
|
+
|
43
|
+
EM.add_timer(4) {
|
44
|
+
IRedisMock.received.should include("ping")
|
45
|
+
done
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should trigger a reconnect when there's no response to ping" do
|
51
|
+
connect_mock do |redis|
|
52
|
+
redis.configure_inactivity_check(2, 1)
|
53
|
+
IRedisMock.pause # no responses from now on
|
54
|
+
|
55
|
+
EM.add_timer(1.5) {
|
56
|
+
redis.get "test"
|
57
|
+
}
|
58
|
+
|
59
|
+
EM.add_timer(5) {
|
60
|
+
IRedisMock.received.should include("disconnect")
|
61
|
+
done
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module IRedisMock
|
2
|
+
def self.start(replies = {})
|
3
|
+
@sig = EventMachine::start_server("127.0.0.1", 6381, Connection)
|
4
|
+
@received = []
|
5
|
+
@replies = replies
|
6
|
+
@paused = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.stop
|
10
|
+
EventMachine::stop_server(@sig)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.received
|
14
|
+
@received ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.pause
|
18
|
+
@paused = true
|
19
|
+
end
|
20
|
+
def self.unpause
|
21
|
+
@paused = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.paused
|
25
|
+
@paused
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.replies
|
29
|
+
@replies
|
30
|
+
end
|
31
|
+
|
32
|
+
class Connection < EventMachine::Connection
|
33
|
+
def initialize
|
34
|
+
@data = ""
|
35
|
+
@parts = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def unbind
|
39
|
+
IRedisMock.received << 'disconnect'
|
40
|
+
end
|
41
|
+
|
42
|
+
def receive_data(data)
|
43
|
+
@data << data
|
44
|
+
|
45
|
+
while (idx = @data.index("\r\n"))
|
46
|
+
@parts << @data[0..idx-1]
|
47
|
+
@data = @data[idx+2..-1]
|
48
|
+
end
|
49
|
+
|
50
|
+
while @parts.length > 0
|
51
|
+
throw "commands out of sync" unless @parts[0][0] == '*'
|
52
|
+
|
53
|
+
num_parts = @parts[0][1..-1].to_i * 2 + 1
|
54
|
+
return if @parts.length < num_parts
|
55
|
+
|
56
|
+
command_parts = @parts[0..num_parts]
|
57
|
+
@parts = @parts[num_parts..-1]
|
58
|
+
|
59
|
+
# Discard length declarations
|
60
|
+
command_line =
|
61
|
+
command_parts
|
62
|
+
.reject { |p| p[0] == '*' || p[0] == '$' }
|
63
|
+
.join ' '
|
64
|
+
|
65
|
+
if IRedisMock.replies.member?(command_line)
|
66
|
+
reply = IRedisMock.replies[command_line]
|
67
|
+
else
|
68
|
+
reply = "+OK"
|
69
|
+
end
|
70
|
+
|
71
|
+
# p "[#{command_line}] => [#{reply}]"
|
72
|
+
|
73
|
+
IRedisMock.received << command_line
|
74
|
+
|
75
|
+
if IRedisMock.paused
|
76
|
+
# puts "Paused, therefore not sending [#{reply}]"
|
77
|
+
else
|
78
|
+
send_data "#{reply}\r\n"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-hiredis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martyn Loughran
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: eventmachine
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.6.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 0.6.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: em-spec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,12 +114,14 @@ files:
|
|
114
114
|
- lib/em-hiredis/version.rb
|
115
115
|
- spec/base_client_spec.rb
|
116
116
|
- spec/connection_spec.rb
|
117
|
+
- spec/inactivity_check_spec.rb
|
117
118
|
- spec/live_redis_protocol_spec.rb
|
118
119
|
- spec/lock_spec.rb
|
119
120
|
- spec/pubsub_spec.rb
|
120
121
|
- spec/redis_commands_spec.rb
|
121
122
|
- spec/spec_helper.rb
|
122
123
|
- spec/support/connection_helper.rb
|
124
|
+
- spec/support/inprocess_redis_mock.rb
|
123
125
|
- spec/support/redis_mock.rb
|
124
126
|
- spec/url_param_spec.rb
|
125
127
|
homepage: http://github.com/mloughran/em-hiredis
|
@@ -141,18 +143,20 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
143
|
version: '0'
|
142
144
|
requirements: []
|
143
145
|
rubyforge_project: em-hiredis
|
144
|
-
rubygems_version: 2.
|
146
|
+
rubygems_version: 2.5.1
|
145
147
|
signing_key:
|
146
148
|
specification_version: 4
|
147
149
|
summary: Eventmachine redis client
|
148
150
|
test_files:
|
149
151
|
- spec/base_client_spec.rb
|
150
152
|
- spec/connection_spec.rb
|
153
|
+
- spec/inactivity_check_spec.rb
|
151
154
|
- spec/live_redis_protocol_spec.rb
|
152
155
|
- spec/lock_spec.rb
|
153
156
|
- spec/pubsub_spec.rb
|
154
157
|
- spec/redis_commands_spec.rb
|
155
158
|
- spec/spec_helper.rb
|
156
159
|
- spec/support/connection_helper.rb
|
160
|
+
- spec/support/inprocess_redis_mock.rb
|
157
161
|
- spec/support/redis_mock.rb
|
158
162
|
- spec/url_param_spec.rb
|