Skip to content

Commit 5f6025c

Browse files
committed
Add bypass option
Allows to bypass caching, locking or both.
1 parent e67878f commit 5f6025c

File tree

3 files changed

+57
-8
lines changed

3 files changed

+57
-8
lines changed

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,26 @@ Check locks with
150150
LockAndCache.locked? :stock_price, company: 'MSFT', date: '2015-05-05'
151151
```
152152

153+
Caching, locking or both can be bypassed
154+
155+
```ruby
156+
LockAndCache.lock_and_cache(:stock_price, {company: 'MSFT', date: '2015-05-05'}, expires: 10, nil_expires: 1, bypass: :cache) do
157+
# ignore any cached value, get yer stock quote
158+
end
159+
```
160+
161+
```ruby
162+
LockAndCache.lock_and_cache(:stock_price, {company: 'MSFT', date: '2015-05-05'}, expires: 10, nil_expires: 1, bypass: :lock) do
163+
# return cached value if it exists, otherwise get yet stock quote *without* acquiring lock
164+
end
165+
```
166+
167+
```ruby
168+
LockAndCache.lock_and_cache(:stock_price, {company: 'MSFT', date: '2015-05-05'}, expires: 10, nil_expires: 1, bypass: :both) do
169+
# get yer stock quote without caching or locking
170+
end
171+
```
172+
153173
#### Context mode
154174

155175
"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.
@@ -234,6 +254,6 @@ You can expire nil values with a different timeout (`nil_expires`) than other va
234254
4. Push to the branch (`git push origin my-new-feature`)
235255
5. Create a new Pull Request
236256

237-
# Copyright
257+
# Copyright
238258

239259
Copyright 2015 Seamus Abshere

lib/lock_and_cache/action.rb

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ def nil_expires
2424
@nil_expires = options.has_key?('nil_expires') ? options['nil_expires'].to_f.round : nil
2525
end
2626

27+
def bypass
28+
return @bypass if defined?(@bypass)
29+
@bypass = options.has_key?('bypass') ? options['bypass'] : nil
30+
end
31+
2732
def digest
2833
@digest ||= key.digest
2934
end
@@ -55,21 +60,24 @@ def perform
5560
raise "heartbeat_expires must be >= 2 seconds" unless heartbeat_expires >= 2
5661
heartbeat_frequency = (heartbeat_expires / 2).ceil
5762
LockAndCache.logger.debug { "[lock_and_cache] A1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
58-
if cache_storage.exists(digest) and (existing = cache_storage.get(digest)).is_a?(String)
63+
if ![:cache, :both].include?(bypass) and cache_storage.exists(digest) and (existing = cache_storage.get(digest)).is_a?(String)
5964
return load_existing(existing)
6065
end
6166
LockAndCache.logger.debug { "[lock_and_cache] B1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
6267
retval = nil
6368
lock_secret = SecureRandom.hex 16
6469
acquired = false
6570
begin
66-
Timeout.timeout(max_lock_wait, TimeoutWaitingForLock) do
67-
until lock_storage.set(lock_digest, lock_secret, nx: true, ex: heartbeat_expires)
68-
LockAndCache.logger.debug { "[lock_and_cache] C1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
69-
sleep rand
71+
unless [:lock, :both].include?(bypass)
72+
Timeout.timeout(max_lock_wait, TimeoutWaitingForLock) do
73+
until lock_storage.set(lock_digest, lock_secret, nx: true, ex: heartbeat_expires)
74+
LockAndCache.logger.debug { "[lock_and_cache] C1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
75+
sleep rand
76+
end
77+
acquired = true
7078
end
71-
acquired = true
7279
end
80+
return blk.call if [:cache, :both].include?(bypass)
7381
LockAndCache.logger.debug { "[lock_and_cache] D1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
7482
if cache_storage.exists(digest) and (existing = cache_storage.get(digest)).is_a?(String)
7583
LockAndCache.logger.debug { "[lock_and_cache] E1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }

spec/lock_and_cache_spec.rb

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,27 @@ def lock_and_cache_key
343343
expect(LockAndCache.lock_and_cache('hello') { raise(Exception.new("stop")) }).to eq(:red)
344344
end
345345

346+
it 'doesn\'t break when bypass has an unknown value' do
347+
expect(LockAndCache.lock_and_cache('hello', bypass: nil) { :red }).to eq(:red)
348+
expect(LockAndCache.lock_and_cache('hello', bypass: :foo) { raise(Exception.new("stop")) }).to eq(:red)
349+
end
350+
351+
it 'doesn\'t cache when bypass == :cache' do
352+
count = 0
353+
expect(LockAndCache.lock_and_cache('hello') { count += 1 }).to eq(1)
354+
expect(count).to eq(1)
355+
expect(LockAndCache.lock_and_cache('hello', bypass: :cache) { count += 1 }).to eq(2)
356+
expect(count).to eq(2)
357+
end
358+
359+
it 'doesn\'t cache when bypass == :both' do
360+
count = 0
361+
expect(LockAndCache.lock_and_cache('hello') { count += 1 }).to eq(1)
362+
expect(count).to eq(1)
363+
expect(LockAndCache.lock_and_cache('hello', bypass: :both) { count += 1 }).to eq(2)
364+
expect(count).to eq(2)
365+
end
366+
346367
it 'caches errors (briefly)' do
347368
count = 0
348369
expect {
@@ -434,7 +455,7 @@ def lock_and_cache_key
434455
expect(LockAndCache.lock_and_cache(['hello', 1, { target: 'world' }]) { count += 1 }).to eq(1)
435456
expect(count).to eq(1)
436457
end
437-
458+
438459
it 'treats a single hash arg as a cache key (not as options)' do
439460
count = 0
440461
LockAndCache.lock_and_cache(hello: 'world', expires: 100) { count += 1 }

0 commit comments

Comments
 (0)