gilmour-em-hiredis 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1c7d2fb57fb7ecea14121caf7c63409e225f5a3e
4
+ data.tar.gz: be6da3ecb311f550675ecdd054a4b1aa8d5ea435
5
+ SHA512:
6
+ metadata.gz: e1623301124a9b3551febaf10db4c58273022d8a39006467458113c74fe9fa0d09c900ee2685f17e014ea755c9e7715aa54edf2f2b5cca7e04579ae038abaa61
7
+ data.tar.gz: b4d54cb1499261e94f0814886a3a40147c2a1dd9fcf6234023eb7dccf4a08e3becedc83052a349326cd496c9e5a76e5153d534fa7a542c112e0f9fd01903af78
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .DS_Store
6
+ *.swp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 (2014-06-30)
4
+
5
+ [NEW] Lua script support (see README for details).
6
+
7
+ [NEW] `Client#reconnect!` method which disconnects, reconfigures, and reconnects.
8
+
9
+ [CHANGED] Locking abstraction re-implemented using lua (safer and simpler) [mdpye].
10
+
11
+ [CHANGED] Hiredis dependency updated to 0.5.x
12
+
13
+ ## 0.2.1 (2013-04-22)
14
+
15
+ [NEW] Support for connecting to redis on a unix socket.
16
+
17
+ [CHANGED] Redis error reply message now used as message for RedisError.
18
+
19
+ ## 0.2.0 (2013-04-05)
20
+
21
+ [NEW] Richer interface for pubsub (accessible via `client.pubsub`). See example in `examples/pubsub.rb`.
22
+
23
+ [NEW] Better failure handling:
24
+
25
+ * Clients now emit the following events: connected, reconnected, disconnected, reconnect_failed (passes the number of consecutive failures)
26
+ * Client is considered failed after 4 consecutive failures
27
+ * Fails all queued commands when client failed
28
+ * Can now reconfiure and reconnect an exising client
29
+ * Reconnect timeout can be configured (defaults to 0.5s)
30
+
31
+ [NEW] Added `EM::Hiredis::Lock` and `EM::Hiredis::PersistentLock`
32
+
33
+ [CHANGE] When a redis command fails, the errback is now always passed an `EM::Hiredis::Error`.
34
+
35
+ [FIX] Fixed info parsing for Redis 2.6
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENCE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2011 by Martyn Loughran
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # em-hiredis
2
+ ## Note: This is to be used mainly as a dependency for the [Gilmour gem](https://github.com/adityagodbole/gilmour). This will be deprecated when [this pull request](https://github.com/mloughran/em-hiredis/pull/38) is merged.
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
18
+
19
+ Connect to redis:
20
+
21
+ require 'em-hiredis'
22
+ redis = EM::Hiredis.connect
23
+
24
+ Or, connect to redis with a redis URL (for a different host, port, password, DB)
25
+
26
+ redis = EM::Hiredis.connect("redis://:secretpassword@example.com:9000/4")
27
+
28
+ Commands may be sent immediately. Any commands sent while connecting to redis will be queued.
29
+
30
+ All redis commands are available without any remapping of names, and return a deferrable
31
+
32
+ redis.set('foo', 'bar').callback {
33
+ redis.get('foo').callback { |value|
34
+ p [:returned, value]
35
+ }
36
+ }
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
+
48
+ As a shortcut, if you're only interested in binding to the success case you can simply provide a block to any command
49
+
50
+ redis.get('foo') { |value|
51
+ p [:returned, value]
52
+ }
53
+
54
+ ## Understanding the state of the connection
55
+
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.
57
+
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=`).
59
+
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).
61
+
62
+ ## Pubsub
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 pubsub interface
71
+
72
+ Basically just bind to `:message` and `:pmessage` events:
73
+
74
+ # Create two connections, one will be used for subscribing
75
+ redis = EM::Hiredis.connect
76
+ pubsub = redis.pubsub_client
77
+
78
+ pubsub.subscribe('bar.0').callback { puts "Subscribed" }
79
+ pubsub.psubscribe('bar.*')
80
+
81
+ pubsub.on(:message) { |channel, message|
82
+ p [:message, channel, message]
83
+ }
84
+
85
+ pubsub.on(:pmessage) { |key, channel, message|
86
+ p [:pmessage, key, channel, message]
87
+ }
88
+
89
+ EM.add_periodic_timer(1) {
90
+ redis.publish("bar.#{rand(2)}", "hello").errback { |e|
91
+ p [:publisherror, e]
92
+ }
93
+ }
94
+
95
+ ### Richer pubsub interface
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_client.subscribe("foo") { |msg|
103
+ p [:sub1, msg]
104
+ }
105
+
106
+ redis.pubsub_client.psubscribe("f*") { |channel, 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_client.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
+ ## Lua
122
+
123
+ You can of course call EVAL or EVALSHA directly; the following is a higher-level API.
124
+
125
+ Registering a named command on a redis client defines a ruby method with the given name on the client:
126
+
127
+ redis.register_script(:multiply, <<-END)
128
+ return redis.call('get', KEYS[1]) * ARGV[1]
129
+ END
130
+
131
+ The method can be called in a very similar way to any other redis command; the only difference is that the first argument must be an array of keys, and the second (optional) an array of values.
132
+
133
+ # Multiplies the value at key foo by 2
134
+ redis.multiply(['foo'], [2]).callback { ... }
135
+
136
+ Lua commands are submitted to redis using EVALSHA for efficiency. If redis replies with a NOSCRIPT error, the command is automatically re-submitted with EVAL; this is totally transparent to your code and the intermediate 'failure' will not be passed to your errback.
137
+
138
+ You may register scripts globally, in which case they will be available to all clients:
139
+
140
+ EM::Hiredis::Client.register_script(:multiply, <<-END)
141
+ return redis.call('get', KEYS[1]) * ARGV[1]
142
+ END
143
+
144
+ As a final convenience, it is possible to load all lua scripts from a directory automatically. All `.lua` files in the directory will be registered, and named according to filename (so a file called `sum.lua` becomes available as `redis.sum(...)`).
145
+
146
+ EM::Hiredis::Client.load_scripts_from('./lua_scripts')
147
+
148
+ For examples see `examples/lua.rb` or `lib/em-hiredis/lock_lua`.
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_timeout(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
+
167
+ ## Developing
168
+
169
+ You need bundler and a local redis server running on port 6379 to run the test suite.
170
+
171
+ # WARNING: The tests call flushdb on db 9 - this clears all keys!
172
+ bundle exec rake
173
+
174
+ Run an individual spec:
175
+
176
+ bundle exec rspec spec/redis_commands_spec.rb
177
+
178
+ Many thanks to the em-redis gem for getting this gem bootstrapped with some tests.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc 'Default: run specs.'
7
+ task :default => :spec
8
+
9
+ desc "Run specs"
10
+ RSpec::Core::RakeTask.new do |t|
11
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "em-hiredis/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "gilmour-em-hiredis"
7
+ s.version = EventMachine::Hiredis::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Martyn Loughran", "Aditya Godbole"]
10
+ s.email = ["aa@gdbl.me"]
11
+ s.homepage = "http://github.com/adityagodbole/em-hiredis"
12
+ s.summary = %q{Eventmachine redis client dependency for gilmour}
13
+ s.description = %q{Eventmachine redis client using hiredis native parser}
14
+
15
+ s.add_dependency 'eventmachine', '~> 1.0'
16
+ s.add_dependency 'hiredis', '~> 0.5.0'
17
+
18
+ s.add_development_dependency 'em-spec', '~> 0.2.5'
19
+ s.add_development_dependency 'rspec', '~> 2.6.0'
20
+ s.add_development_dependency 'rake'
21
+
22
+ s.rubyforge_project = "em-hiredis"
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
+ s.require_paths = ["lib"]
28
+ end
@@ -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,4 @@
1
+ local a = redis.call('get', KEYS[1])
2
+ local b = redis.call('get', KEYS[2])
3
+
4
+ return a + b
@@ -0,0 +1,35 @@
1
+ $:.unshift(File.expand_path('../../lib', __FILE__))
2
+ require 'em-hiredis'
3
+
4
+ EM.run do
5
+ scripts_dir = File.expand_path("../lua", __FILE__)
6
+ EM::Hiredis::Client.load_scripts_from(scripts_dir)
7
+
8
+ redis = EM::Hiredis.connect
9
+
10
+ redis.register_script(:multiply, <<-END)
11
+ return redis.call('get', KEYS[1]) * ARGV[1]
12
+ END
13
+
14
+ redis.set('foo', 42).callback {
15
+ redis.set('bar', 8).callback {
16
+
17
+ # Multiply is defined above.
18
+ # It is passed one key and one argument.
19
+ redis.multiply(['foo'], [2]).callback { |v|
20
+ puts "Multiply returned: #{v}"
21
+ }.errback { |e|
22
+ puts "Multiply error: #{e}"
23
+ }
24
+
25
+ # Sum is a lua script defined in sum.lua.
26
+ # It is passed two keys.
27
+ redis.sum(['foo', 'bar']).callback { |sum|
28
+ puts "Sum returned: #{sum}"
29
+ }.errback { |e|
30
+ puts "Sum error: #{e}"
31
+ }
32
+
33
+ }
34
+ }
35
+ end
@@ -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
+ }