From ceb1c7d0eae5aba6a5f7cf3e0b64dc5a7a7aefb6 Mon Sep 17 00:00:00 2001 From: Sam Eaton Date: Wed, 20 Jun 2018 19:56:26 -0700 Subject: [PATCH 1/3] add set_purge_interval method --- README.md | 16 +++++++++++ spec/cache_hash_spec.cr | 64 +++++++++++++++++++++++++++++++++++++---- src/cache_hash.cr | 24 ++++++++++++++++ 3 files changed, 99 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ae41c92..f206cd1 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,22 @@ Removes all key/value pairs from the hash. Removes all stale key/value pairs from the hash. +### `.set_purge_interval(interval : Time::Span, stale_only : Bool = true)` + +Sets an interval where key/value pairs will automatically be purged. + +**Example:** + +```ruby +cache_hash = CacheHash(String).new(1.minute) +cache_hash.set_purge_interval(10.minutes) # stale_only defaults to true +``` + +```ruby +cache_hash = CacheHash(String).new(1.minute, stale_only: false) +cache_hash.set_purge_interval(10.minutes) # deletes all values, not just stale ones +``` + ### `.keys() : Array(String)` Runs `purge_stale` and returns an array of all the the non-stale keys. diff --git a/spec/cache_hash_spec.cr b/spec/cache_hash_spec.cr index 8b2c475..8c10eab 100644 --- a/spec/cache_hash_spec.cr +++ b/spec/cache_hash_spec.cr @@ -165,14 +165,17 @@ describe CacheHash do describe "#time" do it "returns a time if the kv pair is not stale" do - hash = CacheHash(String).new(Time::Span.new(0, 0, 3)) - - t = Time.now + time_before = Time.now + sleep 1 + hash = CacheHash(String).new(3.seconds) hash.set "city_1", "Seattle" - hash.time("city_1").class.should eq(Time) + sleep 1 + time_after = Time.now city_1_time = hash.time("city_1").not_nil! - (city_1_time > t && city_1_time < Time.now).should be_true + city_1_time.class.should eq(Time) + (city_1_time > time_before).should be_true + (city_1_time < time_after).should be_true end it "delete the kv pair if it is stale" do @@ -278,4 +281,55 @@ describe CacheHash do hash.raw.empty?.should be_true end end + + describe "#set_purge_interval" do + it "purges stale values from hash" do + hash = CacheHash(String).new(4.seconds) + hash.set_purge_interval(3.seconds) + hash.set "city_1", "Seattle" + hash.set "city_2", "Hong Kong" + hash.set "city_3", "Sacramento" + + hash.get("city_1").should eq("Seattle") + hash.get("city_2").should eq("Hong Kong") + hash.get("city_3").should eq("Sacramento") + hash.raw.empty?.should be_false + sleep 4 + hash.get("city_1").should be_nil + hash.get("city_2").should be_nil + hash.get("city_3").should be_nil + hash.raw.empty?.should be_true + + hash.set "city_1", "Seattle" + sleep 2 + hash.set "city_2", "Hong Kong" + hash.get("city_1").should eq("Seattle") + hash.get("city_2").should eq("Hong Kong") + sleep 2 + hash.get("city_1").should be_nil + hash.get("city_2").should eq("Hong Kong") + sleep 2 + hash.get("city_1").should be_nil + hash.get("city_2").should be_nil + hash.raw.empty?.should be_true + end + + it "purges all values from hash if specified" do + hash = CacheHash(String).new(5.seconds) + hash.set_purge_interval(3.seconds, stale_only: false) + hash.set "city_1", "Seattle" + hash.set "city_2", "Hong Kong" + hash.set "city_3", "Sacramento" + + hash.get("city_1").should eq("Seattle") + hash.get("city_2").should eq("Hong Kong") + hash.get("city_3").should eq("Sacramento") + hash.raw.empty?.should be_false + sleep 3 + hash.get("city_1").should be_nil + hash.get("city_2").should be_nil + hash.get("city_3").should be_nil + hash.raw.empty?.should be_true + end + end end diff --git a/src/cache_hash.cr b/src/cache_hash.cr index f8e32fd..7d25057 100644 --- a/src/cache_hash.cr +++ b/src/cache_hash.cr @@ -2,6 +2,7 @@ class CacheHash(V) def initialize(@cache_time_span : Time::Span) @kv_hash = {} of String => V @time_hash = {} of String => Time + @is_purge_interval_running = false end def get(key : String) @@ -83,4 +84,27 @@ class CacheHash(V) end end end + + def set_purge_interval(interval : Time::Span, stale_only = true) + @purge_interval = interval + @purge_interval_stale_only = stale_only + unless @is_purge_interval_running + spawn do + run_purge_interval_loop + end + end + end + + private def run_purge_interval_loop + stale_only = @purge_interval_stale_only + if (interval = @purge_interval).is_a?(Time::Span) + if stale_only + purge_stale + else + purge + end + sleep interval.as(Time::Span) + run_purge_interval_loop + end + end end From 52d0eec883903441d0c204d1935b341447595d9d Mon Sep 17 00:00:00 2001 From: Sam Eaton Date: Wed, 20 Jun 2018 20:01:48 -0700 Subject: [PATCH 2/3] fixes set_purge_interval example --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f206cd1..37daa7f 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,8 @@ cache_hash.set_purge_interval(10.minutes) # stale_only defaults to true ``` ```ruby -cache_hash = CacheHash(String).new(1.minute, stale_only: false) -cache_hash.set_purge_interval(10.minutes) # deletes all values, not just stale ones +cache_hash = CacheHash(String).new(1.minute) +cache_hash.set_purge_interval(10.minutes, stale_only: false) # deletes all values, not just stale ones ``` ### `.keys() : Array(String)` From 12db97e91c52864307f8d6ba44c5eb4f3b00c483 Mon Sep 17 00:00:00 2001 From: Sam Eaton Date: Thu, 21 Jun 2018 09:12:41 -0700 Subject: [PATCH 3/3] uses loop instead of recursion --- spec/cache_hash_spec.cr | 2 +- src/cache_hash.cr | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/spec/cache_hash_spec.cr b/spec/cache_hash_spec.cr index 8c10eab..c8ed086 100644 --- a/spec/cache_hash_spec.cr +++ b/spec/cache_hash_spec.cr @@ -325,7 +325,7 @@ describe CacheHash do hash.get("city_2").should eq("Hong Kong") hash.get("city_3").should eq("Sacramento") hash.raw.empty?.should be_false - sleep 3 + sleep 4 hash.get("city_1").should be_nil hash.get("city_2").should be_nil hash.get("city_3").should be_nil diff --git a/src/cache_hash.cr b/src/cache_hash.cr index 7d25057..dc8f024 100644 --- a/src/cache_hash.cr +++ b/src/cache_hash.cr @@ -96,15 +96,19 @@ class CacheHash(V) end private def run_purge_interval_loop - stale_only = @purge_interval_stale_only - if (interval = @purge_interval).is_a?(Time::Span) - if stale_only - purge_stale - else - purge + return if @is_purge_interval_running + + @is_purge_interval_running = true + loop do + stale_only = @purge_interval_stale_only + if (interval = @purge_interval).is_a?(Time::Span) + sleep interval.as(Time::Span) + if stale_only + purge_stale + else + purge + end end - sleep interval.as(Time::Span) - run_purge_interval_loop end end end