0.04
Low commit activity in last 3 years
A long-lived project that still receives updates
Simple redis-based lock mechanism for your sidekiq workers
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

sidekiq-lock

Sidekiq::Lock

GitHub Release CI

Note: This is a complete piece of software, it should work across all future sidekiq & ruby versions.

Redis-based simple locking mechanism for sidekiq. Uses SET command 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 6

Add this line to your application's Gemfile:

gem 'sidekiq-lock'

And then execute:

$ 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:

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 convenience

So 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 hex

Dynamic lock example:

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 the lock after work is done.
        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 before starting Sidekiq:

Sidekiq.lock_method = :redis_lock

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 before starting Sidekiq:

# 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
end

Sidekiq.lock_container = CustomStorage.new

Inline testing

perform_inline invokes Sidekiq's normal server middleware chain. Sidekiq::Testing.inline!, however, uses a separate testing middleware chain. Add the lock middleware to that chain if you want inline jobs to behave like production jobs:

Sidekiq::Testing.server_middleware do |chain|
  chain.add Sidekiq::Lock::Middleware
end

For tests that call perform directly, require sidekiq/lock/testing/inline in your test/spec helper to make these manual setup and cleanup methods available:

  • 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