with_advisory_lock Documentation

repository·master·Indexed 20 days ago

https://github.com/closuretree/with_advisory_lock

A Ruby gem providing advisory locking (mutexes) for ActiveRecord models using MySQL or PostgreSQL. It enables process synchronization across multiple hosts by using the database server as the lock manager. Supports PostgreSQL, MySQL (mysql2), and Trilogy adapters, with specialized support for JRuby and TruffleRuby. Provides methods like `with_advisory_lock` and `with_advisory_lock!` to execute code blocks within locks, and utilities to inspect current advisory locks.

Tokens
1.9K
Snippets
7
Records
9
Agent score
72%

What's inside with_advisory_lock

  1. Install with_advisory_lock via Bundler

    master

    To use advisory locking in your ActiveRecord application, add the gem to your Gemfile and run bundle.

    Requirements:

    • ActiveRecord 7.2+
    • Ruby 3.3+
    • JRuby or TruffleRuby
    • MySQL 8+ or PostgreSQL (MySQL 5.7 and SQLite are not supported)
    gem 'with_advisory_lock'
    $ bundle
  2. Install and integrate WithAdvisoryLock with ActiveRecord

    master

    The with_advisory_lock gem integrates with ActiveRecord via ActiveSupport hooks. When required in a standard Ruby environment (MRI or TruffleRuby), it automatically includes WithAdvisoryLock::Concern into ActiveRecord::Base and prepends the appropriate database-specific advisory lock modules to your connection adapters.

    Supported adapter integrations include:

    • PostgreSQL: Uses WithAdvisoryLock::CoreAdvisory and WithAdvisoryLock::PostgreSQLAdvisory.
    • MySQL (mysql2): Uses WithAdvisoryLock::CoreAdvisory and WithAdvisoryLock::MySQLAdvisory.
    • Trilogy: Uses WithAdvisoryLock::CoreAdvisory and WithAdvisoryLock::MySQLAdvisory.

    For JRuby, the gem uses a specialized JRubyAdapter which is installed via WithAdvisoryLock::JRubyAdapter.install! to ensure compatibility.

  3. Configure local database services via Docker Compose

    master

    The docker-compose.yml file provides a pre-configured environment for testing with_advisory_lock against PostgreSQL, MySQL, and MariaDB. You can use these services to set up local database instances with specific credentials and ports.

    PostgreSQL Configuration

    • Image: postgres:17-alpine
    • Host Port: 5433
    • Environment Variables:
      • POSTGRES_USER: test
      • POSTGRES_PASSWORD: test
      • POSTGRES_DB: with_advisory_lock_test

    MySQL Configuration

    • Image: mysql:8
    • Host Port: 3366
    • Environment Variables:
      • MYSQL_USER: test
      • MYSQL_PASSWORD: test
      • MYSQL_DATABASE: with_advisory_lock_test
      • MYSQL_RANDOM_ROOT_PASSWORD: yes
      • MYSQL_ROOT_HOST: %

    MariaDB Configuration

    • Image: mariadb:12
    • Host Port: 3368
    • Environment Variables:
      • MARIADB_USER: test
      • MARIADB_PASSWORD: test
      • MARIADB_DATABASE: with_advisory_lock_test_trilogy
      • MARIADB_RANDOM_ROOT_PASSWORD: yes
      • MARIADB_ROOT_HOST: %
    services:
      pg:
        image: postgres:17-alpine
        environment:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: with_advisory_lock_test
        ports:
          - "5433:5432"
      mysql:
        image: mysql:8
        environment:
          MYSQL_USER: test
          MYSQL_PASSWORD: test
          MYSQL_DATABASE: with_advisory_lock_test
          MYSQL_RANDOM_ROOT_PASSWORD: "yes"
          MYSQL_ROOT_HOST: '%'
        ports:
          - "3366:3306"
      mariadb:
        image: mariadb:12
        environment:
          MARIADB_USER: test
          MARIADB_PASSWORD: test
          MARIADB_DATABASE: with_advisory_lock_test_trilogy
          MARIADB_RANDOM_ROOT_PASSWORD: "yes"
          MARIADB_ROOT_HOST: '%'
        ports:
          - "3368:3306"
  4. Check if an advisory lock is currently held

    master

    Use advisory_lock_exists? to check if a specific lock name is currently active in the current connection's lock stack.

    Note: For PostgreSQL, the implementation attempts a non-blocking query first to avoid race conditions before falling back to a zero-timeout acquisition attempt.

    if MyModel.advisory_lock_exists?('my_lock_name')
      puts 'Lock is currently held'
    end
  5. Use with_advisory_lock to execute code within a lock

    master

    The with_advisory_lock method allows you to wrap a block of code in an advisory lock. If the lock is successfully acquired, the block is executed and its return value is returned. If the lock cannot be acquired (e.g., due to a timeout), the method returns false instead of raising an error.

    Arguments:

    • lock_name: The identifier for the lock.
    • options: A hash of options (e.g., timeout_seconds).
    • &block: The code to execute while holding the lock.
    # Returns the result of the block if lock acquired, otherwise returns false
    MyModel.with_advisory_lock('my_lock_name') do
      # critical section
    end
  6. Use with_advisory_lock! to enforce lock acquisition

    master

    The with_advisory_lock! method is similar to with_advisory_lock, but it enforces acquisition. If the lock cannot be acquired, it raises a WithAdvisoryLock::FailedToAcquireLock error.

    Arguments:

    • lock_name: The identifier for the lock.
    • options: A hash of options.
    • &block: The code to execute while holding the lock.
    # Raises WithAdvisoryLock::FailedToAcquireLock if lock cannot be acquired
    MyModel.with_advisory_lock!('my_lock_name') do
      # critical section
    end
  7. Inspect current advisory locks

    master

    You can inspect the locks currently held by the active connection using current_advisory_lock or current_advisory_locks.

    • current_advisory_lock: Returns the name of the most recent (top of stack) lock.
    • current_advisory_locks: Returns an array of names of all locks currently held in the stack.
    # Returns the name of the top-most lock
    lock = MyModel.current_advisory_lock
    
    # Returns an array of all held lock names
    all_locks = MyModel.current_advisory_locks
  8. Use the WithAdvisoryLock::Result object to check lock status

    master

    When using methods that return a WithAdvisoryLock::Result object (typically when a lock is not immediately acquired), you can inspect the outcome using the following attributes:

    • lock_was_acquired?: A predicate method that returns true if the lock was successfully acquired, and false otherwise.
    • result: Contains the return value of the block executed within the lock, if the lock was acquired.
    # Example of how a Result object might be used
    result_obj = WithAdvisoryLock::Result.new(lock_was_acquired: true, result: "success")
    
    if result_obj.lock_was_acquired?
      puts "Lock was held! Block result: #{result_obj.result}"
    else
      puts "Failed to acquire lock."
    end