1
2 '''
3 # DESCRIPTION:
4 # Entropy Object Oriented Interface
5
6 Copyright (C) 2007-2009 Fabio Erculiani
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 '''
22
23
24 from __future__ import with_statement
25 from entropy.core import Singleton
26 from entropy.misc import TimeScheduled, Lifo
27 import time
28
30 """
31 Entropy asynchronous and synchronous cache writer
32 and reader. This class is a Singleton and contains
33 a thread doing the cache writes asynchronously, thus
34 it must be stopped before your application is terminated
35 calling the stop() method.
36 """
37
38 import entropy.dump as dumpTools
39 import entropy.tools as entropyTools
40 import copy
41
43 """
44 Singleton overloaded method. Equals to __init__.
45 This is the place where all the properties initialization
46 takes place.
47 """
48 import threading
49 self.__alive = False
50 self.__cache_writer = None
51 self.__cache_buffer = Lifo()
52 self.__cache_lock = threading.Lock()
53
55 """
56 Return a copy of an object done by the standard
57 library "copy" module.
58
59 @param obj: object to copy
60 @type obj: any Python object
61 @rtype: copied object
62 @return: copied object
63 """
64 return self.copy.deepcopy(obj)
65
67 """
68 This is where the actual asynchronous copy takes
69 place. __cacher runs on a different threads and
70 all the operations done by this are atomic and
71 thread-safe. It just loops over and over until
72 __alive becomes False.
73 """
74 while 1:
75 if not self.__alive:
76 break
77 with self.__cache_lock:
78 data = self.__cache_buffer.pop()
79 if data == None:
80 break
81 key, data = data
82 d_o = self.dumpTools.dumpobj
83 if not d_o:
84 break
85 d_o(key, data)
86
89
91 """
92 This is the method used to start the asynchronous cache
93 writer but also the whole cacher. If this method is not
94 called, the instance will always trash and cache write
95 request.
96
97 @return: None
98 """
99 with self.__cache_lock:
100 self.__cache_buffer.clear()
101 self.__cache_writer = TimeScheduled(1, self.__cacher)
102 self.__cache_writer.set_delay_before(True)
103 self.__cache_writer.start()
104 while not self.__cache_writer.isAlive():
105 continue
106 self.__alive = True
107
109 """
110 Return whether start is called or not. This equals to
111 checking if the cacher is running, thus is writing cache
112 to disk.
113
114 @return: None
115 """
116 return self.__alive
117
119 """
120 This method stops the execution of the cacher, which won't
121 accept cache writes anymore. The thread responsible of writing
122 to disk is stopped here and the Cacher will be back to being
123 inactive. A watchdog will avoid the thread to freeze the
124 call if the write buffer is overloaded.
125
126 @return: None
127 """
128
129 self.__alive = False
130 watch_dog = 20
131 while self.__cache_buffer.is_filled() and (watch_dog > 0):
132 watch_dog -= 1
133 time.sleep(0.5)
134
135 with self.__cache_lock:
136 self.__cache_buffer.clear()
137 if self.__cache_writer != None:
138 self.__cache_writer.kill()
139 self.__cache_writer.join()
140 self.__cache_writer = None
141
142 - def sync(self, wait = False):
143 """
144 This method can be called anytime and forces the instance
145 to flush all the cache writes queued to disk. If wait == False
146 a watchdog prevents this call to get stuck in case of write
147 buffer overloads.
148
149 @param wait: indicates if waiting until done (synchronous mode)
150 or not
151 @type wait: bool
152
153 @return: None
154 """
155 if not self.__alive:
156 self.__cache_buffer.clear()
157 return
158
159 watch_dog = 10
160 while self.__cache_buffer.is_filled() and ((watch_dog > 0) or wait) \
161 and self.__alive:
162
163 if not wait:
164 watch_dog -= 1
165 time.sleep(0.5)
166
167 self.__cache_buffer.clear()
168
170 """
171 This method makes buffered cache to be discarded synchronously.
172
173 @return: None
174 """
175 self.__cache_buffer.clear()
176 with self.__cache_lock:
177 self.__cache_buffer.clear()
178
179 - def push(self, key, data, async = True):
180 """
181 This is the place where data is either added
182 to the write queue or written to disk (if async == False)
183 only and only if start() method has been called.
184
185 @param key: cache data identifier
186 @type key: basestring
187 @param data: picklable object
188 @type data: any picklable object
189 @param async: store cache asynchronously or not
190 @type async: bool
191 @rtype: None
192 @return: nothing
193 """
194 if not self.__alive:
195 return
196 if async:
197 with self.__cache_lock:
198 self.__cache_buffer.push((key, self.__copy_obj(data),))
199 else:
200 self.dumpTools.dumpobj(key, data)
201
202 - def pop(self, key):
203 """
204 This is the place where data is retrieved from cache.
205 You must know the cache identifier used when push()
206 was called.
207
208 @param key: cache data identifier
209 @type key: basestring
210 @rtype: Python object
211 @return: object stored into the stack or None (if stack is empty)
212 """
213 with self.__cache_lock:
214 l_o = self.dumpTools.loadobj
215 if not l_o:
216 return
217 return l_o(key)
218