RedisLocker
RedisLocker is a gem which provides locking system with redis backend.
Installation
Add this line to your application's Gemfile:
gem 'redis_locker'And then execute:
$ bundle install
Or install it yourself as:
$ gem install redis_locker
Usage
Main idea of RedisLocker is to provide simple interface for blocking models and its methods.
Configuration
RedisLocker needs redis obviously so you have to pass redis connection with configure block
RedisLocker.configure do |config|
config.redis_connection = Redis.new
endabove snippet has to be called before you start using redis lock so eg. if you're using Ruby on Rails you can place it in application.rb
Model
A model in RedisLocker is any class that implements id method. To make model RedisLockable you have to include RedisLocker in your class
class MyModel
include RedisLocker
endEvery lock is internally identified by model's id thus if you will lock model with id=10, then model with id=11 will be unlocked but every other instance with id=10 will be locked.
Locking
RedisLocker has two types of locks: model_lock and method_lock. Important thing is that every method_lock creates model_lock.
High Level Api
High Level Api is provided by instance method with_redis_lock and two class methods lock_every_method_call and lock_method.
with_redis_lock
with_redis_lock is an api to deal with model locks. It locks an object, then executes passed block and after that unlocks object
some_redis_lockable_object.with_redis_lock do
some_task
endIf there will be another with_redis_lock called then second call will fail.
lock_method
lock_method internally wraps method to with_redis_lock call every time when it's called.
class Model
include RedisLocker
lock_method :some_method
def id
10
end
def some_method
#sth
end
endCode above creates method_lock for :some_method and model_lock for Model with id=10 every time when some_method is called
lock_every_method_call
It effectively does same thing as adding lock_method for every method in Model class except method passed as excluded methods.
class Model
include RedisLocker
lock_every_method_call except: [:id, :initialize, :not_locked_method]
def id
10
end
def locked_method
#sth
end
def not_locked_method
#sth
end
endDefault excluded methods are id and initialize
Additional options
Every high level api method accepts same options: :strategy, :retry_interval and :retry_count. Unless you're using :retry startegy, :retry_count and :retry_interval will be ignored
:strategy
:strategy tells RedisLocker what to do when locked action is tried to be performed, default strategy is :exception.
Exception ( strategy: :exception ) strategy raises RedisLocker::Errors::Locked when another lock on resource is present.
Retry ( strategy: :retry ) strategy tries :retry_count + 1 times to execute code with :retry_interval between tries.
class Model
lock_method :some_method, strategy: :retry, retry_count: 2, retry_interval: 1
# rest of class omitted
endabove snippet after some_method was called tries to execute some_method, when lock occurs it will try two times with 1 second interval. If lock will be still present then it will raise RedisLocker::Errors::Locked
Silently die ( strategy: :silently_die ) strategy returns false if lock occurs
Low level api
RedisLocker provides also low level api which allows to manualy locking and unlocking models, which can be helpful sometimes but shouldn't be used without good reason
lock
lock method locks model and returns true if model was locked successfuly or false if model is already locked
lock!
lock! method does same thing as lock but if model was locked already it raises RedisLocker::Errors::AlreadyLocked error
unlock
unlock unlocks object if there is lock and returns true, otherwise returns false
locked?
locked? returns if object is locked
You can mix low level and high level api
some_model.lockthen if you try in another place
some_model.with_redis_lock strategy: :exception do
#sth
endIt will raise exception because some_model is locked but you will be able to call some locked method because there is no lock on any specific method.
releasing all locks
When you or someone else messed up with locks which are still present in redis you can use RedisLocker.release_locks! which removes all locks in redis.
Extending RedisLocker
You can write own locker by inheriting from RedisLocker::Locker, you have to implement lock, lock!, locked? and unlock methods. RedisLocker::Locker provides unique @instance_hash to sign locks and with_redis_lock method with implemented :exception, :retry and :silently_die strategies. You aren't forced to use Redis to store locks, if you want to you have to include RedisLocker::RedisConnection module which provides redis method to access redis connection. Otherwise you have to write own storing logic eg. using DB, own store engine or even files.
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/rwegrzyniak/redis_locker. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the RedisLocker project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.