Class: Idempo::RedisBackend
- Inherits:
-
Object
- Object
- Idempo::RedisBackend
- Defined in:
- lib/idempo/redis_backend.rb
Defined Under Namespace
Constant Summary collapse
- LOCK_TTL_SECONDS =
The TTL value for the lock, which is used if the process holding the lock crashes or gets killed
5 * 60
- DELETE_BY_KEY_AND_VALUE_SCRIPT =
<<~EOL redis.replicate_commands() if redis.call("get",KEYS[1]) == ARGV[1] then -- we are still holding the lock, release it redis.call("del",KEYS[1]) return "ok" else -- someone else holds the lock or it has expired return "stale" end EOL
- SET_WITH_TTL_IF_LOCK_STILL_HELD_SCRIPT =
See redis.io/topics/distlock as well as a rebuttal in martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
<<~EOL redis.replicate_commands() if redis.call("get", KEYS[1]) == ARGV[1] then -- we are still holding the lock, we can go ahead and set it redis.call("set", KEYS[2], ARGV[2], "px", ARGV[3]) return "ok" else return "stale" end EOL
Class Method Summary collapse
Instance Method Summary collapse
-
#initialize(redis_or_connection_pool = Redis.new) ⇒ RedisBackend
constructor
A new instance of RedisBackend.
- #prune! ⇒ Object
- #with_idempotency_key(request_key) ⇒ Object
Constructor Details
#initialize(redis_or_connection_pool = Redis.new) ⇒ RedisBackend
Returns a new instance of RedisBackend.
67 68 69 70 71 |
# File 'lib/idempo/redis_backend.rb', line 67 def initialize(redis_or_connection_pool = Redis.new) require "redis" require "securerandom" @redis_pool = redis_or_connection_pool.respond_to?(:with) ? redis_or_connection_pool : NullPool.new(redis_or_connection_pool) end |
Class Method Details
.eval_or_evalsha(redis, script_code, keys:, argv:) ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/idempo/redis_backend.rb', line 95 def self.eval_or_evalsha(redis, script_code, keys:, argv:) script_sha = Digest::SHA1.hexdigest(script_code) redis.evalsha(script_sha, keys: keys, argv: argv) rescue Redis::CommandError => e if e..include? "NOSCRIPT" # The Redis server has never seen this script before. Needs to run only once in the entire lifetime # of the Redis server, until the script changes - in which case it will be loaded under a different SHA redis.script(:load, script_code) retry else raise e end end |
Instance Method Details
#prune! ⇒ Object
91 92 93 |
# File 'lib/idempo/redis_backend.rb', line 91 def prune! # Do nothing end |
#with_idempotency_key(request_key) ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/idempo/redis_backend.rb', line 73 def with_idempotency_key(request_key) lock_key = "idempo:lock:#{request_key}" token = SecureRandom.bytes(32) did_acquire = @redis_pool.with { |r| r.set(lock_key, token, nx: true, ex: LOCK_TTL_SECONDS) } raise Idempo::ConcurrentRequest unless did_acquire begin store = Store.new(redis_pool: @redis_pool, lock_redis_key: lock_key, lock_token: token, key: request_key) yield(store) ensure outcome_of_del = @redis_pool.with do |r| Idempo::RedisBackend.eval_or_evalsha(r, DELETE_BY_KEY_AND_VALUE_SCRIPT, keys: [lock_key], argv: [token]) end Measurometer.increment_counter("idempo.redis_lock_state_when_releasing_lock", 1, state: outcome_of_del) end end |