em-hiredis 0.2.1 → 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/CHANGELOG.md +10 -0
- data/README.md +33 -9
- data/em-hiredis.gemspec +2 -1
- data/examples/lua/sum.lua +4 -0
- data/examples/lua_example.rb +35 -0
- data/lib/em-hiredis/base_client.rb +20 -4
- data/lib/em-hiredis/client.rb +41 -0
- data/lib/em-hiredis/lock.rb +50 -68
- 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 +9 -5
- data/lib/em-hiredis/version.rb +1 -1
- data/spec/base_client_spec.rb +1 -1
- data/spec/lock_spec.rb +137 -0
- metadata +39 -35
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 48fe707c4b77c8666f0466d1a33f342222f01341
|
4
|
+
data.tar.gz: f9119b5ab7ad4af24a98f8392556f85813c1baf0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0039ac23a12e4027902ea932b5f518be4a4ddfcf553a7cdb30103a46d465f35b2954be053135e26d90eea4c2d2f922ff550925ce89a14191a0d800d1b05b41ca
|
7
|
+
data.tar.gz: 3c5a950d9abbde1c1b1ef55a359f7804d9128b9f6527a43b5126423fc70d7c0a809fedd7dbe482a430db8f177cf2eb1f25c4013414b3fd78ee99c3c6b22d24a5
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
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
|
+
|
3
13
|
## 0.2.1 (2013-04-22)
|
4
14
|
|
5
15
|
[NEW] Support for connecting to redis on a unix socket.
|
data/README.md
CHANGED
@@ -67,7 +67,7 @@ The regular `EM::Hiredis::Client` no longer understands pubsub messages - this l
|
|
67
67
|
|
68
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
69
|
|
70
|
-
### Close to the metal
|
70
|
+
### Close to the metal pubsub interface
|
71
71
|
|
72
72
|
Basically just bind to `:message` and `:pmessage` events:
|
73
73
|
|
@@ -92,7 +92,7 @@ Basically just bind to `:message` and `:pmessage` events:
|
|
92
92
|
}
|
93
93
|
}
|
94
94
|
|
95
|
-
### Richer interface
|
95
|
+
### Richer pubsub interface
|
96
96
|
|
97
97
|
If you pass a block to `subscribe` or `psubscribe`, the passed block will be called whenever a message arrives on that subscription:
|
98
98
|
|
@@ -118,19 +118,43 @@ If you pass a block to `subscribe` or `psubscribe`, the passed block will be cal
|
|
118
118
|
|
119
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
120
|
|
121
|
-
##
|
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 { ... }
|
122
135
|
|
123
|
-
|
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.
|
124
137
|
|
125
|
-
|
126
|
-
|
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
|
+
## Developing
|
127
151
|
|
128
|
-
|
152
|
+
You need bundler and a local redis server running on port 6379 to run the test suite.
|
129
153
|
|
130
154
|
# WARNING: The tests call flushdb on db 9 - this clears all keys!
|
131
|
-
bundle exec rake
|
155
|
+
bundle exec rake
|
132
156
|
|
133
|
-
|
157
|
+
Run an individual spec:
|
134
158
|
|
135
159
|
bundle exec rspec spec/redis_commands_spec.rb
|
136
160
|
|
data/em-hiredis.gemspec
CHANGED
@@ -12,7 +12,8 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.summary = %q{Eventmachine redis client}
|
13
13
|
s.description = %q{Eventmachine redis client using hiredis native parser}
|
14
14
|
|
15
|
-
s.add_dependency '
|
15
|
+
s.add_dependency 'eventmachine', '~> 1.0'
|
16
|
+
s.add_dependency 'hiredis', '~> 0.5.0'
|
16
17
|
|
17
18
|
s.add_development_dependency 'em-spec', '~> 0.2.5'
|
18
19
|
s.add_development_dependency 'rspec', '~> 2.6.0'
|
@@ -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
|
@@ -21,7 +21,6 @@ module EventMachine::Hiredis
|
|
21
21
|
@defs = []
|
22
22
|
@command_queue = []
|
23
23
|
|
24
|
-
@closing_connection = false
|
25
24
|
@reconnect_failed_count = 0
|
26
25
|
@reconnect_timer = nil
|
27
26
|
@failed = false
|
@@ -55,7 +54,21 @@ module EventMachine::Hiredis
|
|
55
54
|
end
|
56
55
|
end
|
57
56
|
|
57
|
+
# Disconnect then reconnect the redis connection.
|
58
|
+
#
|
59
|
+
# Pass optional uri - e.g. to connect to a different redis server.
|
60
|
+
# Any pending redis commands will be failed, but during the reconnection
|
61
|
+
# new commands will be queued and sent after connected.
|
62
|
+
#
|
63
|
+
def reconnect!(new_uri = nil)
|
64
|
+
@connection.close_connection
|
65
|
+
configure(new_uri) if new_uri
|
66
|
+
@auto_reconnect = true
|
67
|
+
EM.next_tick { reconnect_connection }
|
68
|
+
end
|
69
|
+
|
58
70
|
def connect
|
71
|
+
@auto_reconnect = true
|
59
72
|
@connection = EM.connect(@host, @port, Connection, @host, @port)
|
60
73
|
|
61
74
|
@connection.on(:closed) do
|
@@ -64,14 +77,14 @@ module EventMachine::Hiredis
|
|
64
77
|
@defs = []
|
65
78
|
@deferred_status = nil
|
66
79
|
@connected = false
|
67
|
-
|
80
|
+
if @auto_reconnect
|
68
81
|
# Next tick avoids reconnecting after for example EM.stop
|
69
82
|
EM.next_tick { reconnect }
|
70
83
|
end
|
71
84
|
emit(:disconnected)
|
72
85
|
EM::Hiredis.logger.info("#{@connection} Disconnected")
|
73
86
|
else
|
74
|
-
|
87
|
+
if @auto_reconnect
|
75
88
|
@reconnect_failed_count += 1
|
76
89
|
@reconnect_timer = EM.add_timer(EM::Hiredis.reconnect_timeout) {
|
77
90
|
@reconnect_timer = nil
|
@@ -156,11 +169,14 @@ module EventMachine::Hiredis
|
|
156
169
|
|
157
170
|
def close_connection
|
158
171
|
EM.cancel_timer(@reconnect_timer) if @reconnect_timer
|
159
|
-
@
|
172
|
+
@auto_reconnect = false
|
160
173
|
@connection.close_connection_after_writing
|
161
174
|
end
|
162
175
|
|
176
|
+
# Note: This method doesn't disconnect if already connected. You probably
|
177
|
+
# want to use `reconnect!`
|
163
178
|
def reconnect_connection
|
179
|
+
@auto_reconnect = true
|
164
180
|
EM.cancel_timer(@reconnect_timer) if @reconnect_timer
|
165
181
|
reconnect
|
166
182
|
end
|
data/lib/em-hiredis/client.rb
CHANGED
@@ -1,9 +1,50 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
1
3
|
module EventMachine::Hiredis
|
2
4
|
class Client < BaseClient
|
3
5
|
def self.connect(host = 'localhost', port = 6379)
|
4
6
|
new(host, port).connect
|
5
7
|
end
|
6
8
|
|
9
|
+
def self.load_scripts_from(dir)
|
10
|
+
Dir.glob("#{dir}/*.lua").each do |f|
|
11
|
+
name = Regexp.new(/([^\/]*)\.lua$/).match(f)[1]
|
12
|
+
lua = File.open(f, 'r').read
|
13
|
+
EM::Hiredis.logger.debug { "Registering script: #{name}" }
|
14
|
+
EM::Hiredis::Client.register_script(name, lua)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.register_script(name, lua)
|
19
|
+
sha = Digest::SHA1.hexdigest(lua)
|
20
|
+
self.send(:define_method, name.to_sym) { |keys, args=[]|
|
21
|
+
eval_script(lua, sha, keys, args)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def register_script(name, lua)
|
26
|
+
sha = Digest::SHA1.hexdigest(lua)
|
27
|
+
singleton = class << self; self end
|
28
|
+
singleton.send(:define_method, name.to_sym) { |keys, args=[]|
|
29
|
+
eval_script(lua, sha, keys, args)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def eval_script(lua, lua_sha, keys, args)
|
34
|
+
df = EM::DefaultDeferrable.new
|
35
|
+
method_missing(:evalsha, lua_sha, keys.size, *keys, *args).callback(
|
36
|
+
&df.method(:succeed)
|
37
|
+
).errback { |e|
|
38
|
+
if e.kind_of?(RedisError) && e.redis_error.message.start_with?("NOSCRIPT")
|
39
|
+
self.eval(lua, keys.size, *keys, *args)
|
40
|
+
.callback(&df.method(:succeed)).errback(&df.method(:fail))
|
41
|
+
else
|
42
|
+
df.fail(e)
|
43
|
+
end
|
44
|
+
}
|
45
|
+
df
|
46
|
+
end
|
47
|
+
|
7
48
|
def monitor(&blk)
|
8
49
|
@monitoring = true
|
9
50
|
method_missing(:monitor, &blk)
|
data/lib/em-hiredis/lock.rb
CHANGED
@@ -1,35 +1,52 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
1
3
|
module EM::Hiredis
|
2
|
-
#
|
4
|
+
# Cross-process re-entrant lock, backed by redis
|
3
5
|
class Lock
|
4
|
-
|
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)
|
5
13
|
def onexpire(&blk); @onexpire = blk; end
|
6
14
|
|
7
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
|
8
19
|
@redis, @key, @timeout = redis, key, timeout
|
9
|
-
@
|
10
|
-
@expiry = nil
|
20
|
+
@token = SecureRandom.hex
|
11
21
|
end
|
12
22
|
|
13
23
|
# Acquire the lock
|
14
24
|
#
|
15
|
-
#
|
25
|
+
# This is a re-entrant lock, re-acquiring will succeed and extend the timeout
|
16
26
|
#
|
17
|
-
# Returns a deferrable which either succeeds if the lock can be acquired, or fails if it cannot.
|
27
|
+
# Returns a deferrable which either succeeds if the lock can be acquired, or fails if it cannot.
|
18
28
|
def acquire
|
19
29
|
df = EM::DefaultDeferrable.new
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
EM
|
25
|
-
|
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
|
26
41
|
else
|
27
|
-
|
42
|
+
EM::Hiredis.logger.debug "#{to_s} failed to acquire"
|
43
|
+
df.fail("Lock is not available")
|
28
44
|
end
|
29
45
|
}.errback { |e|
|
46
|
+
EM::Hiredis.logger.error "#{to_s} Error acquiring lock #{e}"
|
30
47
|
df.fail(e)
|
31
48
|
}
|
32
|
-
|
49
|
+
df
|
33
50
|
end
|
34
51
|
|
35
52
|
# Release the lock
|
@@ -37,23 +54,29 @@ module EM::Hiredis
|
|
37
54
|
# Returns a deferrable
|
38
55
|
def unlock
|
39
56
|
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
57
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
53
72
|
end
|
54
73
|
|
55
|
-
# This should not be used in normal operation
|
74
|
+
# This should not be used in normal operation.
|
75
|
+
# Force clear without regard to who owns the lock.
|
56
76
|
def clear
|
77
|
+
EM::Hiredis.logger.warn "#{to_s} Force clearing lock (unsafe)"
|
78
|
+
EM.cancel_timer(@expire_timer) if @expire_timer
|
79
|
+
|
57
80
|
@redis.del(@key)
|
58
81
|
end
|
59
82
|
|
@@ -61,46 +84,5 @@ module EM::Hiredis
|
|
61
84
|
"[lock #{@key}]"
|
62
85
|
end
|
63
86
|
|
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
87
|
end
|
106
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
|
@@ -21,11 +21,8 @@ module EM::Hiredis
|
|
21
21
|
@redis, @key = redis, key
|
22
22
|
@timeout = options[:lock_timeout] || 100
|
23
23
|
@retry_timeout = options[:retry_interval] || 60
|
24
|
+
|
24
25
|
@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
26
|
@locked = false
|
30
27
|
EM.next_tick {
|
31
28
|
@running = true
|
@@ -42,6 +39,11 @@ module EM::Hiredis
|
|
42
39
|
@onlocked.call if @onlocked
|
43
40
|
@locked = true
|
44
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
|
+
}
|
45
47
|
}.errback { |e|
|
46
48
|
if @locked
|
47
49
|
# We were previously locked
|
@@ -54,7 +56,7 @@ module EM::Hiredis
|
|
54
56
|
EM::Hiredis.logger.warn "Unexpected error acquiring #{@lock} #{err}"
|
55
57
|
end
|
56
58
|
|
57
|
-
EM.add_timer(@retry_timeout) {
|
59
|
+
@retry_timer = EM.add_timer(@retry_timeout) {
|
58
60
|
acquire() unless @locked
|
59
61
|
}
|
60
62
|
}
|
@@ -62,6 +64,8 @@ module EM::Hiredis
|
|
62
64
|
|
63
65
|
def stop
|
64
66
|
@running = false
|
67
|
+
EM.cancel_timer(@extend_timer) if @extend_timer
|
68
|
+
EM.cancel_timer(@retry_timer) if @retry_timer
|
65
69
|
if @locked
|
66
70
|
# We were previously locked
|
67
71
|
@onunlocked.call if @onunlocked
|
data/lib/em-hiredis/version.rb
CHANGED
data/spec/base_client_spec.rb
CHANGED
@@ -105,7 +105,7 @@ describe EM::Hiredis::BaseClient do
|
|
105
105
|
df.errback { |e|
|
106
106
|
e.class.should == EM::Hiredis::RedisError
|
107
107
|
e.should be_kind_of(EM::Hiredis::Error)
|
108
|
-
msg = "
|
108
|
+
msg = "WRONGTYPE Operation against a key holding the wrong kind of value"
|
109
109
|
e.message.should == msg
|
110
110
|
# This is the wrapped error from redis:
|
111
111
|
e.redis_error.class.should == RuntimeError
|
data/spec/lock_spec.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EventMachine::Hiredis::Lock do
|
4
|
+
|
5
|
+
def start(timeout = 1)
|
6
|
+
connect(timeout) do |redis|
|
7
|
+
@redis = redis
|
8
|
+
yield
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def new_lock
|
13
|
+
EventMachine::Hiredis::Lock.new(@redis, "test-lock", 2)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can be acquired" do
|
17
|
+
start {
|
18
|
+
new_lock.acquire.callback {
|
19
|
+
done
|
20
|
+
}.errback { |e|
|
21
|
+
fail e
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "is re-entrant" do
|
27
|
+
start {
|
28
|
+
lock = new_lock
|
29
|
+
lock.acquire.callback {
|
30
|
+
lock.acquire.callback {
|
31
|
+
done
|
32
|
+
}.errback { |e|
|
33
|
+
fail e
|
34
|
+
}
|
35
|
+
}.errback { |e|
|
36
|
+
fail e
|
37
|
+
}
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
it "is exclusive" do
|
42
|
+
start {
|
43
|
+
new_lock.acquire.callback {
|
44
|
+
new_lock.acquire.errback {
|
45
|
+
done
|
46
|
+
}.callback {
|
47
|
+
fail "Should not be able to acquire lock from different client"
|
48
|
+
}
|
49
|
+
}.errback { |e|
|
50
|
+
fail e
|
51
|
+
}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
it "can be released and taken by another instance" do
|
56
|
+
start {
|
57
|
+
lock = new_lock
|
58
|
+
lock.acquire.callback {
|
59
|
+
lock.unlock.callback {
|
60
|
+
new_lock.acquire.callback {
|
61
|
+
done
|
62
|
+
}.errback { |e|
|
63
|
+
fail e
|
64
|
+
}
|
65
|
+
}.errback { |e|
|
66
|
+
fail e
|
67
|
+
}
|
68
|
+
}.errback { |e|
|
69
|
+
fail e
|
70
|
+
}
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
it "times out" do
|
75
|
+
start(3) {
|
76
|
+
new_lock.acquire.callback {
|
77
|
+
EM.add_timer(2) {
|
78
|
+
new_lock.acquire.callback {
|
79
|
+
done
|
80
|
+
}.errback { |e|
|
81
|
+
fail e
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}.errback { |e|
|
85
|
+
fail e
|
86
|
+
}
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
it "extends timeout on re-entry" do
|
91
|
+
start(4) {
|
92
|
+
lock = new_lock
|
93
|
+
lock.acquire.callback {
|
94
|
+
EM.add_timer(1) {
|
95
|
+
lock.acquire.callback {
|
96
|
+
EM.add_timer(1.5) {
|
97
|
+
# Check it's still locked by initial instance
|
98
|
+
new_lock.acquire.errback {
|
99
|
+
done
|
100
|
+
}.callback { |e|
|
101
|
+
fail e
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}.errback { |e|
|
105
|
+
fail e
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}.errback { |e|
|
109
|
+
fail e
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
it "fails to release if it has not been taken" do
|
115
|
+
start {
|
116
|
+
new_lock.unlock.errback {
|
117
|
+
done
|
118
|
+
}.callback {
|
119
|
+
fail "Released lock which had not been taken"
|
120
|
+
}
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
it "fails to release if taken by another instance" do
|
125
|
+
start {
|
126
|
+
new_lock.acquire.callback {
|
127
|
+
new_lock.unlock.errback {
|
128
|
+
done
|
129
|
+
}.callback {
|
130
|
+
fail "Released lock belonging to another instance"
|
131
|
+
}
|
132
|
+
}.errback { |e|
|
133
|
+
fail e
|
134
|
+
}
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
metadata
CHANGED
@@ -1,78 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-hiredis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Martyn Loughran
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-06-30 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: eventmachine
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
14
27
|
- !ruby/object:Gem::Dependency
|
15
28
|
name: hiredis
|
16
29
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
30
|
requirements:
|
19
|
-
- - ~>
|
31
|
+
- - "~>"
|
20
32
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.
|
33
|
+
version: 0.5.0
|
22
34
|
type: :runtime
|
23
35
|
prerelease: false
|
24
36
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
37
|
requirements:
|
27
|
-
- - ~>
|
38
|
+
- - "~>"
|
28
39
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.
|
40
|
+
version: 0.5.0
|
30
41
|
- !ruby/object:Gem::Dependency
|
31
42
|
name: em-spec
|
32
43
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
44
|
requirements:
|
35
|
-
- - ~>
|
45
|
+
- - "~>"
|
36
46
|
- !ruby/object:Gem::Version
|
37
47
|
version: 0.2.5
|
38
48
|
type: :development
|
39
49
|
prerelease: false
|
40
50
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
51
|
requirements:
|
43
|
-
- - ~>
|
52
|
+
- - "~>"
|
44
53
|
- !ruby/object:Gem::Version
|
45
54
|
version: 0.2.5
|
46
55
|
- !ruby/object:Gem::Dependency
|
47
56
|
name: rspec
|
48
57
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
58
|
requirements:
|
51
|
-
- - ~>
|
59
|
+
- - "~>"
|
52
60
|
- !ruby/object:Gem::Version
|
53
61
|
version: 2.6.0
|
54
62
|
type: :development
|
55
63
|
prerelease: false
|
56
64
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
65
|
requirements:
|
59
|
-
- - ~>
|
66
|
+
- - "~>"
|
60
67
|
- !ruby/object:Gem::Version
|
61
68
|
version: 2.6.0
|
62
69
|
- !ruby/object:Gem::Dependency
|
63
70
|
name: rake
|
64
71
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
72
|
requirements:
|
67
|
-
- -
|
73
|
+
- - ">="
|
68
74
|
- !ruby/object:Gem::Version
|
69
75
|
version: '0'
|
70
76
|
type: :development
|
71
77
|
prerelease: false
|
72
78
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
79
|
requirements:
|
75
|
-
- -
|
80
|
+
- - ">="
|
76
81
|
- !ruby/object:Gem::Version
|
77
82
|
version: '0'
|
78
83
|
description: Eventmachine redis client using hiredis native parser
|
@@ -82,8 +87,8 @@ executables: []
|
|
82
87
|
extensions: []
|
83
88
|
extra_rdoc_files: []
|
84
89
|
files:
|
85
|
-
- .gitignore
|
86
|
-
- .rspec
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
87
92
|
- CHANGELOG.md
|
88
93
|
- Gemfile
|
89
94
|
- LICENCE
|
@@ -91,6 +96,8 @@ files:
|
|
91
96
|
- Rakefile
|
92
97
|
- em-hiredis.gemspec
|
93
98
|
- examples/getting_started.rb
|
99
|
+
- examples/lua/sum.lua
|
100
|
+
- examples/lua_example.rb
|
94
101
|
- examples/pubsub_basics.rb
|
95
102
|
- examples/pubsub_more.rb
|
96
103
|
- examples/pubsub_raw.rb
|
@@ -100,12 +107,15 @@ files:
|
|
100
107
|
- lib/em-hiredis/connection.rb
|
101
108
|
- lib/em-hiredis/event_emitter.rb
|
102
109
|
- lib/em-hiredis/lock.rb
|
110
|
+
- lib/em-hiredis/lock_lua/lock_acquire.lua
|
111
|
+
- lib/em-hiredis/lock_lua/lock_release.lua
|
103
112
|
- lib/em-hiredis/persistent_lock.rb
|
104
113
|
- lib/em-hiredis/pubsub_client.rb
|
105
114
|
- lib/em-hiredis/version.rb
|
106
115
|
- spec/base_client_spec.rb
|
107
116
|
- spec/connection_spec.rb
|
108
117
|
- spec/live_redis_protocol_spec.rb
|
118
|
+
- spec/lock_spec.rb
|
109
119
|
- spec/pubsub_spec.rb
|
110
120
|
- spec/redis_commands_spec.rb
|
111
121
|
- spec/spec_helper.rb
|
@@ -114,38 +124,32 @@ files:
|
|
114
124
|
- spec/url_param_spec.rb
|
115
125
|
homepage: http://github.com/mloughran/em-hiredis
|
116
126
|
licenses: []
|
127
|
+
metadata: {}
|
117
128
|
post_install_message:
|
118
129
|
rdoc_options: []
|
119
130
|
require_paths:
|
120
131
|
- lib
|
121
132
|
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
-
none: false
|
123
133
|
requirements:
|
124
|
-
- -
|
134
|
+
- - ">="
|
125
135
|
- !ruby/object:Gem::Version
|
126
136
|
version: '0'
|
127
|
-
segments:
|
128
|
-
- 0
|
129
|
-
hash: -3658727761083238148
|
130
137
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
-
none: false
|
132
138
|
requirements:
|
133
|
-
- -
|
139
|
+
- - ">="
|
134
140
|
- !ruby/object:Gem::Version
|
135
141
|
version: '0'
|
136
|
-
segments:
|
137
|
-
- 0
|
138
|
-
hash: -3658727761083238148
|
139
142
|
requirements: []
|
140
143
|
rubyforge_project: em-hiredis
|
141
|
-
rubygems_version:
|
144
|
+
rubygems_version: 2.2.2
|
142
145
|
signing_key:
|
143
|
-
specification_version:
|
146
|
+
specification_version: 4
|
144
147
|
summary: Eventmachine redis client
|
145
148
|
test_files:
|
146
149
|
- spec/base_client_spec.rb
|
147
150
|
- spec/connection_spec.rb
|
148
151
|
- spec/live_redis_protocol_spec.rb
|
152
|
+
- spec/lock_spec.rb
|
149
153
|
- spec/pubsub_spec.rb
|
150
154
|
- spec/redis_commands_spec.rb
|
151
155
|
- spec/spec_helper.rb
|