Class: Promiscuous::Redis::Mutex

Inherits:
Object
  • Object
show all
Defined in:
lib/promiscuous/redis.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key, options = {}) ⇒ Mutex

Returns a new instance of Mutex.



105
106
107
108
109
110
111
112
113
114
115
# File 'lib/promiscuous/redis.rb', line 105

def initialize(key, options={})
  # TODO remove old code with orig_key
  @orig_key = key.to_s
  @key      = "#{key}:lock"
  @timeout  = options[:timeout].to_i
  @sleep    = options[:sleep].to_f
  @expire   = options[:expire].to_i
  @lock_set = options[:lock_set]
  @node     = options[:node]
  raise "Which node?" unless @node
end

Instance Attribute Details

#tokenObject (readonly)

Returns the value of attribute token.



103
104
105
# File 'lib/promiscuous/redis.rb', line 103

def token
  @token
end

Instance Method Details

#extendObject



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/promiscuous/redis.rb', line 174

def extend
  now  = Time.now.to_i
  @@extend_script ||= Promiscuous::Redis::Script.new <<-SCRIPT
    local key = KEYS[1]
    local expires_at = tonumber(ARGV[1])
    local token = ARGV[2]

    if redis.call('hget', key, 'token') == token then
      redis.call('hset', key, 'expires_at', expires_at)
      return true
    else
      return false
    end
  SCRIPT
  !!@@extend_script.eval(@node, :keys => [@key].compact, :argv => [now + @expire, @token])
end

#keyObject



117
118
119
# File 'lib/promiscuous/redis.rb', line 117

def key
  @orig_key
end

#lockObject



125
126
127
128
129
130
131
132
133
# File 'lib/promiscuous/redis.rb', line 125

def lock
  result = false
  start_at = Time.now
  while Time.now - start_at < @timeout
    break if result = try_lock
    sleep @sleep
  end
  result
end

#nodeObject



121
122
123
# File 'lib/promiscuous/redis.rb', line 121

def node
  @node
end

#still_locked?Boolean

Returns:

  • (Boolean)


218
219
220
221
# File 'lib/promiscuous/redis.rb', line 218

def still_locked?
  raise "You never locked that mutex" unless @token
  @node.hget(@key, 'token').to_i == @token
end

#try_lockObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/promiscuous/redis.rb', line 135

def try_lock
  raise "You are trying to lock an already locked mutex" if @token

  now = Time.now.to_i

  # This script loading is not thread safe (touching a class variable), but
  # that's okay, because the race is harmless.
  @@lock_script ||= Promiscuous::Redis::Script.new <<-SCRIPT
    local key = KEYS[1]
    local token_key = KEYS[2]
    local lock_set = KEYS[3]
    local now = tonumber(ARGV[1])
    local expires_at = tonumber(ARGV[2])
    local orig_key = ARGV[3]

    local prev_expires_at = tonumber(redis.call('hget', key, 'expires_at'))
    if prev_expires_at and prev_expires_at > now then
      return {false, nil}
    end

    local next_token = redis.call('incr', 'promiscuous:next_token')

    redis.call('hmset', key, 'expires_at', expires_at, 'token', next_token)

    if lock_set then
      redis.call('zadd', lock_set, now, orig_key)
    end

    if prev_expires_at then
      return {'recovered', next_token}
    else
      return {true, next_token}
    end
  SCRIPT
  result, @token = @@lock_script.eval(@node, :keys => [@key, 'promiscuous:next_token', @lock_set].compact,
                                             :argv => [now, now + @expire, @orig_key])
  result == 'recovered' ? :recovered : !!result
end

#unlockObject



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/promiscuous/redis.rb', line 191

def unlock
  raise "You are trying to unlock a non locked mutex" unless @token

  # Since it's possible that the operations in the critical section took a long time,
  # we can't just simply release the lock. The unlock method checks if the unique @token
  # remains the same, and do not release if the lock token was overwritten.
  @@unlock_script ||= Script.new <<-LUA
    local key = KEYS[1]
    local lock_set = KEYS[2]
    local token = ARGV[1]
    local orig_key = ARGV[2]

    if redis.call('hget', key, 'token') == token then
      redis.call('del', key)
      if lock_set then
        redis.call('zrem', lock_set, orig_key)
      end
      return true
    else
      return false
    end
  LUA
  result = @@unlock_script.eval(@node, :keys => [@key, @lock_set].compact, :argv => [@token, @orig_key])
  @token = nil
  !!result
end