Class: UncomplicatedMutex

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

Constant Summary collapse

MutexTimeout =
Class.new(StandardError)
LUA_ACQUIRE =
"return redis.call('SET', KEYS[1], ARGV[2], 'NX', 'EX', ARGV[1]) and redis.call('expire', KEYS[1], ARGV[1]) and 1 or 0"
LUA_RELEASE =
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(obj, opts = {}) ⇒ UncomplicatedMutex

Returns a new instance of UncomplicatedMutex.



12
13
14
15
16
17
18
19
20
21
# File 'lib/uncomplicated_mutex.rb', line 12

def initialize(obj, opts = {})
  @verbose          = opts[:verbose]
  @timeout          = opts[:timeout] || 300
  @fail_on_timeout  = opts[:fail_on_timeout]
  @ticks            = opts[:ticks] || 100
  @wait_tick        = @timeout.to_f / @ticks.to_f
  @redis            = opts[:redis] || Redis.new
  @lock_name        = "lock:#{obj.class.name}:#{obj.id}".squeeze(":")
  @token            = Digest::MD5.new.hexdigest("#{@lock_name}_#{Time.now.to_f}")
end

Instance Attribute Details

#lock_nameObject (readonly)

Returns the value of attribute lock_name.



5
6
7
# File 'lib/uncomplicated_mutex.rb', line 5

def lock_name
  @lock_name
end

Instance Method Details

#acquire_mutexObject



23
24
25
26
# File 'lib/uncomplicated_mutex.rb', line 23

def acquire_mutex
  puts("Running transaction to acquire the lock #{@lock_name}") if @verbose
  @redis.eval(LUA_ACQUIRE, [ @lock_name ], [ @timeout, @token ]) == 1
end

#destroy_mutexObject



28
29
30
31
# File 'lib/uncomplicated_mutex.rb', line 28

def destroy_mutex
  puts("Destroying the lock #{@lock_name}") if @verbose
  @redis.del(@lock_name)
end

#lock(&block) ⇒ Object



33
34
35
36
37
38
39
40
# File 'lib/uncomplicated_mutex.rb', line 33

def lock(&block)
  begin
    wait_for_mutex
    yield block
  ensure
    release_mutex
  end
end

#overwrite_mutexObject



42
43
44
45
# File 'lib/uncomplicated_mutex.rb', line 42

def overwrite_mutex
  puts("Replacing the lock #{@lock_name} with #{@token}") if @verbose
  @redis.set(@lock_name, @token)
end

#recurse_until_ready(depth = 1) ⇒ Object



47
48
49
50
51
# File 'lib/uncomplicated_mutex.rb', line 47

def recurse_until_ready(depth = 1)
  return false if depth == @ticks
  wait_a_tick if depth > 1
  acquire_mutex || recurse_until_ready(depth + 1)
end

#release_mutexObject



53
54
55
56
# File 'lib/uncomplicated_mutex.rb', line 53

def release_mutex
  puts("Releasing the lock #{@lock_name} if it still holds the value '#{@token}'") if @verbose
  @redis.eval(LUA_RELEASE, [ @lock_name ], [ @token ])
end

#wait_a_tickObject



58
59
60
61
# File 'lib/uncomplicated_mutex.rb', line 58

def wait_a_tick
  puts("Sleeping #{@wait_tick} for the lock #{@lock_name} to become available") if @verbose
  sleep(@wait_tick)
end

#wait_for_mutexObject



63
64
65
66
67
68
69
70
71
# File 'lib/uncomplicated_mutex.rb', line 63

def wait_for_mutex
  if recurse_until_ready
    puts("Acquired lock #{@lock_name}") if @verbose
  else
    puts("Failed to acquire the lock") if @verbose
    raise MutexTimeout.new("Failed to acquire the lock") if @fail_on_timeout
    overwrite_mutex
  end
end