From de1ddd54a49e4ec96e428259db3016ce875b1891 Mon Sep 17 00:00:00 2001 From: "Kent Dahl @ Sixty" Date: Fri, 28 Aug 2015 18:41:16 +0200 Subject: [PATCH 1/6] Option to serve stale content for a cached_include in case of errors. --- lib/lesmok/config.rb | 2 + lib/lesmok/tags/cached_include.rb | 71 ++++++++++++++++++++----------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/lib/lesmok/config.rb b/lib/lesmok/config.rb index ba0aa41..a7ea7fc 100644 --- a/lib/lesmok/config.rb +++ b/lib/lesmok/config.rb @@ -3,10 +3,12 @@ class Config attr_accessor :logger attr_accessor :cache attr_accessor :caching_enabled + attr_accessor :serve_stale_content attr_accessor :available_cache_stores attr_accessor :debugging_enabled attr_accessor :raise_errors_enabled + alias :serve_stale_content? :serve_stale_content alias :raise_errors? :raise_errors_enabled alias :debugging? :debugging_enabled diff --git a/lib/lesmok/tags/cached_include.rb b/lib/lesmok/tags/cached_include.rb index a9b475b..cda8edf 100644 --- a/lib/lesmok/tags/cached_include.rb +++ b/lib/lesmok/tags/cached_include.rb @@ -12,52 +12,73 @@ class CachedInclude < ::Liquid::Include include ExpiryCalculation extend GlobalKeyHandling + STALE_BREAD_KEY_SUFFIX = ':LESMOK-STALE-BREAD' + def render(context) return super unless fragment_caching_enabled? - cached_on_obj = context[@attributes['cache_on']] - cache_val = cached_on_obj && cached_on_obj.respond_to?(:cache_key) && cached_on_obj.cache_key - cache_val ||= context[@attributes['cache_key']] - - template_name = context[@template_name] - - ## Catch cases where cached_include is used incorrectly. - if cache_val.blank? - if Lesmok.config.debugging? - Lesmok.logger.warn "[#{self.class}] No valid cache key given for '#{template_name}' template!" - Lesmok.logger.debug " -- No cache key given nor found for object: #{cached_on_obj.inspect.truncate(64)}" - end - if Lesmok.config.raise_errors? - raise ArgumentError.new("No valid cache key! given for '#{template_name}' template!") - end - end + cache_val = calculate_cache_key_for(context) return super unless cache_val.present? - ## Allow sub-scoping w/o manually creating cache key. - cache_subscope = context[@attributes['cache_scope']] - cache_val += ":SUBSCOPE-#{cache_subscope}" if cache_subscope.present? + template_name = context[@template_name] - expire_in = calculate_expiry(cached_on_obj, context[@attributes['expire_in']]) + expire_in = calculate_expiry(context[@attributes['cache_on']], context[@attributes['expire_in']]) cache_key = self.class.full_cache_key_for(cache_val, template_name) cache_store = select_cache_store_for(context) + Lesmok.logger.debug "[#{self.class}] Lookup #{cache_key} in #{cache_store}..." if Lesmok.config.debugging? result = cache_store.fetch(cache_key, expires_in: expire_in) do Lesmok.logger.debug "[#{self.class}] --- cache miss on #{cache_key} in #{cache_store}!" if Lesmok.config.debugging? - super + rendered_str = super + if Lesmok.config.serve_stale_content? && rendered_str.present? + stale_cache_store = select_cache_store_for(context, :stale) + stale_cache_store.set(cache_key + STALE_BREAD_KEY_SUFFIX, rendered_str, expires_in: nil) + end + rendered_str end if context.errors.present? - ::Lesmok.logger.debug " -- Liquid errors (#{context.errors.size}) seen in: #{@template_name}" + ::Lesmok.logger.debug "[lesmok] -- Liquid errors (#{context.errors.size}) seen in: #{template_name}" end result rescue Exception => err log_exception(err, context) - "" + if Lesmok.config.serve_stale_content? && cache_key.present? + stale_cache_store = select_cache_store_for(context, :stale) + + stale = stale_cache_store.get(cache_key + STALE_BREAD_KEY_SUFFIX) + ::Lesmok.logger.warn "[lesmok] Serving stale content in: #{template_name} [#{cache_key}]" if stale.present? + stale + else + "" + end + end + + def calculate_cache_key_for(context) + cached_on_obj = context[@attributes['cache_on']] + cache_val = cached_on_obj && cached_on_obj.respond_to?(:cache_key) && cached_on_obj.cache_key + cache_val ||= context[@attributes['cache_key']] + + ## Catch cases where cached_include is used incorrectly. + if cache_val.blank? + template_name = context[@template_name] + if Lesmok.config.debugging? + Lesmok.logger.warn "[#{self.class}] No valid cache key given for '#{template_name}' template!" + Lesmok.logger.debug " -- No cache key given nor found for object: #{cached_on_obj.inspect.truncate(64)}" + end + if Lesmok.config.raise_errors? + raise ArgumentError.new("No valid cache key! given for '#{template_name}' template!") + end + end + ## Allow sub-scoping w/o manually creating cache key. + cache_subscope = context[@attributes['cache_scope']] + cache_val += ":SUBSCOPE-#{cache_subscope}" if cache_subscope.present? + cache_val end - def select_cache_store_for(context) - cache_store_name = context[@attributes['cache_store']] + def select_cache_store_for(context, fallback_store_name = nil) + cache_store_name = context[@attributes['cache_store']] || fallback_store_name cache_store = Lesmok.config.find_cache_store(cache_store_name) end From c55524a671f6af5d09a65a9c1d6f0a219e63b1b0 Mon Sep 17 00:00:00 2001 From: "Kent Dahl @ Sixty" Date: Fri, 28 Aug 2015 18:56:29 +0200 Subject: [PATCH 2/6] Refactoring CachedInclude#render for some more readability. --- lib/lesmok/tags/cached_include.rb | 51 +++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/lib/lesmok/tags/cached_include.rb b/lib/lesmok/tags/cached_include.rb index cda8edf..4305a85 100644 --- a/lib/lesmok/tags/cached_include.rb +++ b/lib/lesmok/tags/cached_include.rb @@ -26,9 +26,12 @@ def render(context) cache_key = self.class.full_cache_key_for(cache_val, template_name) cache_store = select_cache_store_for(context) - Lesmok.logger.debug "[#{self.class}] Lookup #{cache_key} in #{cache_store}..." if Lesmok.config.debugging? + result = perform_cached_inclusion_rendering_for(context, cache_store, cache_key) do + super + end + lesmok_logger.debug "[#{self.class}] Lookup #{cache_key} in #{cache_store}..." if Lesmok.config.debugging? result = cache_store.fetch(cache_key, expires_in: expire_in) do - Lesmok.logger.debug "[#{self.class}] --- cache miss on #{cache_key} in #{cache_store}!" if Lesmok.config.debugging? + lesmok_logger.debug "[#{self.class}] --- cache miss on #{cache_key} in #{cache_store}!" if Lesmok.config.debugging? rendered_str = super if Lesmok.config.serve_stale_content? && rendered_str.present? stale_cache_store = select_cache_store_for(context, :stale) @@ -38,7 +41,7 @@ def render(context) end if context.errors.present? - ::Lesmok.logger.debug "[lesmok] -- Liquid errors (#{context.errors.size}) seen in: #{template_name}" + lesmok_logger.debug "[lesmok] -- Liquid errors (#{context.errors.size}) seen in: #{template_name}" end result @@ -46,15 +49,29 @@ def render(context) log_exception(err, context) if Lesmok.config.serve_stale_content? && cache_key.present? stale_cache_store = select_cache_store_for(context, :stale) - stale = stale_cache_store.get(cache_key + STALE_BREAD_KEY_SUFFIX) - ::Lesmok.logger.warn "[lesmok] Serving stale content in: #{template_name} [#{cache_key}]" if stale.present? + lesmok_logger.warn "[lesmok] Serving stale content in: #{template_name} [#{cache_key}]" if stale.present? stale else "" end end + + def perform_cached_inclusion_rendering_for(context, cache_store, cache_key) do + lesmok_logger.debug "[#{self.class}] Lookup #{cache_key} in #{cache_store}..." if Lesmok.config.debugging? + result = cache_store.fetch(cache_key, expires_in: expire_in) do + lesmok_logger.debug "[#{self.class}] --- cache miss on #{cache_key} in #{cache_store}!" if Lesmok.config.debugging? + rendered_str = yield + if Lesmok.config.serve_stale_content? && rendered_str.present? + stale_cache_store = select_cache_store_for(context, :stale) + stale_cache_store.set(cache_key + STALE_BREAD_KEY_SUFFIX, rendered_str, expires_in: nil) + end + rendered_str + end + result + end + def calculate_cache_key_for(context) cached_on_obj = context[@attributes['cache_on']] cache_val = cached_on_obj && cached_on_obj.respond_to?(:cache_key) && cached_on_obj.cache_key @@ -62,14 +79,7 @@ def calculate_cache_key_for(context) ## Catch cases where cached_include is used incorrectly. if cache_val.blank? - template_name = context[@template_name] - if Lesmok.config.debugging? - Lesmok.logger.warn "[#{self.class}] No valid cache key given for '#{template_name}' template!" - Lesmok.logger.debug " -- No cache key given nor found for object: #{cached_on_obj.inspect.truncate(64)}" - end - if Lesmok.config.raise_errors? - raise ArgumentError.new("No valid cache key! given for '#{template_name}' template!") - end + return error_calculating_cache_key(context, cached_on_obj) end ## Allow sub-scoping w/o manually creating cache key. cache_subscope = context[@attributes['cache_scope']] @@ -77,6 +87,18 @@ def calculate_cache_key_for(context) cache_val end + def error_calculating_cache_key(context, cached_on_obj) + template_name = context[@template_name] + if Lesmok.config.debugging? + lesmok_logger.warn "[#{self.class}] No valid cache key given for '#{template_name}' template!" + lesmok_logger.debug " -- No cache key given nor found for object: #{cached_on_obj.inspect.truncate(64)}" + end + if Lesmok.config.raise_errors? + raise ArgumentError.new("No valid cache key! given for '#{template_name}' template!") + end + nil + end + def select_cache_store_for(context, fallback_store_name = nil) cache_store_name = context[@attributes['cache_store']] || fallback_store_name cache_store = Lesmok.config.find_cache_store(cache_store_name) @@ -85,6 +107,9 @@ def select_cache_store_for(context, fallback_store_name = nil) def fragment_caching_enabled? Lesmok.config.caching? end + def lesmok_logger + ::Lesmok.logger + end end end From 099aa627e9853a28e3a5860687a57b960dbe8522 Mon Sep 17 00:00:00 2001 From: "Kent Dahl @ Sixty" Date: Fri, 28 Aug 2015 19:16:09 +0200 Subject: [PATCH 3/6] Refactor fix. --- lib/lesmok/tags/cached_include.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/lesmok/tags/cached_include.rb b/lib/lesmok/tags/cached_include.rb index 4305a85..bf09234 100644 --- a/lib/lesmok/tags/cached_include.rb +++ b/lib/lesmok/tags/cached_include.rb @@ -29,16 +29,6 @@ def render(context) result = perform_cached_inclusion_rendering_for(context, cache_store, cache_key) do super end - lesmok_logger.debug "[#{self.class}] Lookup #{cache_key} in #{cache_store}..." if Lesmok.config.debugging? - result = cache_store.fetch(cache_key, expires_in: expire_in) do - lesmok_logger.debug "[#{self.class}] --- cache miss on #{cache_key} in #{cache_store}!" if Lesmok.config.debugging? - rendered_str = super - if Lesmok.config.serve_stale_content? && rendered_str.present? - stale_cache_store = select_cache_store_for(context, :stale) - stale_cache_store.set(cache_key + STALE_BREAD_KEY_SUFFIX, rendered_str, expires_in: nil) - end - rendered_str - end if context.errors.present? lesmok_logger.debug "[lesmok] -- Liquid errors (#{context.errors.size}) seen in: #{template_name}" From b08ca6f92bf6732e6a414119ee406b9338ab07e6 Mon Sep 17 00:00:00 2001 From: "Kent Dahl @ Sixty" Date: Mon, 31 Aug 2015 16:23:41 +0200 Subject: [PATCH 4/6] Stale content serving as dynamic setting. --- lib/lesmok/config.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/lesmok/config.rb b/lib/lesmok/config.rb index a7ea7fc..cce9903 100644 --- a/lib/lesmok/config.rb +++ b/lib/lesmok/config.rb @@ -8,7 +8,6 @@ class Config attr_accessor :debugging_enabled attr_accessor :raise_errors_enabled - alias :serve_stale_content? :serve_stale_content alias :raise_errors? :raise_errors_enabled alias :debugging? :debugging_enabled @@ -21,8 +20,11 @@ def cache end def caching? - return false if !@caching_enabled - @caching_enabled.kind_of?(Proc) ? @caching_enabled.call : true + check_dynamically_toggleable_setting(@caching_enabled) + end + + def serve_stale_content? + check_dynamically_toggleable_setting(@serve_stale_content) end def find_cache_store(name = nil) @@ -37,5 +39,17 @@ def rails? Object.const_defined? "Rails" end + + protected + + ## + # Some settings can be changed at run-time from other parts of the system. + # Allow setting a `proc` to check this each time. + # + def check_dynamically_toggleable_setting(toggleable) + return false if !toggleable + toggleable.kind_of?(Proc) ? toggleable.call : true + end + end end From 19e22dade3e057bdb9b388f5b2f1d7313d04b3d7 Mon Sep 17 00:00:00 2001 From: "Kent Dahl @ Sixty" Date: Mon, 31 Aug 2015 16:35:56 +0200 Subject: [PATCH 5/6] Refactoring; should return stale content also on errors in flow? --- lib/lesmok/tags/cached_include.rb | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/lesmok/tags/cached_include.rb b/lib/lesmok/tags/cached_include.rb index bf09234..e156863 100644 --- a/lib/lesmok/tags/cached_include.rb +++ b/lib/lesmok/tags/cached_include.rb @@ -26,29 +26,23 @@ def render(context) cache_key = self.class.full_cache_key_for(cache_val, template_name) cache_store = select_cache_store_for(context) - result = perform_cached_inclusion_rendering_for(context, cache_store, cache_key) do + num_previous_errs = context.errors.size + result = perform_cached_inclusion_rendering_for(context, cache_store, cache_key, expire_in) do super end - if context.errors.present? + if context.errors.size > num_previous_errs lesmok_logger.debug "[lesmok] -- Liquid errors (#{context.errors.size}) seen in: #{template_name}" + return serve_any_stale_cached_content(context, cache_key) end result rescue Exception => err log_exception(err, context) - if Lesmok.config.serve_stale_content? && cache_key.present? - stale_cache_store = select_cache_store_for(context, :stale) - stale = stale_cache_store.get(cache_key + STALE_BREAD_KEY_SUFFIX) - lesmok_logger.warn "[lesmok] Serving stale content in: #{template_name} [#{cache_key}]" if stale.present? - stale - else - "" - end + serve_any_stale_cached_content(context, cache_key) || "" end - - def perform_cached_inclusion_rendering_for(context, cache_store, cache_key) do + def perform_cached_inclusion_rendering_for(context, cache_store, cache_key, expire_in) lesmok_logger.debug "[#{self.class}] Lookup #{cache_key} in #{cache_store}..." if Lesmok.config.debugging? result = cache_store.fetch(cache_key, expires_in: expire_in) do lesmok_logger.debug "[#{self.class}] --- cache miss on #{cache_key} in #{cache_store}!" if Lesmok.config.debugging? @@ -62,6 +56,18 @@ def perform_cached_inclusion_rendering_for(context, cache_store, cache_key) do result end + def serve_any_stale_cached_content(context, cache_key) + if Lesmok.config.serve_stale_content? && cache_key.present? + stale_cache_store = select_cache_store_for(context, :stale) + stale = stale_cache_store.get(cache_key + STALE_BREAD_KEY_SUFFIX) + lesmok_logger.warn "[lesmok] Serving stale content in: #{context[@template_name]} [#{cache_key}]" if stale.present? + lesmok_logger.debug "[lesmok] STALE: #{stale}" + stale + else + nil + end + end + def calculate_cache_key_for(context) cached_on_obj = context[@attributes['cache_on']] cache_val = cached_on_obj && cached_on_obj.respond_to?(:cache_key) && cached_on_obj.cache_key From 2dc212d7756e16ffd59c32c81c5182e9ef6cecb4 Mon Sep 17 00:00:00 2001 From: "Kent Dahl @ Sixty" Date: Mon, 31 Aug 2015 16:49:12 +0200 Subject: [PATCH 6/6] Only store in stale cache if rendered OK. --- lib/lesmok/tags/cached_include.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/lesmok/tags/cached_include.rb b/lib/lesmok/tags/cached_include.rb index e156863..598321f 100644 --- a/lib/lesmok/tags/cached_include.rb +++ b/lib/lesmok/tags/cached_include.rb @@ -47,7 +47,7 @@ def perform_cached_inclusion_rendering_for(context, cache_store, cache_key, expi result = cache_store.fetch(cache_key, expires_in: expire_in) do lesmok_logger.debug "[#{self.class}] --- cache miss on #{cache_key} in #{cache_store}!" if Lesmok.config.debugging? rendered_str = yield - if Lesmok.config.serve_stale_content? && rendered_str.present? + if Lesmok.config.serve_stale_content? && rendered_str.present? && context.errors.empty? stale_cache_store = select_cache_store_for(context, :stale) stale_cache_store.set(cache_key + STALE_BREAD_KEY_SUFFIX, rendered_str, expires_in: nil) end