Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bypass option #11

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, 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, bypass: :lock) do
# return cached value if it exists, otherwise get yer stock quote *without* acquiring lock
end
```

```ruby
LockAndCache.lock_and_cache(:stock_price, {company: 'MSFT', date: '2015-05-05'}, expires: 10, 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