class GCRA::RedisStore

Redis store, expects all timestamps and durations to be integers with nanoseconds since epoch.

Constants

CAS_SCRIPT
CAS_SCRIPT_MISSING_KEY_RESPONSE
CAS_SHA

Digest::SHA1.hexdigest(CAS_SCRIPT)

CONNECTED_TO_READONLY
SCRIPT_NOT_IN_CACHE_RESPONSE

Public Class Methods

new(redis, key_prefix, options = {}) click to toggle source
# File lib/gcra/redis_store.rb, line 23
def initialize(redis, key_prefix, options = {})
  @redis = redis
  @key_prefix = key_prefix

  @reconnect_on_readonly = options[:reconnect_on_readonly] || false
end

Public Instance Methods

compare_and_set_with_ttl(key, old_value, new_value, ttl_nano) click to toggle source

Atomically compare the value at key to the old value. If it matches, set it to the new value and return true. Otherwise, return false. If the key does not exist in the store, return false with no error. If the swap succeeds, update the ttl for the key atomically.

# File lib/gcra/redis_store.rb, line 67
def compare_and_set_with_ttl(key, old_value, new_value, ttl_nano)
  full_key = @key_prefix + key
  retried = false
  begin
    ttl_milli = calculate_ttl_milli(ttl_nano)
    swapped = @redis.evalsha(CAS_SHA, keys: [full_key], argv: [old_value, new_value, ttl_milli])
  rescue Redis::CommandError => e
    if e.message == CAS_SCRIPT_MISSING_KEY_RESPONSE
      return false
    elsif e.message == SCRIPT_NOT_IN_CACHE_RESPONSE && !retried
      @redis.script('load', CAS_SCRIPT)
      retried = true
      retry
    elsif e.message == CONNECTED_TO_READONLY && @reconnect_on_readonly && !retried
      @redis.client.reconnect
      retried = true
      retry
    end
    raise
  end

  return swapped == 1
end
get_with_time(key) click to toggle source

Returns the value of the key or nil, if it isn't in the store. Also returns the time from the Redis server, with microsecond precision.

# File lib/gcra/redis_store.rb, line 32
def get_with_time(key)
  time_response, value = @redis.pipelined do
    @redis.time # returns tuple (seconds since epoch, microseconds)
    @redis.get(@key_prefix + key)
  end
  # Convert tuple to nanoseconds
  time = (time_response[0] * 1_000_000 + time_response[1]) * 1_000
  if value != nil
    value = value.to_i
  end

  return value, time
end
set_if_not_exists_with_ttl(key, value, ttl_nano) click to toggle source

Set the value of key only if it is not already set. Return whether the value was set. Also set the key's expiration (ttl, in seconds).

# File lib/gcra/redis_store.rb, line 48
def set_if_not_exists_with_ttl(key, value, ttl_nano)
  full_key = @key_prefix + key
  retried = false
  begin
    ttl_milli = calculate_ttl_milli(ttl_nano)
    @redis.set(full_key, value, nx: true, px: ttl_milli)
  rescue Redis::CommandError => e
    if e.message == CONNECTED_TO_READONLY && @reconnect_on_readonly && !retried
      @redis.client.reconnect
      retried = true
      retry
    end
    raise
  end
end

Private Instance Methods

calculate_ttl_milli(ttl_nano) click to toggle source
# File lib/gcra/redis_store.rb, line 93
def calculate_ttl_milli(ttl_nano)
  ttl_milli = ttl_nano / 1_000_000
  # Setting 0 as expiration/ttl would result in an error.
  # Therefore overwrite it and use 1
  if ttl_milli == 0
    return 1
  end
  return ttl_milli
end