50432d25b520cc4e8c2e80076a6fe1c0f6aa4d73
[packages/precise/mcollective.git] / lib / mcollective / cache.rb
1 module MCollective
2   # A class to manage a number of named caches.  Each cache can have a unique
3   # timeout for keys in it and each cache has an independent Mutex protecting
4   # access to it.
5   #
6   # This cache is setup early in the process of loading the mcollective
7   # libraries, before any threads are created etc making it suitable as a
8   # cross thread cache or just a store for Mutexes you need to use across
9   # threads like in an Agent or something.
10   #
11   #    # sets up a new cache, noop if it already exist
12   #    Cache.setup(:ddl, 600)
13   #    => true
14   #
15   #    # writes an item to the :ddl cache, this item will
16   #    # be valid on the cache for 600 seconds
17   #    Cache.write(:ddl, :something, "value")
18   #    => "value"
19   #
20   #    # reads from the cache, read failures due to non existing
21   #    # data or expired data will raise an exception
22   #    Cache.read(:ddl, :something)
23   #    => "value"
24   #
25   #    # time left till expiry, raises if nothing is found
26   #    Cache.ttl(:ddl, :something)
27   #    => 500
28   #
29   #    # forcibly evict something from the cache
30   #    Cache.invalidate!(:ddl, :something)
31   #    => "value"
32   #
33   #    # deletes an entire named cache and its mutexes
34   #    Cache.delete!(:ddl)
35   #    => true
36   #
37   #    # you can also just use this cache as a global mutex store
38   #    Cache.setup(:mylock)
39   #
40   #    Cache.synchronize(:mylock) do
41   #      do_something()
42   #    end
43   #
44   module Cache
45     extend Translatable
46
47     # protects access to @cache_locks and top level @cache
48     @locks_mutex = Mutex.new
49
50     # stores a mutex per named cache
51     @cache_locks = {}
52
53     # the named caches protected by items in @cache_locks
54     @cache = {}
55
56     def self.setup(cache_name, ttl=300)
57       @locks_mutex.synchronize do
58         break if @cache_locks.include?(cache_name)
59
60         @cache_locks[cache_name] = Mutex.new
61
62         @cache_locks[cache_name].synchronize do
63           @cache[cache_name] = {:max_age => Float(ttl)}
64         end
65       end
66
67       true
68     end
69
70     def self.check_cache!(cache_name)
71       raise_code(:PLMC13, "Could not find a cache called '%{cache_name}'", :debug, :cache_name => cache_name) unless has_cache?(cache_name)
72     end
73
74     def self.has_cache?(cache_name)
75       @locks_mutex.synchronize do
76         @cache.include?(cache_name)
77       end
78     end
79
80     def self.delete!(cache_name)
81       check_cache!(cache_name)
82
83       @locks_mutex.synchronize do
84         @cache_locks.delete(cache_name)
85         @cache.delete(cache_name)
86       end
87
88       true
89     end
90
91     def self.write(cache_name, key, value)
92       check_cache!(cache_name)
93
94       @cache_locks[cache_name].synchronize do
95         @cache[cache_name][key] ||= {}
96         @cache[cache_name][key][:cache_create_time] = Time.now
97         @cache[cache_name][key][:value] = value
98       end
99
100       value
101     end
102
103     def self.read(cache_name, key)
104       check_cache!(cache_name)
105
106       unless ttl(cache_name, key) > 0
107         raise_code(:PLMC11, "Cache expired on '%{cache_name}' key '%{item}'", :debug, :cache_name => cache_name, :item => key)
108       end
109
110       log_code(:PLMC12, "Cache hit on '%{cache_name}' key '%{item}'", :debug, :cache_name => cache_name, :item => key)
111
112       @cache_locks[cache_name].synchronize do
113         @cache[cache_name][key][:value]
114       end
115     end
116
117     def self.ttl(cache_name, key)
118       check_cache!(cache_name)
119
120       @cache_locks[cache_name].synchronize do
121         unless @cache[cache_name].include?(key)
122           raise_code(:PLMC15, "No item called '%{item}' for cache '%{cache_name}'", :debug, :cache_name => cache_name, :item => key)
123         end
124
125         @cache[cache_name][:max_age] - (Time.now - @cache[cache_name][key][:cache_create_time])
126       end
127     end
128
129     def self.synchronize(cache_name)
130       check_cache!(cache_name)
131
132       raise_code(:PLMC14, "No block supplied to synchronize on cache '%{cache_name}'", :debug, :cache_name => cache_name) unless block_given?
133
134       @cache_locks[cache_name].synchronize do
135         yield
136       end
137     end
138
139     def self.invalidate!(cache_name, key)
140       check_cache!(cache_name)
141
142       @cache_locks[cache_name].synchronize do
143         return false unless @cache[cache_name].include?(key)
144
145         @cache[cache_name].delete(key)
146       end
147     end
148   end
149 end