class Redlock

Constants

CLOCK_DRIFT_FACTOR
DEFAULT_RETRY_COUNT
DEFAULT_RETRY_DELAY
UNLOCK_SCRIPT
VERSION

Public Class Methods

new(*server_urls) click to toggle source
# File lib/redlock.rb, line 15
def initialize(*server_urls)
  @servers = []
  server_urls.each{|url|
    @servers << Redis.new(:url => url)
  }
  @quorum = server_urls.length / 2 + 1
  @retry_count = DEFAULT_RETRY_COUNT
  @retry_delay = DEFAULT_RETRY_DELAY
  load_script
end

Public Instance Methods

get_unique_lock_id() click to toggle source
# File lib/redlock.rb, line 57
def get_unique_lock_id
  val = ""
  bytes = SecureRandom.random_bytes(20)
  bytes.each_byte{|b|
    val << b.to_s(32)
  }
  val 
end
load_script() click to toggle source
# File lib/redlock.rb, line 26
def load_script
  @servers.each do |server|
    @unlock_sha = server.script(:load, UNLOCK_SCRIPT)
  end
end
lock(resource,ttl,val=nil) click to toggle source
# File lib/redlock.rb, line 66
def lock(resource,ttl,val=nil)
  val = get_unique_lock_id if val.nil?

  if @testing_mode == :bypass
    return {
      validity: ttl,
      resource: resource,
      val: val
    }
  elsif @testing_mode == :fail
    return false
  end

  @retry_count.times {
    n = 0
    start_time = (Time.now.to_f*1000).to_i
    @servers.each{|s|
      n += 1 if lock_instance(s,resource,val,ttl)
    }
    # Add 2 milliseconds to the drift to account for Redis expires
    # precision, which is 1 milliescond, plus 1 millisecond min drift
    # for small TTLs.
    drift = (ttl*CLOCK_DRIFT_FACTOR).to_i + 2
    validity_time = ttl-((Time.now.to_f*1000).to_i - start_time)-drift 
    if n >= @quorum && validity_time > 0
      return {
        :validity => validity_time,
        :resource => resource,
        :val => val
      }
    else
      @servers.each{|s|
        unlock_instance(s,resource,val)
      }
    end
    # Wait a random delay before to retry
    sleep(rand(@retry_delay).to_f/1000)
  }
  return false
end
lock_instance(redis,resource,val,ttl) click to toggle source
# File lib/redlock.rb, line 41
def lock_instance(redis,resource,val,ttl)
  begin
    return redis.set(resource, val, nx: true, px: ttl)
  rescue
    return false
  end
end
set_retry(count,delay) click to toggle source
# File lib/redlock.rb, line 32
def set_retry(count,delay)
  @retry_count = count
  @retry_delay = delay
end
testing=(mode) click to toggle source
# File lib/redlock.rb, line 37
def testing=(mode)
  @testing_mode = mode
end
unlock(lock) click to toggle source
# File lib/redlock.rb, line 107
def unlock(lock)
  return if @testing_mode == :bypass

  @servers.each{|s|
    unlock_instance(s,lock[:resource],lock[:val])
  }
end
unlock_instance(redis,resource,val) click to toggle source
# File lib/redlock.rb, line 49
def unlock_instance(redis,resource,val)
  begin
    redis.evalsha(@unlock_sha, keys: [resource], argv: [val])
  rescue
    # Nothing to do, unlocking is just a best-effort attempt.
  end
end