Skip to content

Commit

Permalink
refactor: outer lock with locked!
Browse files Browse the repository at this point in the history
  • Loading branch information
Matteo Rossi committed Jan 29, 2025
1 parent dfb5d91 commit d765a5c
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 8 deletions.
2 changes: 1 addition & 1 deletion lib/rack/idempotency_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def handle_request!(request, env)
cached_response = request.cached_response!
return cached_response unless cached_response.nil?

app.call(env).tap { |response| request.cache!(response) }
request.locked! { app.call(env).tap { |response| request.cache!(response) } }
end
end
end
14 changes: 9 additions & 5 deletions lib/rack/idempotency_key/memory_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def get(key)
value = store[key]
return if value.nil?

if expired?(value[:added_at])
if expired?(value[:expires_at])
store.delete(key)
return
end
Expand All @@ -25,21 +25,25 @@ def get(key)
end
end

def set(key, value)
def set(key, value, ttl: expires_in)
mutex.synchronize do
raise Rack::IdempotencyKey::ConflictError if store.key?(key)

store[key] ||= { value: value, added_at: Time.now.utc }
store[key] ||= { value: value, expires_at: Time.now.utc + ttl }
store[key][:value]
end
end

def unset(key)
mutex.synchronize { store.delete(key) }
end

private

attr_reader :store, :expires_in, :mutex

def expired?(added_at)
Time.now.utc - added_at > expires_in
def expired?(expires_at)
Time.now.utc > expires_at
end
end
end
Expand Down
10 changes: 8 additions & 2 deletions lib/rack/idempotency_key/redis_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ def get(key)
raise Rack::IdempotencyKey::StoreError, "#{self.class}: #{e.message}"
end

def set(key, value)
def set(key, value, ttl: expires_in)
with_redis do |redis|
result = redis.set(namespaced_key(key), value, nx: true, ex: expires_in)
result = redis.set(namespaced_key(key), value, nx: true, ex: ttl)
raise Rack::IdempotencyKey::ConflictError unless result
end

Expand All @@ -31,6 +31,12 @@ def set(key, value)
raise Rack::IdempotencyKey::StoreError, "#{self.class}: #{e.message}"
end

def unset(key)
with_redis { |redis| redis.del(key) }
rescue Redis::BaseError => e
raise Rack::IdempotencyKey::StoreError, "#{self.class}: #{e.message}"
end

private

attr_reader :store, :expires_in
Expand Down
16 changes: 16 additions & 0 deletions lib/rack/idempotency_key/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module Rack
class IdempotencyKey
class Request
DEFAULT_LOCK_TTL = 60 # seconds

# @param request [Rack::Request]
# @param store [Store]
def initialize(request, store)
Expand All @@ -23,6 +25,16 @@ def cached_response!
end
end

def locked!
store.set(lock_key, 1, ttl: DEFAULT_LOCK_TTL)

begin
yield
ensure
store.unset(lock_key)
end
end

def cache!(response)
status, = response
store.set(cache_key, response) if status != 400
Expand Down Expand Up @@ -56,6 +68,10 @@ def cache_key
private

attr_reader :request, :store

def lock_key
"#{idempotency_key}_lock"
end
end
end
end

0 comments on commit d765a5c

Please sign in to comment.