--- /dev/null
+module MCollective
+ # A class to manage a number of named caches. Each cache can have a unique
+ # timeout for keys in it and each cache has an independent Mutex protecting
+ # access to it.
+ #
+ # This cache is setup early in the process of loading the mcollective
+ # libraries, before any threads are created etc making it suitable as a
+ # cross thread cache or just a store for Mutexes you need to use across
+ # threads like in an Agent or something.
+ #
+ # # sets up a new cache, noop if it already exist
+ # Cache.setup(:ddl, 600)
+ # => true
+ #
+ # # writes an item to the :ddl cache, this item will
+ # # be valid on the cache for 600 seconds
+ # Cache.write(:ddl, :something, "value")
+ # => "value"
+ #
+ # # reads from the cache, read failures due to non existing
+ # # data or expired data will raise an exception
+ # Cache.read(:ddl, :something)
+ # => "value"
+ #
+ # # time left till expiry, raises if nothing is found
+ # Cache.ttl(:ddl, :something)
+ # => 500
+ #
+ # # forcibly evict something from the cache
+ # Cache.invalidate!(:ddl, :something)
+ # => "value"
+ #
+ # # deletes an entire named cache and its mutexes
+ # Cache.delete!(:ddl)
+ # => true
+ #
+ # # you can also just use this cache as a global mutex store
+ # Cache.setup(:mylock)
+ #
+ # Cache.synchronize(:mylock) do
+ # do_something()
+ # end
+ #
+ module Cache
+ extend Translatable
+
+ # protects access to @cache_locks and top level @cache
+ @locks_mutex = Mutex.new
+
+ # stores a mutex per named cache
+ @cache_locks = {}
+
+ # the named caches protected by items in @cache_locks
+ @cache = {}
+
+ def self.setup(cache_name, ttl=300)
+ @locks_mutex.synchronize do
+ break if @cache_locks.include?(cache_name)
+
+ @cache_locks[cache_name] = Mutex.new
+
+ @cache_locks[cache_name].synchronize do
+ @cache[cache_name] = {:max_age => Float(ttl)}
+ end
+ end
+
+ true
+ end
+
+ def self.check_cache!(cache_name)
+ raise_code(:PLMC13, "Could not find a cache called '%{cache_name}'", :debug, :cache_name => cache_name) unless has_cache?(cache_name)
+ end
+
+ def self.has_cache?(cache_name)
+ @locks_mutex.synchronize do
+ @cache.include?(cache_name)
+ end
+ end
+
+ def self.delete!(cache_name)
+ check_cache!(cache_name)
+
+ @locks_mutex.synchronize do
+ @cache_locks.delete(cache_name)
+ @cache.delete(cache_name)
+ end
+
+ true
+ end
+
+ def self.write(cache_name, key, value)
+ check_cache!(cache_name)
+
+ @cache_locks[cache_name].synchronize do
+ @cache[cache_name][key] ||= {}
+ @cache[cache_name][key][:cache_create_time] = Time.now
+ @cache[cache_name][key][:value] = value
+ end
+
+ value
+ end
+
+ def self.read(cache_name, key)
+ check_cache!(cache_name)
+
+ unless ttl(cache_name, key) > 0
+ raise_code(:PLMC11, "Cache expired on '%{cache_name}' key '%{item}'", :debug, :cache_name => cache_name, :item => key)
+ end
+
+ log_code(:PLMC12, "Cache hit on '%{cache_name}' key '%{item}'", :debug, :cache_name => cache_name, :item => key)
+
+ @cache_locks[cache_name].synchronize do
+ @cache[cache_name][key][:value]
+ end
+ end
+
+ def self.ttl(cache_name, key)
+ check_cache!(cache_name)
+
+ @cache_locks[cache_name].synchronize do
+ unless @cache[cache_name].include?(key)
+ raise_code(:PLMC15, "No item called '%{item}' for cache '%{cache_name}'", :debug, :cache_name => cache_name, :item => key)
+ end
+
+ @cache[cache_name][:max_age] - (Time.now - @cache[cache_name][key][:cache_create_time])
+ end
+ end
+
+ def self.synchronize(cache_name)
+ check_cache!(cache_name)
+
+ raise_code(:PLMC14, "No block supplied to synchronize on cache '%{cache_name}'", :debug, :cache_name => cache_name) unless block_given?
+
+ @cache_locks[cache_name].synchronize do
+ yield
+ end
+ end
+
+ def self.invalidate!(cache_name, key)
+ check_cache!(cache_name)
+
+ @cache_locks[cache_name].synchronize do
+ return false unless @cache[cache_name].include?(key)
+
+ @cache[cache_name].delete(key)
+ end
+ end
+ end
+end