redis-em-mutex 0.1.2 → 0.2.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.
- data/HISTORY.rdoc +9 -0
- data/README.rdoc +92 -2
- data/lib/redis/em-mutex/ns.rb +47 -0
- data/lib/redis/em-mutex/version.rb +1 -1
- data/lib/redis/em-mutex.rb +215 -132
- data/spec/redis-em-mutex-condition.rb +162 -0
- data/spec/redis-em-mutex-namespaces.rb +16 -16
- data/spec/redis-em-mutex-owners.rb +156 -0
- data/spec/redis-em-mutex-semaphores.rb +146 -76
- metadata +19 -14
data/HISTORY.rdoc
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
0.2.0
|
2
|
+
- added compatibility with EM::Synchrony::Thread::ConditionVariable
|
3
|
+
- added #sleep and #wakeup
|
4
|
+
- moved Redis::EM::Mutex::NS to autoloaded file
|
5
|
+
- added #unlock! and now mutex object stores ownership information
|
6
|
+
- added #expired? and other expiration status methods
|
7
|
+
- added watching? helper
|
8
|
+
- added customizable ownership
|
9
|
+
|
1
10
|
0.1.2
|
2
11
|
- features macro-style definitions
|
3
12
|
- fixed: setup with :redis
|
data/README.rdoc
CHANGED
@@ -18,9 +18,11 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
|
|
18
18
|
multiple semaphores are required to be locked at once)
|
19
19
|
* best served with EM-Synchrony (uses EM::Synchrony::ConnectionPool internally)
|
20
20
|
* fiber-safe
|
21
|
-
* deadlock detection (only trivial cases: locking twice the same resource from the same
|
21
|
+
* deadlock detection (only trivial cases: locking twice the same resource from the same owner)
|
22
22
|
* mandatory lock expiration (with refreshing)
|
23
23
|
* macro-style definitions (Mutex::Macro mixin)
|
24
|
+
* compatible with Synchrony::Thread::ConditionVariable
|
25
|
+
* extendable (beyond fibers) mutex ownership
|
24
26
|
|
25
27
|
== BUGS/LIMITATIONS
|
26
28
|
|
@@ -42,7 +44,7 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
|
|
42
44
|
|
43
45
|
==== Gemfile
|
44
46
|
|
45
|
-
gem "redis-em-mutex", "~> 0.
|
47
|
+
gem "redis-em-mutex", "~> 0.2.0"
|
46
48
|
|
47
49
|
==== Github
|
48
50
|
|
@@ -189,8 +191,96 @@ The locking scope will be Mutex global namespace + class name + method name.
|
|
189
191
|
|
190
192
|
end
|
191
193
|
|
194
|
+
=== ConditionVariable
|
195
|
+
|
196
|
+
Redis::EM::Mutex may be used with EventMachine::Synchrony::Thread::ConditionVariable
|
197
|
+
in place of EventMachine::Synchrony::Thread::Mutex.
|
198
|
+
|
199
|
+
mutex = Redis::EM::Mutex.new('resource')
|
200
|
+
resource = EM::Synchrony::Thread::ConditionVariable.new
|
201
|
+
EM::Synchrony.next_tick do
|
202
|
+
mutex.synchronize {
|
203
|
+
# current fiber now needs the resource
|
204
|
+
resource.wait(mutex)
|
205
|
+
# current fiber can now have the resource
|
206
|
+
}
|
207
|
+
end
|
208
|
+
EM::Synchrony.next_tick do
|
209
|
+
mutex.synchronize {
|
210
|
+
# current fiber has finished using the resource
|
211
|
+
resource.signal
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
192
215
|
=== Advanced
|
193
216
|
|
217
|
+
==== Customized owner
|
218
|
+
|
219
|
+
In some cases you might want to extend the ownership of a locked semaphore beyond the current fiber.
|
220
|
+
That is to be able to "share" a locked semaphore between group of fibers associated with some external resource.
|
221
|
+
|
222
|
+
For example we want to allow only one connection from one ip address to be made to our group of servers
|
223
|
+
at the same time.
|
224
|
+
Of course you could configure some kind of load balancer that distributes your load to do that for you,
|
225
|
+
but let's just assume that the load balancer has no such features available.
|
226
|
+
|
227
|
+
The default "owner" of a locked semaphore consists of 3 parts: "Machine uuid" + "process pid" + "fiber id".
|
228
|
+
Redis::EM::Mutex allows you to replace the last part of the "owner" with your own value.
|
229
|
+
To customize mutex's "ownership" you should add :owner option to the created mutex,
|
230
|
+
passing some well defined owner id.
|
231
|
+
In our case the "scope" of a mutex will be the client's ip address and the "owner id"
|
232
|
+
will be an __id__ of an EventMachine::Connection object created upon connection from the client.
|
233
|
+
This way we make sure that any other connection from the same ip address made to any server and process
|
234
|
+
will have different ownership defined. At the same time all the fibers working with that Connection instance
|
235
|
+
can access mutex's #refresh and #unlock methods concurrently.
|
236
|
+
|
237
|
+
module MutexedConnection
|
238
|
+
def post_init
|
239
|
+
@buffer = ""
|
240
|
+
@mutex = nil
|
241
|
+
ip = Socket.unpack_sockaddr_in(get_peername).last
|
242
|
+
Fiber.new do
|
243
|
+
@mutex = Redis::EM::Mutex.lock(ip, block: 0, owner: self.__id__)
|
244
|
+
if @mutex
|
245
|
+
send_data "Hello #{ip}!"
|
246
|
+
#... initialize stuff
|
247
|
+
else
|
248
|
+
send_data "Duplicate connection not allowed!"
|
249
|
+
close_connection_after_writing
|
250
|
+
end
|
251
|
+
end.resume
|
252
|
+
end
|
253
|
+
|
254
|
+
def unbind
|
255
|
+
if @mutex
|
256
|
+
Fiber.new { @mutex.unlock! }.resume
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def receive_data(data)
|
261
|
+
@buffer << data
|
262
|
+
return unless @mutex
|
263
|
+
Fiber.new do
|
264
|
+
if @mutex.refresh # refresh expiration timeout
|
265
|
+
# ... handle data
|
266
|
+
else
|
267
|
+
close_connection_after_writing
|
268
|
+
end
|
269
|
+
end.resume
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
EM::Synchrony.run {
|
274
|
+
Redis::EM::Mutex.setup(size: 10, url: 'redis:///1', expire: 600)
|
275
|
+
EM.start_server "0.0.0.0", 6969, MutexedConnection
|
276
|
+
}
|
277
|
+
|
278
|
+
==== Forking live
|
279
|
+
|
280
|
+
You may safely fork process while running event reactor and having locked semaphores.
|
281
|
+
The locked semaphores in newly forked process will become unlocked while
|
282
|
+
their locked status in parent process will be preserved.
|
283
|
+
|
194
284
|
mutex = Redis::EM::Mutex.new('resource1', 'resource2', expire: 60)
|
195
285
|
|
196
286
|
EM.synchrony do
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Redis
|
2
|
+
module EM
|
3
|
+
class Mutex
|
4
|
+
class NS
|
5
|
+
attr_reader :ns
|
6
|
+
alias_method :namespace, :ns
|
7
|
+
# Creates a new namespace (Mutex factory).
|
8
|
+
#
|
9
|
+
# - ns = namespace
|
10
|
+
# - opts = options hash:
|
11
|
+
# - :block - default block timeout
|
12
|
+
# - :expire - default expire timeout
|
13
|
+
def initialize(ns, opts = {})
|
14
|
+
@ns = ns
|
15
|
+
@opts = (opts || {}).merge(:ns => ns)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Creates a namespaced cross machine/process/fiber semaphore.
|
19
|
+
#
|
20
|
+
# for arguments see: Redis::EM::Mutex.new
|
21
|
+
def new(*args)
|
22
|
+
if args.last.kind_of?(Hash)
|
23
|
+
args[-1] = @opts.merge(args.last)
|
24
|
+
else
|
25
|
+
args.push @opts
|
26
|
+
end
|
27
|
+
Redis::EM::Mutex.new(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Attempts to grab the lock and waits if it isn’t available.
|
31
|
+
#
|
32
|
+
# See: Redis::EM::Mutex.lock
|
33
|
+
def lock(*args)
|
34
|
+
mutex = new(*args)
|
35
|
+
mutex if mutex.lock
|
36
|
+
end
|
37
|
+
|
38
|
+
# Executes block of code protected with namespaced semaphore.
|
39
|
+
#
|
40
|
+
# See: Redis::EM::Mutex.synchronize
|
41
|
+
def synchronize(*args, &block)
|
42
|
+
new(*args).synchronize(&block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|