diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3a3ec5..c19ccc12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.6.4 + +- Add `cache_refresh_period` to allow for cache refresh after a period of time. (#579) + ## 1.6.3 - Split the `with_deferred_parent_expiration` and `with_deferred_parent_expiration`. (#578) diff --git a/Gemfile.lock b/Gemfile.lock index 8b4f6d11..337b3be9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -93,6 +93,8 @@ GEM PLATFORMS arm64-darwin-22 arm64-darwin-23 + arm64-darwin-24 + x64-mingw-ucrt x86_64-linux DEPENDENCIES diff --git a/lib/identity_cache/cache_fetcher.rb b/lib/identity_cache/cache_fetcher.rb index 3573a628..ecb543c0 100644 --- a/lib/identity_cache/cache_fetcher.rb +++ b/lib/identity_cache/cache_fetcher.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true +require "time" require "securerandom" module IdentityCache + mattr_accessor :cache_refresh_period + self.cache_refresh_period = 60 + class CacheFetcher attr_accessor :cache_backend @@ -93,7 +97,10 @@ def fetch_without_fill_lock(key) data = nil upsert(key) do |value| value = nil if value == IdentityCache::DELETED || FillLock.cache_value?(value) - unless value.nil? + unless value.nil? || + (value&.is_a?(Hash) && + value[:cached_at] && + ((Time.now - value[:cached_at]) / 60).round > IdentityCache.cache_refresh_period) return value end @@ -316,6 +323,10 @@ def add_multi(keys) def add(key, value, expiration_options = EMPTY_HASH) return false unless IdentityCache.should_fill_cache? + if value&.is_a?(Hash) + value.merge(cached_at: Time.now.to_s) + end + @cache_backend.write(key, value, { unless_exist: true, **expiration_options }) end end diff --git a/test/cache_fetcher_test.rb b/test/cache_fetcher_test.rb index dcb0b4b6..7ca97020 100644 --- a/test/cache_fetcher_test.rb +++ b/test/cache_fetcher_test.rb @@ -177,3 +177,31 @@ def other_cache_fetcher @other_cache_fetcher ||= IdentityCache::CacheFetcher.new(backend) end end + +def test_fetch_without_fill_lock_with_stale_cached_data + identity_cache = IdentityCache + identity_cache.cache_refresh_period = 60 + stale_time = Time.now - (identity_cache.cache_refresh_period + 1) * 60 + backend.write(key, { cached_at: stale_time, data: :old_data }) + assert_memcache_operations(2) do + assert_equal(:new_data, cache_fetcher.fetch_without_fill_lock(key) { :new_data }) + end +end + +def test_fetch_without_fill_lock_with_fresh_cached_data + identity_cache = IdentityCache + identity_cache.cache_refresh_period = 60 + fresh_time = Time.now - (identity_cache.cache_refresh_period - 1) * 60 + cached_data = { cached_at: fresh_time, data: :fresh_data } + backend.write(key, cached_data) + assert_memcache_operations(1) do + assert_equal(cached_data, cache_fetcher.fetch_without_fill_lock(key) { :new_data }) + end +end + +def test_fetch_without_fill_lock_with_non_hash_value + backend.write(key, "string_value") + assert_memcache_operations(1) do + assert_equal("string_value", cache_fetcher.fetch_without_fill_lock(key) { :new_data }) + end +end diff --git a/test/helpers/database_connection.rb b/test/helpers/database_connection.rb index 297a8574..eb0e1ffe 100644 --- a/test/helpers/database_connection.rb +++ b/test/helpers/database_connection.rb @@ -79,6 +79,7 @@ def create_tables "host" => ENV["MYSQL_HOST"] || "127.0.0.1", "username" => "root", "port" => ENV["MYSQL_PORT"] ? Integer(ENV["MYSQL_PORT"]) : 3306, + "password" => ENV["MYSQL_PASSWORD"] || "my-secret-pw", }, "postgresql" => { "adapter" => "postgresql",