https://github.com/rwojsznis/sidekiq-lock
Simple redis-based lock mechanism for your sidekiq workers
https://github.com/rwojsznis/sidekiq-lock
lock locking-strategies redis-based-lock redis-lock sidekiq sidekiq-lock
Last synced: about 2 months ago
JSON representation
Simple redis-based lock mechanism for your sidekiq workers
- Host: GitHub
- URL: https://github.com/rwojsznis/sidekiq-lock
- Owner: rwojsznis
- License: mit
- Created: 2013-10-12T14:58:35.000Z (over 11 years ago)
- Default Branch: main
- Last Pushed: 2024-02-07T18:44:33.000Z (over 1 year ago)
- Last Synced: 2025-04-22T21:16:26.555Z (about 2 months ago)
- Topics: lock, locking-strategies, redis-based-lock, redis-lock, sidekiq, sidekiq-lock
- Language: Ruby
- Homepage:
- Size: 81.1 KB
- Stars: 107
- Watchers: 6
- Forks: 11
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
Awesome Lists containing this project
README
![]()
# Sidekiq::Lock
[](https://codeclimate.com/github/rwojsznis/sidekiq-lock)
[](http://badge.fury.io/rb/sidekiq-lock)**Note:** This is a _complete_ piece of software, it should work across all future sidekiq & ruby versions.
Redis-based simple locking mechanism for [sidekiq][2]. Uses [SET command][1] introduced in Redis 2.6.16.
It can be handy if you push a lot of jobs into the queue(s), but you don't want to execute specific jobs at the same
time - it provides a `lock` method that you can use in whatever way you want.## Installation
This gem requires at least:
- redis 2.6.12
- sidekiq 6Add this line to your application's Gemfile:
``` ruby
gem 'sidekiq-lock'
```And then execute:
``` bash
$ bundle
```## Usage
Sidekiq-lock is a middleware/module combination, let me go through my thought process here :).
In your worker class include `Sidekiq::Lock::Worker` module and provide `lock` attribute inside `sidekiq_options`,
for example:``` ruby
class Worker
include Sidekiq::Worker
include Sidekiq::Lock::Worker# static lock that expires after one second
sidekiq_options lock: { timeout: 1000, name: 'lock-worker' }def perform
# ...
end
end
```What will happen is:
- middleware will setup a `Sidekiq::Lock::RedisLock` object under `Thread.current[Sidekiq::Lock::THREAD_KEY]`
(it should work in most use cases without any problems - but it's configurable, more below) - assuming you provided
`lock` options, otherwise it will do nothing, just execute your worker's code- `Sidekiq::Lock::Worker` module provides a `lock` method that just simply points to that thread variable, just as
a convenienceSo now in your worker class you can call (whenever you need):
- `lock.acquire!` - will try to acquire the lock, if returns false on failure (that means some other process / thread
took the lock first)
- `lock.acquired?` - set to `true` when lock is successfully acquired
- `lock.release!` - deletes the lock (only if it's: acquired by current thread and not already expired)### Lock options
sidekiq_options lock will accept static values or `Proc` that will be called on argument(s) passed to `perform` method.
- timeout - specified expire time, in milliseconds
- name - name of the redis key that will be used as lock name
- value - (optional) value of the lock, if not provided it's set to random hexDynamic lock example:
``` ruby
class Worker
include Sidekiq::Worker
include Sidekiq::Lock::Worker
sidekiq_options lock: {
timeout: proc { |user_id, timeout| timeout * 2 },
name: proc { |user_id, timeout| "lock:peruser:#{user_id}" },
value: proc { |user_id, timeout| "#{user_id}" }
}def perform(user_id, timeout)
# ...
# do some work
# only at this point I want to acquire the lock
if lock.acquire!
begin
# I can do the work
# ...
ensure
# You probably want to manually release lock after work is done
# This method can be safely called even if lock wasn't acquired
# by current worker (thread). For more references see RedisLock class
lock.release!
end
else
# reschedule, raise an error or do whatever you want
end
end
end
```Just be sure to provide valid redis key as a lock name.
### Customizing lock method name
You can change `lock` to something else (globally) in sidekiq server configuration:
``` ruby
Sidekiq.configure_server do |config|
config.lock_method = :redis_lock
end
```### Customizing lock _container_
If you would like to change default behavior of storing lock instance in `Thread.current` for whatever reason you
can do that as well via server configuration:``` ruby
# Any thread-safe class that implements .fetch and .store methods will do
class CustomStorage
def fetch
# returns stored lock instance
end
def store(lock_instance)
# store lock
end
endSidekiq.configure_server do |config|
config.lock_container = CustomStorage.new
end
```### Inline testing
As you know middleware is not invoked when testing jobs inline, you can require in your test/spec helper file
`sidekiq/lock/testing/inline` to include two methods that will help you setting / clearing up lock manually:- `set_sidekiq_lock(worker_class, payload)` - note: payload should be an array of worker arguments
- `clear_sidekiq_lock`## Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request[1]: http://redis.io/commands/set
[2]: https://github.com/mperham/sidekiq