lib/caddy/cache.rb in caddy-1.5.5 vs lib/caddy/cache.rb in caddy-1.6.0

- old
+ new

@@ -1,41 +1,58 @@ # frozen_string_literal: true module Caddy class Cache + # Default refresh interval, in seconds DEFAULT_REFRESH_INTERVAL = 60 + + # Percentage to randomly smooth the refresh interval to avoid stampeding herd on expiration REFRESH_INTERVAL_JITTER_PCT = 0.15 - attr_accessor :refresher, :refresh_interval, :error_handler + # @!attribute refresher + # @return [Proc] called on interval {#refresh_interval} with the returned object used as the cache + attr_accessor :refresher - ## - # Create a new cache with key +key+. + # @!attribute refresh_interval + # @return [Numeric] number of seconds between calls to {#refresher}; timeout is set to <tt>{#refresher} - 0.1</tt> + attr_accessor :refresh_interval + + # @!attribute error_handler + # @return [Proc] if unset, defaults to the global error handler (see #{Caddy.error_handler}); + # called when exceptions or timeouts happen within the refresher + attr_accessor :error_handler + + # Create a new periodically updated cache. + # @param key [Symbol] the name of this cache def initialize(key) @task = nil @refresh_interval = DEFAULT_REFRESH_INTERVAL @cache = nil @key = key end - ## # Convenience method for getting the value of the refresher-returned object at path +k+, # assuming the refresher-returned value responds to <tt>[]</tt>. # - # If not, #cache can be used instead to access the refresher-returned object. + # If not, {#cache} can be used instead to access the refresher-returned object. + # @param k key to access from the refresher-returned cache. def [](k) cache[k] end - ## # Returns the refresher-produced value that is used as the cache. def cache raise "Please run `Caddy.start` before attempting to access the cache" unless @task && @task.running? - raise "Caddy cache access of :#{@key} before initial load; allow some more time for your app to start up" unless @cache + unless @cache + logger.warn "Caddy cache access of :#{@key} before initial load; doing synchronous load."\ + "Please allow some more time for your app to start up." + refresh + end + @cache end - ## # Starts the period refresh cycle. # # Every +refresh_interval+ seconds -- smoothed by a jitter amount (a random amount +/- +REFRESH_INTERVAL_JITTER_PCT+) -- # the refresher lambda is called and the results stored in +cache+. # @@ -56,24 +73,40 @@ @task = Concurrent::TimerTask.new( run_now: true, execution_interval: interval, timeout_interval: timeout_interval ) do - @cache = refresher.call.freeze - nil # no need to save the value internally to TimerTask + refresh + nil # no need for the {#Concurrent::TimerTask} to keep a reference to the value end @task.add_observer(Caddy::TaskObserver.new(error_handler, @key)) + + logger.debug "Starting Caddy refresher for :#{@key}, updating every #{interval.round(1)}s." + @task.execute @task.running? end - ## # Stops the current executing refresher. # # The current cache value is persisted even if the task is stopped. def stop @task.shutdown if @task && @task.running? + end + + # Updates the internal cache object. + # + # Freezes the result to avoid mutation errors. + def refresh + @cache = refresher.call.freeze + end + + private + + # Delegates logging to the module logger + def logger + Caddy.logger end end end