Project

kannuki

0.0
No release in over 3 years
Kannuki provides database-agnostic advisory locking for ActiveRecord with support for PostgreSQL and MySQL, offering blocking/non-blocking strategies, instrumentation, and ActiveJob integration.
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

Kannuki

Advisory locking for ActiveRecord with modern Rails conventions.

Ruby Version License Gem Version CI Status

Installation • Basic Usage • Model Extension • ActiveJob • Configuration • User Guide

Kannuki provides database-agnostic advisory locking for ActiveRecord with support for PostgreSQL and MySQL, offering blocking/non-blocking strategies, instrumentation, and ActiveJob integration.

Installation

Add to your Gemfile:

gem 'kannuki'

Then run:

bundle install

Generate an initializer (optional):

rails generate kannuki:install

Basic Usage

Simple Lock

Kannuki.with_lock("my_critical_section") do
  # Exclusive execution
end

With Timeout

Kannuki.with_lock("process_order", timeout: 10) do
  # Returns false if lock not acquired within 10 seconds
end

Non-blocking (Try Lock)

result = Kannuki.try_lock("quick_check") do
  perform_quick_operation
end
puts "Lock was not available" if result == false

Raise on Failure

Kannuki.lock!("must_succeed") do
  critical_operation
end
# => raises Kannuki::LockNotAcquiredError if lock unavailable

Model Extension

class Order < ApplicationRecord
  kannuki :number_generation, scope: :organization_id
end

Usage:

order.with_number_generation_lock do
  order.number = organization.orders.maximum(:number).to_i + 1
  order.save!
end

# Non-blocking
order.try_number_generation_lock { ... }

# Raise on failure
order.number_generation_lock! { ... }

# Check if locked
order.number_generation_locked?

Ad-hoc locking:

order.with_lock("custom_operation") do
  # Lock key: "orders/123/custom_operation"
end

ActiveJob Integration

Prevent Concurrent Execution

class HeavyImportJob < ApplicationJob
  with_lock :import, key: -> { arguments.first }
  
  def perform(import_id)
    # Exclusive execution per import_id
  end
end

Skip Duplicate Jobs

class DataSyncJob < ApplicationJob
  unique_by_lock on_conflict: :skip
  
  def perform(resource_type, resource_id)
    # Only one job with same arguments runs at a time
  end
end

Configuration

# config/initializers/kannuki.rb
Kannuki.configure do |config|
  config.default_timeout = 30
  config.default_strategy = :blocking
  config.key_prefix = "myapp"
  config.enable_instrumentation = Rails.env.production?
  config.retry_attempts = 3
  config.retry_interval = 0.5
  config.retry_backoff = :exponential
end

Strategies

Strategy Behavior
:blocking Waits until lock available or timeout (default)
:non_blocking Returns immediately if unavailable
:retry Retries with configurable backoff
Kannuki.with_lock("op", strategy: :retry, retry_attempts: 5) { ... }

Instrumentation

Kannuki emits ActiveSupport::Notifications events:

  • acquired.kannuki
  • released.kannuki
  • failed.kannuki
  • timeout.kannuki
  • waiting.kannuki
ActiveSupport::Notifications.subscribe(/\.kannuki$/) do |name, start, finish, id, payload|
  duration = (finish - start) * 1000
  Rails.logger.info "[Kannuki] #{name}: #{payload[:lock_key]} (#{duration.round(2)}ms)"
end

Testing

RSpec.configure do |config|
  config.before { Kannuki::Testing.enable! }
  config.after { Kannuki::Testing.clear! }
end

# In tests
Kannuki::Testing.simulate_lock_held("my_lock")
result = Kannuki.try_lock("my_lock") { "success" }
expect(result).to be false

Database Support

PostgreSQL

Feature Supported
Session-level locks Yes
Transaction-level locks Yes
Shared locks Yes

MySQL

Feature Supported
Session-level locks Yes
Transaction-level locks No
Shared locks No

Requirements

  • Ruby >= 3.1
  • Rails >= 7.0
  • PostgreSQL >= 12 or MySQL >= 8.0

Development

bundle install
bundle exec rspec

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/ydah/kannuki.

License

The gem is available as open source under the terms of the MIT License.