Skip to content

Commit

Permalink
Add bypass option
Browse files Browse the repository at this point in the history
Allows to bypass caching, locking or both.
  • Loading branch information
m-o-e committed Mar 1, 2019
1 parent e67878f commit 5f6025c
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 8 deletions.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,26 @@ Check locks with
LockAndCache.locked? :stock_price, company: 'MSFT', date: '2015-05-05'
```

Caching, locking or both can be bypassed

```ruby
LockAndCache.lock_and_cache(:stock_price, {company: 'MSFT', date: '2015-05-05'}, expires: 10, nil_expires: 1, bypass: :cache) do
# ignore any cached value, get yer stock quote
end
```

```ruby
LockAndCache.lock_and_cache(:stock_price, {company: 'MSFT', date: '2015-05-05'}, expires: 10, nil_expires: 1, bypass: :lock) do
# return cached value if it exists, otherwise get yet stock quote *without* acquiring lock
end
```

```ruby
LockAndCache.lock_and_cache(:stock_price, {company: 'MSFT', date: '2015-05-05'}, expires: 10, nil_expires: 1, bypass: :both) do
# get yer stock quote without caching or locking
end
```

#### Context mode

"Context mode" simply adds the class name, method name, and context key (the results of `#id` or `#lock_and_cache_key`) of the caller to the cache key.
Expand Down Expand Up @@ -234,6 +254,6 @@ You can expire nil values with a different timeout (`nil_expires`) than other va
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request

# Copyright
# Copyright

Copyright 2015 Seamus Abshere
20 changes: 14 additions & 6 deletions lib/lock_and_cache/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ def nil_expires
@nil_expires = options.has_key?('nil_expires') ? options['nil_expires'].to_f.round : nil
end

def bypass
return @bypass if defined?(@bypass)
@bypass = options.has_key?('bypass') ? options['bypass'] : nil
end

def digest
@digest ||= key.digest
end
Expand Down Expand Up @@ -55,21 +60,24 @@ 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 cache_storage.exists(digest) and (existing = cache_storage.get(digest)).is_a?(String)
if ![:cache, :both].include?(bypass) and 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}" }
retval = nil
lock_secret = SecureRandom.hex 16
acquired = false
begin
Timeout.timeout(max_lock_wait, TimeoutWaitingForLock) do
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
unless [:lock, :both].include?(bypass)
Timeout.timeout(max_lock_wait, TimeoutWaitingForLock) do
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
acquired = true
end
return blk.call if [:cache, :both].include?(bypass)
LockAndCache.logger.debug { "[lock_and_cache] D1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
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}" }
Expand Down
23 changes: 22 additions & 1 deletion spec/lock_and_cache_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,27 @@ def lock_and_cache_key
expect(LockAndCache.lock_and_cache('hello') { raise(Exception.new("stop")) }).to eq(:red)
end

it 'doesn\'t break when bypass has an unknown value' do
expect(LockAndCache.lock_and_cache('hello', bypass: nil) { :red }).to eq(:red)
expect(LockAndCache.lock_and_cache('hello', bypass: :foo) { raise(Exception.new("stop")) }).to eq(:red)
end

it 'doesn\'t cache when bypass == :cache' do
count = 0
expect(LockAndCache.lock_and_cache('hello') { count += 1 }).to eq(1)
expect(count).to eq(1)
expect(LockAndCache.lock_and_cache('hello', bypass: :cache) { count += 1 }).to eq(2)
expect(count).to eq(2)
end

it 'doesn\'t cache when bypass == :both' do
count = 0
expect(LockAndCache.lock_and_cache('hello') { count += 1 }).to eq(1)
expect(count).to eq(1)
expect(LockAndCache.lock_and_cache('hello', bypass: :both) { count += 1 }).to eq(2)
expect(count).to eq(2)
end

it 'caches errors (briefly)' do
count = 0
expect {
Expand Down Expand Up @@ -434,7 +455,7 @@ def lock_and_cache_key
expect(LockAndCache.lock_and_cache(['hello', 1, { target: 'world' }]) { count += 1 }).to eq(1)
expect(count).to eq(1)
end

it 'treats a single hash arg as a cache key (not as options)' do
count = 0
LockAndCache.lock_and_cache(hello: 'world', expires: 100) { count += 1 }
Expand Down

0 comments on commit 5f6025c

Please sign in to comment.