summaryrefslogtreecommitdiff
path: root/dogpile
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2011-11-17 14:16:10 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2011-11-17 14:16:10 -0500
commite3f1bcfdc9ec1ab9acf09e3cfa366a44033da5e4 (patch)
treed7a29ccf718e752d38f4be33e93a676b21ae1b83 /dogpile
parent4fa66beb0cfeec213ba9ef5dc9606cb2abe6de57 (diff)
downloaddogpile-core-e3f1bcfdc9ec1ab9acf09e3cfa366a44033da5e4.tar.gz
docs
Diffstat (limited to 'dogpile')
-rw-r--r--dogpile/dogpile.py126
-rw-r--r--dogpile/nameregistry.py38
-rw-r--r--dogpile/readwrite_lock.py16
3 files changed, 87 insertions, 93 deletions
diff --git a/dogpile/dogpile.py b/dogpile/dogpile.py
index 94a50c8..f6b1d11 100644
--- a/dogpile/dogpile.py
+++ b/dogpile/dogpile.py
@@ -1,72 +1,7 @@
-"""A "dogpile" lock, one which allows a single thread to generate
-an expensive resource while other threads use the "old" value, until the
-"new" value is ready.
-
-Usage::
-
- # store a reference to a "resource", some
- # object that is expensive to create.
- the_resource = [None]
-
- def some_creation_function():
- # create the resource here
- the_resource[0] = create_some_resource()
-
- def use_the_resource():
- # some function that uses
- # the resource. Won't reach
- # here until some_creation_function()
- # has completed at least once.
- the_resource[0].do_something()
-
- # create Dogpile with 3600 second
- # expiry time
- dogpile = Dogpile(3600)
-
- with dogpile.acquire(some_creation_function):
- use_the_resource()
-
-Above, ``some_creation_function()`` will be called
-when :meth:`.Dogpile.acquire` is first called. The
-block then proceeds. Concurrent threads which
-call :meth:`.Dogpile.acquire` during this initial period
-will block until ``some_creation_function()`` completes.
-
-Once the creation function has completed successfully,
-new calls to :meth:`.Dogpile.acquire` will route a single
-thread into new calls of ``some_creation_function()``
-each time the expiration time is reached. Concurrent threads
-which call :meth:`.Dogpile.acquire` during this period will
-fall through, and not be blocked. It is expected that
-the "stale" version of the resource remain available at this
-time while the new one is generated.
-
-The dogpile lock can also provide a mutex to the creation
-function itself, so that the creation function can perform
-certain tasks only after all "stale reader" threads have finished.
-The example of this is when the creation function has prepared a new
-datafile to replace the old one, and would like to switch in the
-"new" file only when other threads have finished using it.
-
-To enable this feature, use :class:`.SyncReaderDogpile`.
-Then use :meth:`.SyncReaderDogpile.acquire_write_lock` for the critical section
-where readers should be blocked::
-
- from dogpile import SyncReaderDogpile
-
- dogpile = SyncReaderDogpile(3600)
-
- def some_creation_function():
- create_expensive_datafile()
- with dogpile.acquire_write_lock():
- replace_old_datafile_with_new()
-
-"""
from util import thread, threading
import time
import logging
from readwrite_lock import ReadWriteMutex
-from nameregistry import NameRegistry
log = logging.getLogger(__name__)
@@ -80,36 +15,41 @@ class NeedRegenerationException(Exception):
NOT_REGENERATED = object()
class Dogpile(object):
- """Dogpile class.
+ """Dogpile lock class.
- :param expiretime: Expiration time in seconds.
+ Provides an interface around an arbitrary mutex that allows one
+ thread/process to be elected as the creator of a new value,
+ while other threads/processes continue to return the previous version
+ of that value.
"""
- def __init__(self, expiretime, init=False):
- self.dogpilelock = threading.Lock()
+ def __init__(self, expiretime, init=False, lock=None):
+ """Construct a new :class:`.Dogpile`.
+
+ :param expiretime: Expiration time in seconds.
+ :param init: if True, set the 'createdtime' to the
+ current time.
+ :param lock: a mutex object that provides
+ ``acquire()`` and ``release()`` methods.
+
+ """
+ if lock:
+ self.dogpilelock = lock
+ else:
+ self.dogpilelock = threading.Lock()
self.expiretime = expiretime
if init:
self.createdtime = time.time()
- else:
- self.createdtime = -1
-
- @clasmethod
- def registry(cls, *arg, **kw):
- """Return a name-based registry of :class:`.Dogpile` objects.
-
- The registry is an instance of :class:`.NameRegistry`,
- and calling its ``get()`` method with an identifying
- key (anything hashable) will construct a new :class:`.Dogpile`
- object, keyed to that key. Subsequent usages will return
- the same :class:`.Dogpile` object for as long as the
- object remains in scope.
- The given arguments are passed along to the underlying
- constructor of the :class:`.Dogpile` class.
+ createdtime = -1
+ """The last known 'creation time' of the value,
+ stored as an epoch (i.e. from ``time.time()``).
- """
- return NameRegistry(lambda identifier: cls(*arg, **kw))
+ If the value here is -1, it is assumed the value
+ should recreate immediately.
+
+ """
def acquire(self, creator,
value_fn=None,
@@ -129,8 +69,7 @@ class Dogpile(object):
lock. This option removes the need for the dogpile lock
itself to remain persistent across usages; another
dogpile can come along later and pick up where the
- previous one left off. Should be used in conjunction
- with a :class:`.NameRegistry`.
+ previous one left off.
"""
dogpile = self
@@ -214,11 +153,22 @@ class Dogpile(object):
pass
class SyncReaderDogpile(Dogpile):
+ """Provide a read-write lock function on top of the :class:`.Dogpile`
+ class.
+
+ """
def __init__(self, *args, **kw):
super(SyncReaderDogpile, self).__init__(*args, **kw)
self.readwritelock = ReadWriteMutex()
def acquire_write_lock(self):
+ """Return the "write" lock context manager.
+
+ This will provide a section that is mutexed against
+ all readers/writers for the dogpile-maintained value.
+
+ """
+
dogpile = self
class Lock(object):
def __enter__(self):
diff --git a/dogpile/nameregistry.py b/dogpile/nameregistry.py
index cedcf52..159526e 100644
--- a/dogpile/nameregistry.py
+++ b/dogpile/nameregistry.py
@@ -10,6 +10,8 @@ class NameRegistry(object):
class MyFoo(object):
"some important object."
+ def __init__(self, identifier):
+ self.identifier = identifier
registry = NameRegistry(MyFoo)
@@ -19,24 +21,52 @@ class NameRegistry(object):
# thread 2
my_foo = registry.get("foo1")
- Above, "my_foo" in both thread #1 and #2 will
- be *the same object*.
+ Above, ``my_foo`` in both thread #1 and #2 will
+ be *the same object*. The constructor for
+ ``MyFoo`` will be called once, passing the
+ identifier ``foo1`` as the argument.
When thread 1 and thread 2 both complete or
- otherwise delete references to "my_foo", the
- object is *removed* from the NameRegistry as
+ otherwise delete references to ``my_foo``, the
+ object is *removed* from the :class:`.NameRegistry` as
a result of Python garbage collection.
+ :class:`.NameRegistry` is a utility object that
+ is used to maintain new :class:`.Dogpile` objects
+ against a certain key, for as long as that particular key
+ is referenced within the application. An application
+ can deal with an arbitrary number of keys, ensuring that
+ all threads requesting a certain key use the same
+ :class:`.Dogpile` object, without the need to maintain
+ each :class:`.Dogpile` object persistently in memory.
+
"""
_locks = weakref.WeakValueDictionary()
_mutex = threading.RLock()
def __init__(self, creator):
+ """Create a new :class:`.NameRegistry`.
+
+ :param creator: A function that will create a new
+ value, given the identifier passed to the :meth:`.NameRegistry.get`
+ method.
+
+ """
self._values = weakref.WeakValueDictionary()
self._mutex = threading.RLock()
self.creator = creator
def get(self, identifier, *args, **kw):
+ """Get and possibly create the value.
+
+ :param identifier: Hash key for the value.
+ If the creation function is called, this identifier
+ will also be passed to the creation function.
+ :param \*args, \**kw: Additional arguments which will
+ also be passed to the creation function if it is
+ called.
+
+ """
try:
if identifier in self._values:
return self._values[identifier]
diff --git a/dogpile/readwrite_lock.py b/dogpile/readwrite_lock.py
index 51498f6..0c9cc2b 100644
--- a/dogpile/readwrite_lock.py
+++ b/dogpile/readwrite_lock.py
@@ -4,7 +4,17 @@ except ImportError:
import dummy_threading as threading
class ReadWriteMutex(object):
- """A mutex which allows multiple readers, single writer."""
+ """A mutex which allows multiple readers, single writer.
+
+ :class:`.ReadWriteMutex` uses a Python ``threading.Condition``
+ to provide this functionality across threads within a process.
+
+ The Beaker package also contained a file-lock based version
+ of this concept, so that readers/writers could be synchronized
+ across processes with a common filesystem. A future Dogpile
+ release may include this additional class at some point.
+
+ """
def __init__(self):
# counts how many asynchronous methods are executing
@@ -17,6 +27,7 @@ class ReadWriteMutex(object):
self.condition = threading.Condition(threading.Lock())
def acquire_read_lock(self, wait = True):
+ """Acquire the 'read' lock."""
self.condition.acquire()
try:
# see if a synchronous operation is waiting to start
@@ -37,6 +48,7 @@ class ReadWriteMutex(object):
return True
def release_read_lock(self):
+ """Release the 'read' lock."""
self.condition.acquire()
try:
self.async -= 1
@@ -55,6 +67,7 @@ class ReadWriteMutex(object):
self.condition.release()
def acquire_write_lock(self, wait = True):
+ """Acquire the 'write' lock."""
self.condition.acquire()
try:
# here, we are not a synchronous reader, and after returning,
@@ -91,6 +104,7 @@ class ReadWriteMutex(object):
return True
def release_write_lock(self):
+ """Release the 'write' lock."""
self.condition.acquire()
try:
if self.current_sync_operation is not threading.currentThread():