Class: SimpleMutex::Mutex

Inherits:
Object
  • Object
show all
Defined in:
lib/simple_mutex/mutex.rb

Constant Summary collapse

DEFAULT_EXPIRES_IN =

1 hour

60 * 60
ERR_MSGS =
{
  unlock: {
    unknown: lambda do |lock_key|
      "something when wrong when deleting lock key <#{lock_key}>."
    end,
    key_not_found: lambda do |lock_key|
      "lock not found for lock key <#{lock_key}>."
    end,
    signature_mismatch: lambda do |lock_key|
      "signature mismatch for lock key <#{lock_key}>."
    end,
  }.freeze,
  lock: {
    basic: lambda do |lock_key|
      "failed to acquire lock <#{lock_key}>."
    end,
  }.freeze,
}.freeze
BaseError =
Class.new(::StandardError) do
  attr_reader :lock_key

  def initialize(msg, lock_key)
    @lock_key = lock_key
    super(msg)
  end
end
LockError =
Class.new(BaseError)
UnlockError =
Class.new(BaseError)

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(lock_key, expires_in: DEFAULT_EXPIRES_IN, signature: SecureRandom.uuid, payload: nil) ⇒ Mutex

Returns a new instance of Mutex.



115
116
117
118
119
120
121
122
123
124
125
# 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

Class Attribute Details

.redisObject

Returns the value of attribute redis.



42
43
44
# File 'lib/simple_mutex/mutex.rb', line 42

def redis
  @redis
end

Instance Attribute Details

#expires_inObject

Returns the value of attribute expires_in.



113
114
115
# File 'lib/simple_mutex/mutex.rb', line 113

def expires_in
  @expires_in
end

#lock_keyObject

Returns the value of attribute lock_key.



113
114
115
# File 'lib/simple_mutex/mutex.rb', line 113

def lock_key
  @lock_key
end

#payloadObject

Returns the value of attribute payload.



113
114
115
# File 'lib/simple_mutex/mutex.rb', line 113

def payload
  @payload
end

#signatureObject

Returns the value of attribute signature.



113
114
115
# File 'lib/simple_mutex/mutex.rb', line 113

def signature
  @signature
end

Class Method Details

.lock(lock_key, **options) ⇒ Object



44
45
46
# File 'lib/simple_mutex/mutex.rb', line 44

def lock(lock_key, **options)
  new(lock_key, **options).lock
end

.lock!(lock_key, **options) ⇒ Object



48
49
50
# File 'lib/simple_mutex/mutex.rb', line 48

def lock!(lock_key, **options)
  new(lock_key, **options).lock!
end

.raise_error(error_class, msg_template, lock_key) ⇒ Object

Raises:

  • (error_class)


97
98
99
100
101
102
# 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) ⇒ Boolean

Returns:

  • (Boolean)


104
105
106
107
108
109
110
# 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::ParseError, TypeError
  false
end

.unlock(lock_key, signature: nil, force: false) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 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) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# 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) ⇒ Object



93
94
95
# File 'lib/simple_mutex/mutex.rb', line 93

def with_lock(lock_key, **options, &block)
  new(lock_key, **options).with_lock(&block)
end

Instance Method Details

#lockObject



127
128
129
# File 'lib/simple_mutex/mutex.rb', line 127

def lock
  !!redis.set(lock_key, generate_data, nx: true, ex: expires_in)
end

#lock!Object



149
150
151
# File 'lib/simple_mutex/mutex.rb', line 149

def lock!
  lock or raise_error(LockError, :basic)
end

#lock_obtained?Boolean

Returns:

  • (Boolean)


145
146
147
# File 'lib/simple_mutex/mutex.rb', line 145

def lock_obtained?
  self.class.signature_valid?(redis.get(lock_key), signature)
end

#unlock(force: false) ⇒ Object



131
132
133
# 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) ⇒ Object



153
154
155
# File 'lib/simple_mutex/mutex.rb', line 153

def unlock!(force: false)
  self.class.unlock!(lock_key, signature: signature, force: force)
end

#with_lockObject



135
136
137
138
139
140
141
142
143
# File 'lib/simple_mutex/mutex.rb', line 135

def with_lock
  lock!

  begin
    yield
  ensure
    unlock
  end
end