redis-em-mutex 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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 fiber)
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.1.2"
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
@@ -1,7 +1,7 @@
1
1
  class Redis
2
2
  module EM
3
3
  class Mutex
4
- VERSION = '0.1.2'
4
+ VERSION = '0.2.0'
5
5
  end
6
6
  end
7
7
  end