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