class Ribbon::Intercom::Service::Channel::Stores::RedisStore::Mutex

A redis mutex inspired by <github.com/leandromoreira/redlock-rb>

Constants

CLOCK_DRIFT_FACTOR
DEFAULT_RETRY_COUNT
DEFAULT_RETRY_DELAY
UNLOCK_SCRIPT

Lua script to send to Redis when releasing a lock. See: redis.io/commands/set

Attributes

name[R]
redis[R]
retry_count[R]
retry_delay[R]

Public Class Methods

new(redis, name, opts={}) click to toggle source
# File lib/ribbon/intercom/service/channel/stores/redis_store.rb, line 136
def initialize(redis, name, opts={})
  @redis = redis
  @name = name
  @retry_count = opts[:retry_count] || DEFAULT_RETRY_COUNT
  @retry_delay = opts[:retry_delay] || DEFAULT_RETRY_DELAY
end
time_in_ms() click to toggle source
# File lib/ribbon/intercom/service/channel/stores/redis_store.rb, line 126
def time_in_ms
  (Time.now.to_f * 1000).to_i
end

Public Instance Methods

lock(ttl=1000) click to toggle source

Acquire a lock (non-blocking).

# File lib/ribbon/intercom/service/channel/stores/redis_store.rb, line 164
def lock(ttl=1000)
  handle = SecureRandom.uuid
  redis.set(name, handle, nx: true, px: ttl) && handle
end
synchronize(ttl) { |ttl| ... } click to toggle source
# File lib/ribbon/intercom/service/channel/stores/redis_store.rb, line 143
def synchronize(ttl)
  retry_count.times {
    start_time = self.class.time_in_ms

    if (handle=lock(ttl))
      begin
        time_elapsed = (self.class.time_in_ms - start_time).to_i
        return yield(ttl - time_elapsed - _drift(ttl))
      ensure
        unlock(handle)
      end
    else
      sleep(rand(retry_delay).to_f / 1000)
    end
  }

  raise LockUnobtainableError, name
end
unlock(handle) click to toggle source

Release a lock.

# File lib/ribbon/intercom/service/channel/stores/redis_store.rb, line 171
def unlock(handle)
  redis.eval(UNLOCK_SCRIPT, [name], [handle])
rescue
  # Do nothing, unlocking is best effort.
end

Private Instance Methods

_drift(ttl) click to toggle source

Approximate clock drift.

# File lib/ribbon/intercom/service/channel/stores/redis_store.rb, line 181
def _drift(ttl)
  # Add 2 milliseconds to the drift to account for Redis expires
  # precision, which is 1 millisecond, plus 1 millisecond min drift
  # for small TTLs.
  (ttl * CLOCK_DRIFT_FACTOR).to_i + 2
end