Skip to content

Instantly share code, notes, and snippets.

@gotar
Created August 13, 2012 12:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gotar/3340313 to your computer and use it in GitHub Desktop.
Save gotar/3340313 to your computer and use it in GitHub Desktop.
Cache
module Cache
class Entry
include Mongoid::Document
field :key, type: String
field :_id, type: String, default: -> { key }
field :value
field :created_at, type: DateTime
end
# Exception raised when calculation was started, but time alotted has
# passed. Celluloid::Future is included in the exception object.
class TimedOut < Exception
attr_reader :future
def initialize(future)
@future = future
end
end
class << self
# Cache entry may have three states:
#
# - fresh: it is up to date and can be used directly
# - stale: it is out of date, but may be used in some circumstances
# (see below)
# - expired: it is long out of date and will be removed
#
# There are two configuration values related to that:
#
# - stale_time: time after which cache entry becomes stale
# - expiration_time: time after which cache entry is expired
#
# 1. If a value under given key is available in cache and younger than
# :stale_time (default 1 day) it will be returned.
# 2. If a fresh cache is not available, given block will be invoked and
# given :timeout (default infinity) seconds to complete.
# 3. If block completes within given time limit, cache entry will be
# stored in cache and returned.
# 4. If :timeout seconds has passed, but block didn't complete, and
# there is a stale cache entry available, it will be returned.
# Computation of the block will continue and will refresh the cache.
# 5. If :timeout seconds has passed, block didn't complete, and there is
# no cache entry available, Timeout::Error will be raised.
def call(key, options={}, &block)
options.reverse_merge!(default_options)
entry = Entry.where(_id: key).first
if entry && entry.created_at > options[:stale_time].ago
register_hit
else
register_miss
cached = Celluloid::Future.new do
Entry.new(key: key, value: block.call, created_at: Time.now).
tap(&:upsert)
end
begin
entry = Timeout.timeout(options[:timeout]) { cached.value }
rescue Timeout::Error
if entry.nil? || entry.created_at < options[:expiration_time].ago
raise TimedOut.new(cached)
end
end
end
entry.value
end
private
def default_options
{:stale_time => 1.day,
:expiration_time => 7.days,
:timeout => nil}
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment