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

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

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 11 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 side-by-side diff with in-line comments
Download patch
Index: third_party/google-endpoints/dogpile/cache/backends/file.py
diff --git a/third_party/google-endpoints/dogpile/cache/backends/file.py b/third_party/google-endpoints/dogpile/cache/backends/file.py
new file mode 100644
index 0000000000000000000000000000000000000000..309c055a2e360c9d2f810779ac332c14ccfb914b
--- /dev/null
+++ b/third_party/google-endpoints/dogpile/cache/backends/file.py
@@ -0,0 +1,447 @@
+"""
+File Backends
+------------------
+
+Provides backends that deal with local filesystem access.
+
+"""
+
+from __future__ import with_statement
+from ..api import CacheBackend, NO_VALUE
+from contextlib import contextmanager
+from ...util import compat
+from ... import util
+import os
+
+__all__ = 'DBMBackend', 'FileLock', 'AbstractFileLock'
+
+
+class DBMBackend(CacheBackend):
+ """A file-backend using a dbm file to store keys.
+
+ Basic usage::
+
+ from dogpile.cache import make_region
+
+ region = make_region().configure(
+ 'dogpile.cache.dbm',
+ expiration_time = 3600,
+ arguments = {
+ "filename":"/path/to/cachefile.dbm"
+ }
+ )
+
+ DBM access is provided using the Python ``anydbm`` module,
+ which selects a platform-specific dbm module to use.
+ This may be made to be more configurable in a future
+ release.
+
+ Note that different dbm modules have different behaviors.
+ Some dbm implementations handle their own locking, while
+ others don't. The :class:`.DBMBackend` uses a read/write
+ lockfile by default, which is compatible even with those
+ DBM implementations for which this is unnecessary,
+ though the behavior can be disabled.
+
+ The DBM backend by default makes use of two lockfiles.
+ One is in order to protect the DBM file itself from
+ concurrent writes, the other is to coordinate
+ value creation (i.e. the dogpile lock). By default,
+ these lockfiles use the ``flock()`` system call
+ for locking; this is **only available on Unix
+ platforms**. An alternative lock implementation, such as one
+ which is based on threads or uses a third-party system
+ such as `portalocker <https://pypi.python.org/pypi/portalocker>`_,
+ can be dropped in using the ``lock_factory`` argument
+ in conjunction with the :class:`.AbstractFileLock` base class.
+
+ Currently, the dogpile lock is against the entire
+ DBM file, not per key. This means there can
+ only be one "creator" job running at a time
+ per dbm file.
+
+ A future improvement might be to have the dogpile lock
+ using a filename that's based on a modulus of the key.
+ Locking on a filename that uniquely corresponds to the
+ key is problematic, since it's not generally safe to
+ delete lockfiles as the application runs, implying an
+ unlimited number of key-based files would need to be
+ created and never deleted.
+
+ Parameters to the ``arguments`` dictionary are
+ below.
+
+ :param filename: path of the filename in which to
+ create the DBM file. Note that some dbm backends
+ will change this name to have additional suffixes.
+ :param rw_lockfile: the name of the file to use for
+ read/write locking. If omitted, a default name
+ is used by appending the suffix ".rw.lock" to the
+ DBM filename. If False, then no lock is used.
+ :param dogpile_lockfile: the name of the file to use
+ for value creation, i.e. the dogpile lock. If
+ omitted, a default name is used by appending the
+ suffix ".dogpile.lock" to the DBM filename. If
+ False, then dogpile.cache uses the default dogpile
+ lock, a plain thread-based mutex.
+ :param lock_factory: a function or class which provides
+ for a read/write lock. Defaults to :class:`.FileLock`.
+ Custom implementations need to implement context-manager
+ based ``read()`` and ``write()`` functions - the
+ :class:`.AbstractFileLock` class is provided as a base class
+ which provides these methods based on individual read/write lock
+ functions. E.g. to replace the lock with the dogpile.core
+ :class:`.ReadWriteMutex`::
+
+ from dogpile.core.readwrite_lock import ReadWriteMutex
+ from dogpile.cache.backends.file import AbstractFileLock
+
+ class MutexLock(AbstractFileLock):
+ def __init__(self, filename):
+ self.mutex = ReadWriteMutex()
+
+ def acquire_read_lock(self, wait):
+ ret = self.mutex.acquire_read_lock(wait)
+ return wait or ret
+
+ def acquire_write_lock(self, wait):
+ ret = self.mutex.acquire_write_lock(wait)
+ return wait or ret
+
+ def release_read_lock(self):
+ return self.mutex.release_read_lock()
+
+ def release_write_lock(self):
+ return self.mutex.release_write_lock()
+
+ from dogpile.cache import make_region
+
+ region = make_region().configure(
+ "dogpile.cache.dbm",
+ expiration_time=300,
+ arguments={
+ "filename": "file.dbm",
+ "lock_factory": MutexLock
+ }
+ )
+
+ While the included :class:`.FileLock` uses ``os.flock()``, a
+ windows-compatible implementation can be built using a library
+ such as `portalocker <https://pypi.python.org/pypi/portalocker>`_.
+
+ .. versionadded:: 0.5.2
+
+
+
+ """
+ def __init__(self, arguments):
+ self.filename = os.path.abspath(
+ os.path.normpath(arguments['filename'])
+ )
+ dir_, filename = os.path.split(self.filename)
+
+ self.lock_factory = arguments.get("lock_factory", FileLock)
+ self._rw_lock = self._init_lock(
+ arguments.get('rw_lockfile'),
+ ".rw.lock", dir_, filename)
+ self._dogpile_lock = self._init_lock(
+ arguments.get('dogpile_lockfile'),
+ ".dogpile.lock",
+ dir_, filename,
+ util.KeyReentrantMutex.factory)
+
+ # TODO: make this configurable
+ if compat.py3k:
+ import dbm
+ else:
+ import anydbm as dbm
+ self.dbmmodule = dbm
+ self._init_dbm_file()
+
+ def _init_lock(self, argument, suffix, basedir, basefile, wrapper=None):
+ if argument is None:
+ lock = self.lock_factory(os.path.join(basedir, basefile + suffix))
+ elif argument is not False:
+ lock = self.lock_factory(
+ os.path.abspath(
+ os.path.normpath(argument)
+ ))
+ else:
+ return None
+ if wrapper:
+ lock = wrapper(lock)
+ return lock
+
+ def _init_dbm_file(self):
+ exists = os.access(self.filename, os.F_OK)
+ if not exists:
+ for ext in ('db', 'dat', 'pag', 'dir'):
+ if os.access(self.filename + os.extsep + ext, os.F_OK):
+ exists = True
+ break
+ if not exists:
+ fh = self.dbmmodule.open(self.filename, 'c')
+ fh.close()
+
+ def get_mutex(self, key):
+ # using one dogpile for the whole file. Other ways
+ # to do this might be using a set of files keyed to a
+ # hash/modulus of the key. the issue is it's never
+ # really safe to delete a lockfile as this can
+ # break other processes trying to get at the file
+ # at the same time - so handling unlimited keys
+ # can't imply unlimited filenames
+ if self._dogpile_lock:
+ return self._dogpile_lock(key)
+ else:
+ return None
+
+ @contextmanager
+ def _use_rw_lock(self, write):
+ if self._rw_lock is None:
+ yield
+ elif write:
+ with self._rw_lock.write():
+ yield
+ else:
+ with self._rw_lock.read():
+ yield
+
+ @contextmanager
+ def _dbm_file(self, write):
+ with self._use_rw_lock(write):
+ dbm = self.dbmmodule.open(
+ self.filename,
+ "w" if write else "r")
+ yield dbm
+ dbm.close()
+
+ def get(self, key):
+ with self._dbm_file(False) as dbm:
+ if hasattr(dbm, 'get'):
+ value = dbm.get(key, NO_VALUE)
+ else:
+ # gdbm objects lack a .get method
+ try:
+ value = dbm[key]
+ except KeyError:
+ value = NO_VALUE
+ if value is not NO_VALUE:
+ value = compat.pickle.loads(value)
+ return value
+
+ def get_multi(self, keys):
+ return [self.get(key) for key in keys]
+
+ def set(self, key, value):
+ with self._dbm_file(True) as dbm:
+ dbm[key] = compat.pickle.dumps(value,
+ compat.pickle.HIGHEST_PROTOCOL)
+
+ def set_multi(self, mapping):
+ with self._dbm_file(True) as dbm:
+ for key, value in mapping.items():
+ dbm[key] = compat.pickle.dumps(value,
+ compat.pickle.HIGHEST_PROTOCOL)
+
+ def delete(self, key):
+ with self._dbm_file(True) as dbm:
+ try:
+ del dbm[key]
+ except KeyError:
+ pass
+
+ def delete_multi(self, keys):
+ with self._dbm_file(True) as dbm:
+ for key in keys:
+ try:
+ del dbm[key]
+ except KeyError:
+ pass
+
+
+class AbstractFileLock(object):
+ """Coordinate read/write access to a file.
+
+ typically is a file-based lock but doesn't necessarily have to be.
+
+ The default implementation here is :class:`.FileLock`.
+
+ Implementations should provide the following methods::
+
+ * __init__()
+ * acquire_read_lock()
+ * acquire_write_lock()
+ * release_read_lock()
+ * release_write_lock()
+
+ The ``__init__()`` method accepts a single argument "filename", which
+ may be used as the "lock file", for those implementations that use a lock
+ file.
+
+ Note that multithreaded environments must provide a thread-safe
+ version of this lock. The recommended approach for file-
+ descriptor-based locks is to use a Python ``threading.local()`` so
+ that a unique file descriptor is held per thread. See the source
+ code of :class:`.FileLock` for an implementation example.
+
+
+ """
+
+ def __init__(self, filename):
+ """Constructor, is given the filename of a potential lockfile.
+
+ The usage of this filename is optional and no file is
+ created by default.
+
+ Raises ``NotImplementedError`` by default, must be
+ implemented by subclasses.
+ """
+ raise NotImplementedError()
+
+ def acquire(self, wait=True):
+ """Acquire the "write" lock.
+
+ This is a direct call to :meth:`.AbstractFileLock.acquire_write_lock`.
+
+ """
+ return self.acquire_write_lock(wait)
+
+ def release(self):
+ """Release the "write" lock.
+
+ This is a direct call to :meth:`.AbstractFileLock.release_write_lock`.
+
+ """
+ self.release_write_lock()
+
+ @contextmanager
+ def read(self):
+ """Provide a context manager for the "read" lock.
+
+ This method makes use of :meth:`.AbstractFileLock.acquire_read_lock`
+ and :meth:`.AbstractFileLock.release_read_lock`
+
+ """
+
+ self.acquire_read_lock(True)
+ try:
+ yield
+ finally:
+ self.release_read_lock()
+
+ @contextmanager
+ def write(self):
+ """Provide a context manager for the "write" lock.
+
+ This method makes use of :meth:`.AbstractFileLock.acquire_write_lock`
+ and :meth:`.AbstractFileLock.release_write_lock`
+
+ """
+
+ self.acquire_write_lock(True)
+ try:
+ yield
+ finally:
+ self.release_write_lock()
+
+ @property
+ def is_open(self):
+ """optional method."""
+ raise NotImplementedError()
+
+ def acquire_read_lock(self, wait):
+ """Acquire a 'reader' lock.
+
+ Raises ``NotImplementedError`` by default, must be
+ implemented by subclasses.
+ """
+ raise NotImplementedError()
+
+ def acquire_write_lock(self, wait):
+ """Acquire a 'write' lock.
+
+ Raises ``NotImplementedError`` by default, must be
+ implemented by subclasses.
+ """
+ raise NotImplementedError()
+
+ def release_read_lock(self):
+ """Release a 'reader' lock.
+
+ Raises ``NotImplementedError`` by default, must be
+ implemented by subclasses.
+ """
+ raise NotImplementedError()
+
+ def release_write_lock(self):
+ """Release a 'writer' lock.
+
+ Raises ``NotImplementedError`` by default, must be
+ implemented by subclasses.
+ """
+ raise NotImplementedError()
+
+
+class FileLock(AbstractFileLock):
+ """Use lockfiles to coordinate read/write access to a file.
+
+ Only works on Unix systems, using
+ `fcntl.flock() <http://docs.python.org/library/fcntl.html>`_.
+
+ """
+
+ def __init__(self, filename):
+ self._filedescriptor = compat.threading.local()
+ self.filename = filename
+
+ @util.memoized_property
+ def _module(self):
+ import fcntl
+ return fcntl
+
+ @property
+ def is_open(self):
+ return hasattr(self._filedescriptor, 'fileno')
+
+ def acquire_read_lock(self, wait):
+ return self._acquire(wait, os.O_RDONLY, self._module.LOCK_SH)
+
+ def acquire_write_lock(self, wait):
+ return self._acquire(wait, os.O_WRONLY, self._module.LOCK_EX)
+
+ def release_read_lock(self):
+ self._release()
+
+ def release_write_lock(self):
+ self._release()
+
+ def _acquire(self, wait, wrflag, lockflag):
+ wrflag |= os.O_CREAT
+ fileno = os.open(self.filename, wrflag)
+ try:
+ if not wait:
+ lockflag |= self._module.LOCK_NB
+ self._module.flock(fileno, lockflag)
+ except IOError:
+ os.close(fileno)
+ if not wait:
+ # this is typically
+ # "[Errno 35] Resource temporarily unavailable",
+ # because of LOCK_NB
+ return False
+ else:
+ raise
+ else:
+ self._filedescriptor.fileno = fileno
+ return True
+
+ def _release(self):
+ try:
+ fileno = self._filedescriptor.fileno
+ except AttributeError:
+ return
+ else:
+ self._module.flock(fileno, self._module.LOCK_UN)
+ os.close(fileno)
+ del self._filedescriptor.fileno

Powered by Google App Engine
This is Rietveld 408576698