ixti/redis-lockers

View on GitHub
lib/redis/lockers/lock.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

require "securerandom"

require "concurrent/utility/monotonic_time"
require "redis/prescription"

class Redis
  module Lockers
    # Single lock instance.
    class Lock
      LOCK_SCRIPT = Redis::Prescription.read("#{__dir__}/scripts/lock.lua")
      private_constant :LOCK_SCRIPT

      UNLOCK_SCRIPT = Redis::Prescription.read("#{__dir__}/scripts/unlock.lua")
      private_constant :UNLOCK_SCRIPT

      # Create a new Lock instance.
      # @param key [#to_s] Resource name
      # @param ttl [#to_i] TTL in milliseconds
      def initialize(key, ttl:)
        @key   = key.to_s
        @ttl   = ttl.to_i
        @drift = @ttl * 0.01 + 2.0
        @nonce = SecureRandom.uuid
      end

      # Attempts to acquire lease.
      #
      # @param redis [Redis] Redis client
      # @return [Boolean] whenever lock was acquired or not.
      def acquire(redis)
        deadline = timestamp + @ttl - @drift
        success  = LOCK_SCRIPT.eval(redis, {
          :keys => [@key],
          :argv => [@nonce, @ttl]
        })

        success && timestamp <= deadline || false
      end

      # Release acquired lease.
      #
      # @param redis [Redis] Redis client
      # @return [void]
      def release(redis)
        UNLOCK_SCRIPT.eval(redis, :keys => [@key], :argv => [@nonce])
      end

      private

      # Returns monotonic timestamp.
      # @return [Float]
      def timestamp
        Concurrent.monotonic_time
      end
    end
  end
end