From 9e05aaac49cdf6a070451ceb9f44870977dd690e Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Wed, 3 Jun 2015 17:34:48 -0500 Subject: [PATCH 1/3] Add cleanup block to FastCache::Cache, with RSpec tests. --- lib/fast_cache/cache.rb | 12 +++++++-- spec/lib/fast_cache/cache_spec.rb | 45 +++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/lib/fast_cache/cache.rb b/lib/fast_cache/cache.rb index f8153b7..790d4ab 100644 --- a/lib/fast_cache/cache.rb +++ b/lib/fast_cache/cache.rb @@ -39,6 +39,7 @@ def initialize(max_size, ttl, expire_interval = 100) @op_count = 0 @data = {} @expires_at = {} + @cleanup = Proc.new if block_given? end # Retrieves a value from the cache, if available and not expired, or @@ -84,6 +85,7 @@ def delete(key) entry = @data.delete(key) if entry @expires_at.delete(entry) + @cleanup.call(entry.value) if @cleanup entry.value else nil @@ -103,6 +105,7 @@ def empty? # # @return [self] def clear + @data.values.map(&:value).map(&@cleanup) if @cleanup @data.clear @expires_at.clear self @@ -176,6 +179,7 @@ def get(key) if found if entry.expires_at <= t @expires_at.delete(entry) + @cleanup.call(entry.value) if @cleanup return false, nil else @data[key] = entry @@ -194,7 +198,9 @@ def store(key, val) end def store_entry(key, entry) - @data.delete(key) + found = true + old_entry = @data.delete(key) { found = false } + @cleanup.call(old_entry.value) if @cleanup && found @data[key] = entry @expires_at[entry] = key shrink_if_needed @@ -204,6 +210,7 @@ def shrink_if_needed if @data.length > @max_size entry = delete(@data.shift) @expires_at.delete(entry) + @cleanup.call(entry.value) if @cleanup end end @@ -212,7 +219,8 @@ def check_expired(t) while (key_value_pair = @expires_at.first) && (entry = key_value_pair.first).expires_at <= t key = @expires_at.delete(entry) - @data.delete(key) + entry = @data.delete(key) + @cleanup.call(entry.value) if @cleanup end end end diff --git a/spec/lib/fast_cache/cache_spec.rb b/spec/lib/fast_cache/cache_spec.rb index 4769ec7..995eecf 100644 --- a/spec/lib/fast_cache/cache_spec.rb +++ b/spec/lib/fast_cache/cache_spec.rb @@ -15,14 +15,19 @@ end end - context 'non-empty cache' do + shared_context :non_empty_cache do + let(:cache) { described_class.new(3, 60, 1) } before do - @cache = described_class.new(3, 60, 1) + @cache = cache @cache[:a] = 1 @cache[:b] = 2 @cache[:c] = 3 end subject { @cache } + end + + context 'non-empty cache' do + include_context :non_empty_cache its(:empty?) { should be_false } its(:length) { should eq 3 } @@ -112,6 +117,42 @@ subject[:e].should eq 6 end end + + describe 'cleanup block' do + let(:cleanups) { [] } + let(:ttl) { 60 } + let(:cache) { described_class.new(3, ttl, 1) do |obj| + cleanups << obj + end } + it 'is called when the cache is cleared' do + subject.clear + cleanups.should =~ [1,2,3] + end + it 'is called when an item is deleted' do + subject.delete(:a).should eq 1 + cleanups.should =~ [1] + end + it 'is called when an existing item is replaced' do + subject[:a] = 11 + cleanups.should =~ [1] + end + it 'is called when an item is removed when full' do + subject[:d] = 4 + cleanups.should =~ [1] + end + + context 'with immediate expiration' do + let(:ttl) { 0 } + it 'is called when items are expired' do + subject.expire! + cleanups.should =~ [1,2,3] + end + it 'is called when item access triggers expiration' do + subject[:a].should be_nil + cleanups.should =~ [1,2,3] + end + end + end end describe 'TTL behaviors' do From b2ab831ca8bb424080b1933a7b3f4728c6a6ada3 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Sun, 15 Nov 2015 17:13:33 -0600 Subject: [PATCH 2/3] use &cleanup to name the cleanup block, add YARD comment --- lib/fast_cache/cache.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/fast_cache/cache.rb b/lib/fast_cache/cache.rb index 790d4ab..3b52b30 100644 --- a/lib/fast_cache/cache.rb +++ b/lib/fast_cache/cache.rb @@ -32,14 +32,18 @@ class Cache # the cache. # @param [Integer] expire_interval Number of cache operations between # calls to {#expire!}. - def initialize(max_size, ttl, expire_interval = 100) + # @yield [Object] If a block is given, each time an object is removed + # from the cache, it will be yielded to the block. This + # is useful for cleaning up resources used by objects + # stored in the cache. + def initialize(max_size, ttl, expire_interval = 100, &cleanup) @max_size = max_size @ttl = ttl.to_f @expire_interval = expire_interval @op_count = 0 @data = {} @expires_at = {} - @cleanup = Proc.new if block_given? + @cleanup = cleanup end # Retrieves a value from the cache, if available and not expired, or From e4feb40c2c9b773c4771b03a6af4e288cc8a23fc Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Sun, 15 Nov 2015 17:19:25 -0600 Subject: [PATCH 3/3] use #each_value for greater efficiency --- lib/fast_cache/cache.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fast_cache/cache.rb b/lib/fast_cache/cache.rb index 3b52b30..009bd9d 100644 --- a/lib/fast_cache/cache.rb +++ b/lib/fast_cache/cache.rb @@ -109,7 +109,7 @@ def empty? # # @return [self] def clear - @data.values.map(&:value).map(&@cleanup) if @cleanup + @data.each_value {|entry| @cleanup.call(entry.value)} if @cleanup @data.clear @expires_at.clear self