README.md in redis-mutex-1.2.2 vs README.md in redis-mutex-1.2.3

- old
+ new

@@ -9,20 +9,33 @@ -------- In the following example, only one thread / process / server can enter the locked block at one time. ```ruby +Redis::Mutex.lock(:your_lock_name) + # do something exclusively +end +``` + +or + +```ruby mutex = Redis::Mutex.new(:your_lock_name) -mutex.lock do +if mutex.lock # do something exclusively + mutex.unlock +else + puts "failed to obtain lock!" end ``` -By default, when one is holding a lock, others wait **1 second** in total, polling **every 100ms** to see if the lock was released. -When 1 second has passed, the lock method returns `false`. +By default, while one is holding a lock, others wait **1 second** in total, polling **every 100ms** to see if the lock was released. +When 1 second has passed, the lock method returns `false` and others give up. Note that if your job runs longer than **10 seconds**, +the lock will be automatically removed to avoid a deadlock situation in case your job is dead before releasing the lock. Also note +that you can configure any of these timing values, as explained later. -If you want to immediately receive `false` on an unsuccessful locking attempt, you can configure the mutex to work in the non-blocking mode, as explained later. +Or if you want to immediately receive `false` on an unsuccessful locking attempt, you can change the mutex mode to **non-blocking**. Install ------- gem install redis-mutex @@ -40,73 +53,90 @@ ```ruby Redis::Classy.db = Redis.new(:host => 'localhost') ``` -Note that Redis Mutex uses the `redis-classy` gem internally. +Note that Redis Mutex uses the `redis-classy` gem internally to organize keys in an isolated namespace. There are four methods - `new`, `lock`, `unlock` and `sweep`: ```ruby -mutex = Redis::Mutex.new(key, options) -mutex.lock -mutex.unlock -Redis::Mutex.sweep +mutex = Redis::Mutex.new(key, options) # Configure a mutex lock +mutex.lock # Try to obtain the lock +mutex.unlock # Release the lock if it's not expired +Redis::Mutex.sweep # Forcibly remove all locks + +Redis::Mutex.lock(key, options) # Shortcut to new + lock ``` -For the key, it takes any Ruby objects that respond to :id, where the key is automatically set as "TheClass:id", -or pass any string or symbol. +The key argument can be symbol, string, or any Ruby objects that respond to `id` method, where the key is automatically set as +`TheClass:id`. For any given key, `Redis::Mutex:` prefix will be automatically prepended. For instance, if you pass a `Room` +object with id of `123`, the actual key in Redis will be `Redis::Mutex:Room:123`. The automatic prefixing and instance binding +is the feature of `Redis::Classy` - for more internal details, refer to [Redis Classy](https://github.com/kenn/redis-classy). -Also the initialize method takes several options. +The initialize method takes several options. ```ruby :block => 1 # Specify in seconds how long you want to wait for the lock to be released. # Speficy 0 if you need non-blocking sematics and return false immediately. (default: 1) :sleep => 0.1 # Specify in seconds how long the polling interval should be when :block is given. # It is recommended that you do NOT go below 0.01. (default: 0.1) :expire => 10 # Specify in seconds when the lock should forcibly be removed when something went wrong # with the one who held the lock. (default: 10) ``` -The lock method returns `true` when the lock has been successfully obtained, or returns `false` when the attempts -failed after the seconds specified with **:block**. It immediately returns `false` when 0 is given to **:block**. +The lock method returns `true` when the lock has been successfully obtained, or returns `false` when the attempts failed after +the seconds specified with **:block**. When 0 is given to **:block**, it is set to **non-blocking** mode and immediately returns `false`. -Here's a sample usage in a Rails app: +In the following Rails example, only one request can enter to a given room. ```ruby class RoomController < ApplicationController + before_filter { @room = Room.find(params[:id]) } + def enter - @room = Room.find(params[:id]) - - mutex = Redis::Mutex.new(@room) # key => "Room:123" - mutex.lock do + success = Redis::Mutex.lock(@room) do # key => "Room:123" # do something exclusively end + render :text => success ? 'success!' : 'failed to obtain lock!' end end ``` -Note that you need to explicitly call the unlock method unless you don't use the block syntax. +Note that you need to explicitly call the unlock method when you don't use the block syntax, and it is recommended to +put the `unlock` method in the `ensure` clause unless you're sure your code won't raise any exception. -In the following example, - ```ruby def enter mutex = Redis::Mutex.new('non-blocking', :block => 0, :expire => 10.minutes) - mutex.lock - # do something exclusively - mutex.unlock -rescue - mutex.unlock - raise + if mutex.lock + begin + # do something exclusively + ensure + mutex.unlock + end + render :text => 'success!' + else + render :text => 'failed to obtain lock!' + end end ``` -Also note that, internally, the actual key is structured in the following form: +Macro-style definition +---------------------- +If you want to wrap an entire method into a critical section, you can use the macro-style definition. The locking scope +will be `TheClass#method` and only one method can run at any given time. + +If you give a proc object to the `after_failure` option, it will get called after locking attempt failed. + ```ruby - Redis.new.keys - => ["Redis::Mutex:Room:111", "Redis::Mutex:Room:112", ... ] +class JobController < ApplicationController + include Redis::Mutex::Macro + auto_mutex :run, :block => 0, :after_failure => lambda { render :text => "failed to obtain lock!" } + + def run + # do something exclusively + render :text => "success!" + end +end ``` - -The automatic prefixing and binding is the feature of `Redis::Classy`. -For more internal details, refer to [Redis Classy](https://github.com/kenn/redis-classy).