class SimpleMutex::Mutex

Constants

BaseError
DEFAULT_EXPIRES_IN
ERR_MSGS
LockError
UnlockError

Attributes

redis[RW]
expires_in[RW]
lock_key[RW]
payload[RW]
signature[RW]

Public Class Methods

lock(lock_key, **options) click to toggle source
# File lib/simple_mutex/mutex.rb, line 44
def lock(lock_key, **options)
  new(lock_key, **options).lock
end
lock!(lock_key, **options) click to toggle source
# File lib/simple_mutex/mutex.rb, line 48
def lock!(lock_key, **options)
  new(lock_key, **options).lock!
end
new(lock_key, expires_in: DEFAULT_EXPIRES_IN, signature: SecureRandom.uuid, payload: nil) click to toggle source
# File lib/simple_mutex/mutex.rb, line 115
def initialize(lock_key,
               expires_in: DEFAULT_EXPIRES_IN,
               signature:  SecureRandom.uuid,
               payload:    nil)
  ::SimpleMutex.redis_check!

  self.lock_key    = lock_key
  self.expires_in  = expires_in.to_i
  self.signature   = signature
  self.payload     = payload
end
raise_error(error_class, msg_template, lock_key) click to toggle source
# File lib/simple_mutex/mutex.rb, line 97
def raise_error(error_class, msg_template, lock_key)
  template_base = error_class.name.split("::").last.gsub("Error", "").downcase.to_sym
  error_msg     = ERR_MSGS[template_base][msg_template].call(lock_key)

  raise(error_class.new(error_msg, lock_key))
end
signature_valid?(raw_data, signature) click to toggle source
# File lib/simple_mutex/mutex.rb, line 104
def signature_valid?(raw_data, signature)
  return false if raw_data.nil?

  JSON.parse(raw_data)["signature"] == signature
rescue JSON::ParserError, TypeError
  false
end
unlock(lock_key, signature: nil, force: false) click to toggle source
# File lib/simple_mutex/mutex.rb, line 52
def unlock(lock_key, signature: nil, force: false)
  ::SimpleMutex.redis_check!

  redis = ::SimpleMutex.redis

  redis.watch(lock_key) do
    raw_data = redis.get(lock_key)

    if raw_data && (force || signature_valid?(raw_data, signature))
      redis.multi { |multi| multi.del(lock_key) }.first.positive?
    else
      redis.unwatch
      false
    end
  end
end
unlock!(lock_key, signature: nil, force: false) click to toggle source
# File lib/simple_mutex/mutex.rb, line 69
def unlock!(lock_key, signature: nil, force: false)
  ::SimpleMutex.redis_check!

  redis = ::SimpleMutex.redis

  redis.watch(lock_key) do
    raw_data = redis.get(lock_key)

    begin
      raise_error(UnlockError, :key_not_found, lock_key) unless raw_data

      unless force || signature_valid?(raw_data, signature)
        raise_error(UnlockError, :signature_mismatch, lock_key)
      end

      success = redis.multi { |multi| multi.del(lock_key) }.first.positive?

      raise_error(UnlockError, :unknown, lock_key) unless success
    ensure
      redis.unwatch
    end
  end
end
with_lock(lock_key, **options, &block) click to toggle source
# File lib/simple_mutex/mutex.rb, line 93
def with_lock(lock_key, **options, &block)
  new(lock_key, **options).with_lock(&block)
end

Public Instance Methods

lock() click to toggle source
# File lib/simple_mutex/mutex.rb, line 127
def lock
  !!redis.set(lock_key, generate_data, nx: true, ex: expires_in)
end
lock!() click to toggle source
# File lib/simple_mutex/mutex.rb, line 149
def lock!
  lock or raise_error(LockError, :basic)
end
lock_obtained?() click to toggle source
# File lib/simple_mutex/mutex.rb, line 145
def lock_obtained?
  self.class.signature_valid?(redis.get(lock_key), signature)
end
unlock(force: false) click to toggle source
# File lib/simple_mutex/mutex.rb, line 131
def unlock(force: false)
  self.class.unlock(lock_key, signature: signature, force: force)
end
unlock!(force: false) click to toggle source
# File lib/simple_mutex/mutex.rb, line 153
def unlock!(force: false)
  self.class.unlock!(lock_key, signature: signature, force: force)
end
with_lock() { || ... } click to toggle source
# File lib/simple_mutex/mutex.rb, line 135
def with_lock
  lock!

  begin
    yield
  ensure
    unlock
  end
end

Private Instance Methods

generate_data() click to toggle source
# File lib/simple_mutex/mutex.rb, line 161
def generate_data
  JSON.generate(
    "signature"  => signature,
    "created_at" => Time.now.to_s,
    "payload"    => payload,
  )
end
raise_error(error_class, msg_template) click to toggle source
# File lib/simple_mutex/mutex.rb, line 173
def raise_error(error_class, msg_template)
  self.class.raise_error(error_class, msg_template, lock_key)
end
redis() click to toggle source
# File lib/simple_mutex/mutex.rb, line 169
def redis
  ::SimpleMutex.redis
end