summaryrefslogtreecommitdiff
path: root/dogpile/core/nameregistry.py
blob: b22fc34e0d237a6848de2ec5c1c078dda0361c97 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from .util import threading
import weakref

class NameRegistry(object):
    """Generates and return an object, keeping it as a
    singleton for a certain identifier for as long as its
    strongly referenced.

    .. note::

        The :class:`.NameRegistry` exists here to support
        certain usage patterns by the deprecated
        :class:`.Dogpile` object.   It is still potentially
        useful in other cases, however.

    e.g.::

        class MyFoo(object):
            "some important object."
            def __init__(self, identifier):
                self.identifier = identifier

        registry = NameRegistry(MyFoo)

        # thread 1:
        my_foo = registry.get("foo1")

        # thread 2
        my_foo = registry.get("foo1")

    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 :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.

    :param creator: A function that will create a new
     value, given the identifier passed to the :meth:`.NameRegistry.get`
     method.

    """
    _locks = weakref.WeakValueDictionary()
    _mutex = threading.RLock()

    def __init__(self, creator):
        """Create a new :class:`.NameRegistry`.


        """
        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]
            else:
                return self._sync_get(identifier, *args, **kw)
        except KeyError:
            return self._sync_get(identifier, *args, **kw)

    def _sync_get(self, identifier, *args, **kw):
        self._mutex.acquire()
        try:
            try:
                if identifier in self._values:
                    return self._values[identifier]
                else:
                    self._values[identifier] = value = self.creator(identifier, *args, **kw)
                    return value
            except KeyError:
                self._values[identifier] = value = self.creator(identifier, *args, **kw)
                return value
        finally:
            self._mutex.release()