class SynCache::Cache
Constants
- LOCK_SLEEP
a float number of seconds to sleep when a race condition is detected (actual delay is randomized to avoid live lock situation)
Attributes
set to true to report every single cache operation
Public Class Methods
ttl (time to live) is time in seconds from the last access until cache entry is expired (set to nil to disable time limit)
max_size is max number of objects in cache
flush_delay is used to rate-limit flush operations: if less than that number of seconds has passed since last flush, next flush will be delayed; default is no rate limit
# File lib/syncache/syncache.rb, line 80 def initialize(ttl = 60*60, max_size = 5000, flush_delay = nil) @ttl = ttl @max_size = max_size @debug = false if @flush_delay = flush_delay @last_flush = Time.now end @sync = Mutex.new @cache = {} end
Public Instance Methods
retrieve value from cache if it's still fresh
see also Cache#fetch_or_add
# File lib/syncache/syncache.rb, line 152 def [](key) debug { '[] ' << key.to_s } entry = get_locked_entry(key, false) unless entry.nil? begin return entry.value ensure entry.sync.unlock end end end
store new value in cache
see also Cache#fetch_or_add
# File lib/syncache/syncache.rb, line 137 def []=(key, value) debug { '[]= ' << key.to_s } entry = get_locked_entry(key) begin return entry.value = value ensure entry.sync.unlock end end
remove single value from cache
# File lib/syncache/syncache.rb, line 125 def delete(key) debug { 'delete ' << key.to_s } @sync.synchronize do @cache.delete(key) end end
initialize missing cache entry from supplied block
this is the preferred method of adding values to the cache as it locks the key for the duration of computation of the supplied block to prevent parallel execution of resource-intensive actions
# File lib/syncache/syncache.rb, line 171 def fetch_or_add(key) debug { 'fetch_or_add ' << key.to_s } entry = get_locked_entry(key) begin if entry.value.nil? entry.value = yield end return entry.value ensure entry.sync.unlock end end
remove all values from cache
if base is given, only values with keys matching the base (using ===
operator) are removed
# File lib/syncache/syncache.rb, line 102 def flush(base = nil) debug { 'flush ' << base.to_s } @sync.synchronize do if @flush_delay next_flush = @last_flush + @flush_delay if next_flush > Time.now flush_at(next_flush, base) else flush_now(base) @last_flush = Time.now end else flush_now(base) end end end
Private Instance Methods
# File lib/syncache/syncache.rb, line 210 def add_blank_entry(key) @sync.locked? or raise CacheError, 'add_entry called while @sync is not locked' had_same_key = @cache.has_key?(key) entry = @cache[key] = CacheEntry.new(@ttl) check_size unless had_same_key entry end
remove oldest item from cache if size limit reached
# File lib/syncache/syncache.rb, line 249 def check_size debug { 'check_size' } return unless @max_size.kind_of? Numeric if @sync.locked? check_size_internal else @sync.synchronize { check_size_internal } end end
# File lib/syncache/syncache.rb, line 261 def check_size_internal while @cache.size > @max_size do # optimize: supplement hash with queue oldest = @cache.keys.min {|a, b| @cache[a].replacement_index <=> @cache[b].replacement_index } @cache.delete(oldest) end end
delayed flush (ensure all entries matching base expire no later than next_flush)
must be run from inside global lock, see flush
# File lib/syncache/syncache.rb, line 203 def flush_at(next_flush, base = nil) @cache.each do |key, entry| next if base and not base === key entry.expire_at(next_flush) end end
immediate flush (delete all entries matching base)
must be run from inside global lock, see flush
# File lib/syncache/syncache.rb, line 191 def flush_now(base = nil) if base @cache.delete_if {|key, entry| base === key } else @cache = {} end end
# File lib/syncache/syncache.rb, line 220 def get_locked_entry(key, add_if_missing=true) debug { "get_locked_entry #{key}, #{add_if_missing}" } entry = nil # scope fix entry_locked = false until entry_locked do @sync.synchronize do entry = @cache[key] if entry.nil? or entry.stale? if add_if_missing entry = add_blank_entry(key) else @cache.delete(key) unless entry.nil? return nil end end entry_locked = entry.sync.try_lock end sleep(rand * LOCK_SLEEP) unless entry_locked end entry.record_access entry end