Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(722)

Side by Side Diff: third_party/google-endpoints/dogpile/cache/backends/file.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 """
2 File Backends
3 ------------------
4
5 Provides backends that deal with local filesystem access.
6
7 """
8
9 from __future__ import with_statement
10 from ..api import CacheBackend, NO_VALUE
11 from contextlib import contextmanager
12 from ...util import compat
13 from ... import util
14 import os
15
16 __all__ = 'DBMBackend', 'FileLock', 'AbstractFileLock'
17
18
19 class DBMBackend(CacheBackend):
20 """A file-backend using a dbm file to store keys.
21
22 Basic usage::
23
24 from dogpile.cache import make_region
25
26 region = make_region().configure(
27 'dogpile.cache.dbm',
28 expiration_time = 3600,
29 arguments = {
30 "filename":"/path/to/cachefile.dbm"
31 }
32 )
33
34 DBM access is provided using the Python ``anydbm`` module,
35 which selects a platform-specific dbm module to use.
36 This may be made to be more configurable in a future
37 release.
38
39 Note that different dbm modules have different behaviors.
40 Some dbm implementations handle their own locking, while
41 others don't. The :class:`.DBMBackend` uses a read/write
42 lockfile by default, which is compatible even with those
43 DBM implementations for which this is unnecessary,
44 though the behavior can be disabled.
45
46 The DBM backend by default makes use of two lockfiles.
47 One is in order to protect the DBM file itself from
48 concurrent writes, the other is to coordinate
49 value creation (i.e. the dogpile lock). By default,
50 these lockfiles use the ``flock()`` system call
51 for locking; this is **only available on Unix
52 platforms**. An alternative lock implementation, such as one
53 which is based on threads or uses a third-party system
54 such as `portalocker <https://pypi.python.org/pypi/portalocker>`_,
55 can be dropped in using the ``lock_factory`` argument
56 in conjunction with the :class:`.AbstractFileLock` base class.
57
58 Currently, the dogpile lock is against the entire
59 DBM file, not per key. This means there can
60 only be one "creator" job running at a time
61 per dbm file.
62
63 A future improvement might be to have the dogpile lock
64 using a filename that's based on a modulus of the key.
65 Locking on a filename that uniquely corresponds to the
66 key is problematic, since it's not generally safe to
67 delete lockfiles as the application runs, implying an
68 unlimited number of key-based files would need to be
69 created and never deleted.
70
71 Parameters to the ``arguments`` dictionary are
72 below.
73
74 :param filename: path of the filename in which to
75 create the DBM file. Note that some dbm backends
76 will change this name to have additional suffixes.
77 :param rw_lockfile: the name of the file to use for
78 read/write locking. If omitted, a default name
79 is used by appending the suffix ".rw.lock" to the
80 DBM filename. If False, then no lock is used.
81 :param dogpile_lockfile: the name of the file to use
82 for value creation, i.e. the dogpile lock. If
83 omitted, a default name is used by appending the
84 suffix ".dogpile.lock" to the DBM filename. If
85 False, then dogpile.cache uses the default dogpile
86 lock, a plain thread-based mutex.
87 :param lock_factory: a function or class which provides
88 for a read/write lock. Defaults to :class:`.FileLock`.
89 Custom implementations need to implement context-manager
90 based ``read()`` and ``write()`` functions - the
91 :class:`.AbstractFileLock` class is provided as a base class
92 which provides these methods based on individual read/write lock
93 functions. E.g. to replace the lock with the dogpile.core
94 :class:`.ReadWriteMutex`::
95
96 from dogpile.core.readwrite_lock import ReadWriteMutex
97 from dogpile.cache.backends.file import AbstractFileLock
98
99 class MutexLock(AbstractFileLock):
100 def __init__(self, filename):
101 self.mutex = ReadWriteMutex()
102
103 def acquire_read_lock(self, wait):
104 ret = self.mutex.acquire_read_lock(wait)
105 return wait or ret
106
107 def acquire_write_lock(self, wait):
108 ret = self.mutex.acquire_write_lock(wait)
109 return wait or ret
110
111 def release_read_lock(self):
112 return self.mutex.release_read_lock()
113
114 def release_write_lock(self):
115 return self.mutex.release_write_lock()
116
117 from dogpile.cache import make_region
118
119 region = make_region().configure(
120 "dogpile.cache.dbm",
121 expiration_time=300,
122 arguments={
123 "filename": "file.dbm",
124 "lock_factory": MutexLock
125 }
126 )
127
128 While the included :class:`.FileLock` uses ``os.flock()``, a
129 windows-compatible implementation can be built using a library
130 such as `portalocker <https://pypi.python.org/pypi/portalocker>`_.
131
132 .. versionadded:: 0.5.2
133
134
135
136 """
137 def __init__(self, arguments):
138 self.filename = os.path.abspath(
139 os.path.normpath(arguments['filename'])
140 )
141 dir_, filename = os.path.split(self.filename)
142
143 self.lock_factory = arguments.get("lock_factory", FileLock)
144 self._rw_lock = self._init_lock(
145 arguments.get('rw_lockfile'),
146 ".rw.lock", dir_, filename)
147 self._dogpile_lock = self._init_lock(
148 arguments.get('dogpile_lockfile'),
149 ".dogpile.lock",
150 dir_, filename,
151 util.KeyReentrantMutex.factory)
152
153 # TODO: make this configurable
154 if compat.py3k:
155 import dbm
156 else:
157 import anydbm as dbm
158 self.dbmmodule = dbm
159 self._init_dbm_file()
160
161 def _init_lock(self, argument, suffix, basedir, basefile, wrapper=None):
162 if argument is None:
163 lock = self.lock_factory(os.path.join(basedir, basefile + suffix))
164 elif argument is not False:
165 lock = self.lock_factory(
166 os.path.abspath(
167 os.path.normpath(argument)
168 ))
169 else:
170 return None
171 if wrapper:
172 lock = wrapper(lock)
173 return lock
174
175 def _init_dbm_file(self):
176 exists = os.access(self.filename, os.F_OK)
177 if not exists:
178 for ext in ('db', 'dat', 'pag', 'dir'):
179 if os.access(self.filename + os.extsep + ext, os.F_OK):
180 exists = True
181 break
182 if not exists:
183 fh = self.dbmmodule.open(self.filename, 'c')
184 fh.close()
185
186 def get_mutex(self, key):
187 # using one dogpile for the whole file. Other ways
188 # to do this might be using a set of files keyed to a
189 # hash/modulus of the key. the issue is it's never
190 # really safe to delete a lockfile as this can
191 # break other processes trying to get at the file
192 # at the same time - so handling unlimited keys
193 # can't imply unlimited filenames
194 if self._dogpile_lock:
195 return self._dogpile_lock(key)
196 else:
197 return None
198
199 @contextmanager
200 def _use_rw_lock(self, write):
201 if self._rw_lock is None:
202 yield
203 elif write:
204 with self._rw_lock.write():
205 yield
206 else:
207 with self._rw_lock.read():
208 yield
209
210 @contextmanager
211 def _dbm_file(self, write):
212 with self._use_rw_lock(write):
213 dbm = self.dbmmodule.open(
214 self.filename,
215 "w" if write else "r")
216 yield dbm
217 dbm.close()
218
219 def get(self, key):
220 with self._dbm_file(False) as dbm:
221 if hasattr(dbm, 'get'):
222 value = dbm.get(key, NO_VALUE)
223 else:
224 # gdbm objects lack a .get method
225 try:
226 value = dbm[key]
227 except KeyError:
228 value = NO_VALUE
229 if value is not NO_VALUE:
230 value = compat.pickle.loads(value)
231 return value
232
233 def get_multi(self, keys):
234 return [self.get(key) for key in keys]
235
236 def set(self, key, value):
237 with self._dbm_file(True) as dbm:
238 dbm[key] = compat.pickle.dumps(value,
239 compat.pickle.HIGHEST_PROTOCOL)
240
241 def set_multi(self, mapping):
242 with self._dbm_file(True) as dbm:
243 for key, value in mapping.items():
244 dbm[key] = compat.pickle.dumps(value,
245 compat.pickle.HIGHEST_PROTOCOL)
246
247 def delete(self, key):
248 with self._dbm_file(True) as dbm:
249 try:
250 del dbm[key]
251 except KeyError:
252 pass
253
254 def delete_multi(self, keys):
255 with self._dbm_file(True) as dbm:
256 for key in keys:
257 try:
258 del dbm[key]
259 except KeyError:
260 pass
261
262
263 class AbstractFileLock(object):
264 """Coordinate read/write access to a file.
265
266 typically is a file-based lock but doesn't necessarily have to be.
267
268 The default implementation here is :class:`.FileLock`.
269
270 Implementations should provide the following methods::
271
272 * __init__()
273 * acquire_read_lock()
274 * acquire_write_lock()
275 * release_read_lock()
276 * release_write_lock()
277
278 The ``__init__()`` method accepts a single argument "filename", which
279 may be used as the "lock file", for those implementations that use a lock
280 file.
281
282 Note that multithreaded environments must provide a thread-safe
283 version of this lock. The recommended approach for file-
284 descriptor-based locks is to use a Python ``threading.local()`` so
285 that a unique file descriptor is held per thread. See the source
286 code of :class:`.FileLock` for an implementation example.
287
288
289 """
290
291 def __init__(self, filename):
292 """Constructor, is given the filename of a potential lockfile.
293
294 The usage of this filename is optional and no file is
295 created by default.
296
297 Raises ``NotImplementedError`` by default, must be
298 implemented by subclasses.
299 """
300 raise NotImplementedError()
301
302 def acquire(self, wait=True):
303 """Acquire the "write" lock.
304
305 This is a direct call to :meth:`.AbstractFileLock.acquire_write_lock`.
306
307 """
308 return self.acquire_write_lock(wait)
309
310 def release(self):
311 """Release the "write" lock.
312
313 This is a direct call to :meth:`.AbstractFileLock.release_write_lock`.
314
315 """
316 self.release_write_lock()
317
318 @contextmanager
319 def read(self):
320 """Provide a context manager for the "read" lock.
321
322 This method makes use of :meth:`.AbstractFileLock.acquire_read_lock`
323 and :meth:`.AbstractFileLock.release_read_lock`
324
325 """
326
327 self.acquire_read_lock(True)
328 try:
329 yield
330 finally:
331 self.release_read_lock()
332
333 @contextmanager
334 def write(self):
335 """Provide a context manager for the "write" lock.
336
337 This method makes use of :meth:`.AbstractFileLock.acquire_write_lock`
338 and :meth:`.AbstractFileLock.release_write_lock`
339
340 """
341
342 self.acquire_write_lock(True)
343 try:
344 yield
345 finally:
346 self.release_write_lock()
347
348 @property
349 def is_open(self):
350 """optional method."""
351 raise NotImplementedError()
352
353 def acquire_read_lock(self, wait):
354 """Acquire a 'reader' lock.
355
356 Raises ``NotImplementedError`` by default, must be
357 implemented by subclasses.
358 """
359 raise NotImplementedError()
360
361 def acquire_write_lock(self, wait):
362 """Acquire a 'write' lock.
363
364 Raises ``NotImplementedError`` by default, must be
365 implemented by subclasses.
366 """
367 raise NotImplementedError()
368
369 def release_read_lock(self):
370 """Release a 'reader' lock.
371
372 Raises ``NotImplementedError`` by default, must be
373 implemented by subclasses.
374 """
375 raise NotImplementedError()
376
377 def release_write_lock(self):
378 """Release a 'writer' lock.
379
380 Raises ``NotImplementedError`` by default, must be
381 implemented by subclasses.
382 """
383 raise NotImplementedError()
384
385
386 class FileLock(AbstractFileLock):
387 """Use lockfiles to coordinate read/write access to a file.
388
389 Only works on Unix systems, using
390 `fcntl.flock() <http://docs.python.org/library/fcntl.html>`_.
391
392 """
393
394 def __init__(self, filename):
395 self._filedescriptor = compat.threading.local()
396 self.filename = filename
397
398 @util.memoized_property
399 def _module(self):
400 import fcntl
401 return fcntl
402
403 @property
404 def is_open(self):
405 return hasattr(self._filedescriptor, 'fileno')
406
407 def acquire_read_lock(self, wait):
408 return self._acquire(wait, os.O_RDONLY, self._module.LOCK_SH)
409
410 def acquire_write_lock(self, wait):
411 return self._acquire(wait, os.O_WRONLY, self._module.LOCK_EX)
412
413 def release_read_lock(self):
414 self._release()
415
416 def release_write_lock(self):
417 self._release()
418
419 def _acquire(self, wait, wrflag, lockflag):
420 wrflag |= os.O_CREAT
421 fileno = os.open(self.filename, wrflag)
422 try:
423 if not wait:
424 lockflag |= self._module.LOCK_NB
425 self._module.flock(fileno, lockflag)
426 except IOError:
427 os.close(fileno)
428 if not wait:
429 # this is typically
430 # "[Errno 35] Resource temporarily unavailable",
431 # because of LOCK_NB
432 return False
433 else:
434 raise
435 else:
436 self._filedescriptor.fileno = fileno
437 return True
438
439 def _release(self):
440 try:
441 fileno = self._filedescriptor.fileno
442 except AttributeError:
443 return
444 else:
445 self._module.flock(fileno, self._module.LOCK_UN)
446 os.close(fileno)
447 del self._filedescriptor.fileno
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698