Created
August 13, 2012 12:45
-
-
Save gotar/3340313 to your computer and use it in GitHub Desktop.
Cache
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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