Skip to content

Commit

Permalink
v6.0.0 - set {lock,cache}_storage= separately
Browse files Browse the repository at this point in the history
  • Loading branch information
seamusabshere committed Sep 20, 2018
1 parent ba1f794 commit e67878f
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 33 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
6.0.0

* Breaking changes

* Set lock_storage and cache_storage separately

5.0.0

* Enhancements / breaking changes
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Most caching libraries don't do locking, meaning that >1 process can be calculat
## Quickstart

```ruby
LockAndCache.storage = Redis.new
LockAndCache.lock_storage = Redis.new db: 3
LockAndCache.cache_storage = Redis.new db: 4

LockAndCache.lock_and_cache(:stock_price, {company: 'MSFT', date: '2015-05-05'}, expires: 10, nil_expires: 1) do
# get yer stock quote
Expand Down Expand Up @@ -76,7 +77,8 @@ If an error is raised during calculation, that error is propagated to all waiter
### Setup

```ruby
LockAndCache.storage = Redis.new
LockAndCache.lock_storage = Redis.new db: 3
LockAndCache.cache_storage = Redis.new db: 4
```

It will use this redis for both locking and storing cached values.
Expand Down Expand Up @@ -204,7 +206,8 @@ You can expire nil values with a different timeout (`nil_expires`) than other va

## Tunables

* `LockAndCache.storage=[redis]`
* `LockAndCache.lock_storage=[redis]`
* `LockAndCache.cache_storage=[redis]`
* `ENV['LOCK_AND_CACHE_DEBUG']='true'` if you want some debugging output on `$stderr`

## Few dependencies
Expand Down
36 changes: 28 additions & 8 deletions lib/lock_and_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,26 @@ module LockAndCache

class TimeoutWaitingForLock < StandardError; end

# @param redis_connection [Redis] A redis connection to be used for lock and cached value storage
def LockAndCache.storage=(redis_connection)
# @param redis_connection [Redis] A redis connection to be used for lock storage
def LockAndCache.lock_storage=(redis_connection)
raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
@storage = redis_connection
@lock_storage = redis_connection
end

# @return [Redis] The redis connection used for lock and cached value storage
def LockAndCache.storage
@storage
def LockAndCache.lock_storage
@lock_storage
end

# @param redis_connection [Redis] A redis connection to be used for cached value storage
def LockAndCache.cache_storage=(redis_connection)
raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
@cache_storage = redis_connection
end

# @return [Redis] The redis connection used for cached value storage
def LockAndCache.cache_storage
@cache_storage
end

# @param logger [Logger] A logger.
Expand All @@ -41,13 +52,22 @@ def LockAndCache.logger
@logger
end

# Flush LockAndCache's storage.
# Flush LockAndCache's cached value storage.
#
# @note If you are sharing a redis database, it will clear it...
#
# @note If you want to clear a single key, try `LockAndCache.clear(key)` (standalone mode) or `#lock_and_cache_clear(method_id, *key_parts)` in context mode.
def LockAndCache.flush_cache
cache_storage.flushdb
end

# Flush LockAndCache's lock storage.
#
# @note If you are sharing a redis database, it will clear it...
#
# @note If you want to clear a single key, try `LockAndCache.clear(key)` (standalone mode) or `#lock_and_cache_clear(method_id, *key_parts)` in context mode.
def LockAndCache.flush
storage.flushdb
def LockAndCache.flush_locks
lock_storage.flushdb
end

# Lock and cache based on a key.
Expand Down
32 changes: 18 additions & 14 deletions lib/lock_and_cache/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ def lock_digest
@lock_digest ||= key.lock_digest
end

def storage
@storage ||= LockAndCache.storage or raise("must set LockAndCache.storage=[Redis]")
def lock_storage
@lock_storage ||= LockAndCache.lock_storage or raise("must set LockAndCache.lock_storage=[Redis]")
end

def cache_storage
@cache_storage ||= LockAndCache.cache_storage or raise("must set LockAndCache.cache_storage=[Redis]")
end

