1
2 """
3
4 @author: Fabio Erculiani <lxnay@sabayonlinux.org>
5 @contact: lxnay@sabayonlinux.org
6 @copyright: Fabio Erculiani
7 @license: GPL-2
8
9 B{Entropy Framework cache module}.
10
11 This module contains the Entropy, asynchronous caching logic.
12 It is not meant to handle cache pollution management, because
13 this is either handled implicitly when cached items are pulled
14 in or by using entropy.dump or cache cleaners (see
15 entropy.client.interfaces.cache mixin methods)
16
17 """
18
19 from __future__ import with_statement
20 from entropy.core import Singleton
21 from entropy.misc import TimeScheduled, Lifo
22 import time
23
25 """
26 Entropy asynchronous and synchronous cache writer
27 and reader. This class is a Singleton and contains
28 a thread doing the cache writes asynchronously, thus
29 it must be stopped before your application is terminated
30 calling the stop() method.
31
32 Sample code:
33
34 >>> # import module
35 >>> from entropy.cache import EntropyCacher
36 ...
37 >>> # first EntropyCacher load, start it
38 >>> cacher = EntropyCacher()
39 >>> cacher.start()
40 ...
41 >>> # now store something into its cache
42 >>> cacher.push('my_identifier1', [1, 2, 3])
43 >>> # now store something synchronously
44 >>> cacher.push('my_identifier2', [1, 2, 3], async = False)
45 ...
46 >>> # now flush all the caches to disk, and make sure all
47 >>> # is written
48 >>> cacher.sync(wait = True)
49 ...
50 >>> # now fetch something from the cache
51 >>> data = cacher.pop('my_identifier1')
52 [1, 2, 3]
53 ...
54 >>> # now discard all the cached (async) writes
55 >>> cacher.discard()
56 ...
57 >>> # and stop EntropyCacher
58 >>> cacher.stop()
59
60 """
61
62 import entropy.dump as dumpTools
63 import entropy.tools as entropyTools
64 import copy
65
67 """
68 Singleton overloaded method. Equals to __init__.
69 This is the place where all the properties initialization
70 takes place.
71 """
72 import threading
73 self.__alive = False
74 self.__cache_writer = None
75 self.__cache_buffer = Lifo()
76 self.__cache_lock = threading.Lock()
77
79 """
80 Return a copy of an object done by the standard
81 library "copy" module.
82
83 @param obj: object to copy
84 @type obj: any Python object
85 @rtype: copied object
86 @return: copied object
87 """
88 return self.copy.deepcopy(obj)
89
91 """
92 This is where the actual asynchronous copy takes
93 place. __cacher runs on a different threads and
94 all the operations done by this are atomic and
95 thread-safe. It just loops over and over until
96 __alive becomes False.
97 """
98 while 1:
99 if not self.__alive:
100 break
101 with self.__cache_lock:
102 data = self.__cache_buffer.pop()
103 if data == None:
104 break
105 key, data = data
106 d_o = self.dumpTools.dumpobj
107 if not d_o:
108 break
109 d_o(key, data)
110
113
115 """
116 This is the method used to start the asynchronous cache
117 writer but also the whole cacher. If this method is not
118 called, the instance will always trash and cache write
119 request.
120
121 @return: None
122 """
123 with self.__cache_lock:
124 self.__cache_buffer.clear()
125 self.__cache_writer = TimeScheduled(1, self.__cacher)
126 self.__cache_writer.set_delay_before(True)
127 self.__cache_writer.start()
128 while not self.__cache_writer.isAlive():
129 continue
130 self.__alive = True
131
133 """
134 Return whether start is called or not. This equals to
135 checking if the cacher is running, thus is writing cache
136 to disk.
137
138 @return: None
139 """
140 return self.__alive
141
143 """
144 This method stops the execution of the cacher, which won't
145 accept cache writes anymore. The thread responsible of writing
146 to disk is stopped here and the Cacher will be back to being
147 inactive. A watchdog will avoid the thread to freeze the
148 call if the write buffer is overloaded.
149
150 @return: None
151 """
152
153 watch_dog = 80
154 while self.__cache_buffer.is_filled() and (watch_dog > 0):
155 watch_dog -= 1
156 time.sleep(0.125)
157 self.__alive = False
158
159 with self.__cache_lock:
160 self.__cache_buffer.clear()
161 if self.__cache_writer != None:
162 self.__cache_writer.kill()
163 self.__cache_writer.join()
164 self.__cache_writer = None
165
166 - def sync(self, wait = False):
167 """
168 This method can be called anytime and forces the instance
169 to flush all the cache writes queued to disk. If wait == False
170 a watchdog prevents this call to get stuck in case of write
171 buffer overloads.
172
173 @keyword wait: indicates if waiting until done (synchronous mode) or not
174 @type wait: bool
175 @rtype: None
176 @return: None
177 """
178 if not self.__alive:
179 self.__cache_buffer.clear()
180 return
181
182 watch_dog = 40
183 while self.__cache_buffer.is_filled() and ((watch_dog > 0) or wait) \
184 and self.__alive:
185
186 if not wait:
187 watch_dog -= 1
188 time.sleep(0.125)
189
190 self.__cache_buffer.clear()
191
193 """
194 This method makes buffered cache to be discarded synchronously.
195
196 @return: None
197 """
198 self.__cache_buffer.clear()
199 with self.__cache_lock:
200 self.__cache_buffer.clear()
201
202 - def push(self, key, data, async = True):
203 """
204 This is the place where data is either added
205 to the write queue or written to disk (if async == False)
206 only and only if start() method has been called.
207
208 @param key: cache data identifier
209 @type key: string
210 @param data: picklable object
211 @type data: any picklable object
212 @keyword async: store cache asynchronously or not
213 @type async: bool
214 @rtype: None
215 @return: None
216 """
217 if not self.__alive:
218 return
219 if async:
220 with self.__cache_lock:
221 self.__cache_buffer.push((key, self.__copy_obj(data),))
222 else:
223 self.dumpTools.dumpobj(key, data)
224
225 - def pop(self, key):
226 """
227 This is the place where data is retrieved from cache.
228 You must know the cache identifier used when push()
229 was called.
230
231 @param key: cache data identifier
232 @type key: string
233 @rtype: Python object
234 @return: object stored into the stack or None (if stack is empty)
235 """
236 with self.__cache_lock:
237 l_o = self.dumpTools.loadobj
238 if not l_o:
239 return
240 return l_o(key)
241