def load_existing(existing)
Expand All @@ -51,7 +55,7 @@ def perform
raise "heartbeat_expires must be >= 2 seconds" unless heartbeat_expires >= 2
heartbeat_frequency = (heartbeat_expires / 2).ceil
LockAndCache.logger.debug { "[lock_and_cache] A1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
if storage.exists(digest) and (existing = storage.get(digest)).is_a?(String)
if cache_storage.exists(digest) and (existing = cache_storage.get(digest)).is_a?(String)
return load_existing(existing)
end
LockAndCache.logger.debug { "[lock_and_cache] B1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
Expand All @@ -60,14 +64,14 @@ def perform
acquired = false
begin
Timeout.timeout(max_lock_wait, TimeoutWaitingForLock) do
until storage.set(lock_digest, lock_secret, nx: true, ex: heartbeat_expires)
until lock_storage.set(lock_digest, lock_secret, nx: true, ex: heartbeat_expires)
LockAndCache.logger.debug { "[lock_and_cache] C1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
sleep rand
end
acquired = true
end
LockAndCache.logger.debug { "[lock_and_cache] D1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
if storage.exists(digest) and (existing = storage.get(digest)).is_a?(String)
if cache_storage.exists(digest) and (existing = cache_storage.get(digest)).is_a?(String)
LockAndCache.logger.debug { "[lock_and_cache] E1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
retval = load_existing existing
end
Expand All @@ -83,8 +87,8 @@ def perform
break if done
LockAndCache.logger.debug { "[lock_and_cache] heartbeat2 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
# FIXME use lua to check the value
raise "unexpectedly lost lock for #{key.debug}" unless storage.get(lock_digest) == lock_secret
storage.set lock_digest, lock_secret, xx: true, ex: heartbeat_expires
raise "unexpectedly lost lock for #{key.debug}" unless lock_storage.get(lock_digest) == lock_secret
lock_storage.set lock_digest, lock_secret, xx: true, ex: heartbeat_expires
end
end
begin
Expand All @@ -100,32 +104,32 @@ def perform
end
end
ensure
storage.del lock_digest if acquired
lock_storage.del lock_digest if acquired
end
retval
end

def set_error(exception)
storage.set digest, ::Marshal.dump(ERROR_MAGIC_KEY => exception.message), ex: 1
cache_storage.set digest, ::Marshal.dump(ERROR_MAGIC_KEY => exception.message), ex: 1
end

NIL = Marshal.dump nil
def set_nil
if nil_expires
storage.set digest, NIL, ex: nil_expires
cache_storage.set digest, NIL, ex: nil_expires
elsif expires
storage.set digest, NIL, ex: expires
cache_storage.set digest, NIL, ex: expires
else
storage.set digest, NIL
cache_storage.set digest, NIL
end
end

def set_non_nil(retval)
raise "expected not null #{retval.inspect}" if retval.nil?
if expires
storage.set digest, ::Marshal.dump(retval), ex: expires
cache_storage.set digest, ::Marshal.dump(retval), ex: expires
else
storage.set digest, ::Marshal.dump(retval)
cache_storage.set digest, ::Marshal.dump(retval)
end
end
end
Expand Down
9 changes: 4 additions & 5 deletions lib/lock_and_cache/key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,17 @@ def key
end

def locked?
LockAndCache.storage.exists lock_digest
LockAndCache.lock_storage.exists lock_digest
end

def cached?
LockAndCache.storage.exists digest
LockAndCache.cache_storage.exists digest
end

def clear
LockAndCache.logger.debug { "[lock_and_cache] clear #{debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
storage = LockAndCache.storage
storage.del digest
storage.del lock_digest
LockAndCache.cache_storage.del digest
LockAndCache.lock_storage.del lock_digest
end

alias debug key
Expand Down
2 changes: 1 addition & 1 deletion lib/lock_and_cache/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module LockAndCache
VERSION = '5.0.0'
VERSION = '6.0.0'
end
3 changes: 2 additions & 1 deletion spec/lock_and_cache_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ def lock_and_cache_key

describe LockAndCache do
before do
LockAndCache.flush
LockAndCache.flush_locks
LockAndCache.flush_cache
end

it 'has a version number' do
Expand Down
3 changes: 2 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
require 'timeout'

require 'redis'
LockAndCache.storage = Redis.new
LockAndCache.lock_storage = Redis.new db: 3
LockAndCache.cache_storage = Redis.new db: 4

require 'thread/pool'

Expand Down

0 comments on commit e67878f

Please sign in to comment.