OLD | NEW |
(Empty) | |
| 1 import time |
| 2 import logging |
| 3 |
| 4 log = logging.getLogger(__name__) |
| 5 |
| 6 |
| 7 class NeedRegenerationException(Exception): |
| 8 """An exception that when raised in the 'with' block, |
| 9 forces the 'has_value' flag to False and incurs a |
| 10 regeneration of the value. |
| 11 |
| 12 """ |
| 13 |
| 14 NOT_REGENERATED = object() |
| 15 |
| 16 |
| 17 class Lock(object): |
| 18 """Dogpile lock class. |
| 19 |
| 20 Provides an interface around an arbitrary mutex |
| 21 that allows one thread/process to be elected as |
| 22 the creator of a new value, while other threads/processes |
| 23 continue to return the previous version |
| 24 of that value. |
| 25 |
| 26 :param mutex: A mutex object that provides ``acquire()`` |
| 27 and ``release()`` methods. |
| 28 :param creator: Callable which returns a tuple of the form |
| 29 (new_value, creation_time). "new_value" should be a newly |
| 30 generated value representing completed state. "creation_time" |
| 31 should be a floating point time value which is relative |
| 32 to Python's ``time.time()`` call, representing the time |
| 33 at which the value was created. This time value should |
| 34 be associated with the created value. |
| 35 :param value_and_created_fn: Callable which returns |
| 36 a tuple of the form (existing_value, creation_time). This |
| 37 basically should return what the last local call to the ``creator()`` |
| 38 callable has returned, i.e. the value and the creation time, |
| 39 which would be assumed here to be from a cache. If the |
| 40 value is not available, the :class:`.NeedRegenerationException` |
| 41 exception should be thrown. |
| 42 :param expiretime: Expiration time in seconds. Set to |
| 43 ``None`` for never expires. This timestamp is compared |
| 44 to the creation_time result and ``time.time()`` to determine if |
| 45 the value returned by value_and_created_fn is "expired". |
| 46 :param async_creator: A callable. If specified, this callable will be |
| 47 passed the mutex as an argument and is responsible for releasing the mutex |
| 48 after it finishes some asynchronous value creation. The intent is for |
| 49 this to be used to defer invocation of the creator callable until some |
| 50 later time. |
| 51 |
| 52 """ |
| 53 |
| 54 def __init__( |
| 55 self, |
| 56 mutex, |
| 57 creator, |
| 58 value_and_created_fn, |
| 59 expiretime, |
| 60 async_creator=None, |
| 61 ): |
| 62 self.mutex = mutex |
| 63 self.creator = creator |
| 64 self.value_and_created_fn = value_and_created_fn |
| 65 self.expiretime = expiretime |
| 66 self.async_creator = async_creator |
| 67 |
| 68 def _is_expired(self, createdtime): |
| 69 """Return true if the expiration time is reached, or no |
| 70 value is available.""" |
| 71 |
| 72 return not self._has_value(createdtime) or \ |
| 73 ( |
| 74 self.expiretime is not None and |
| 75 time.time() - createdtime > self.expiretime |
| 76 ) |
| 77 |
| 78 def _has_value(self, createdtime): |
| 79 """Return true if the creation function has proceeded |
| 80 at least once.""" |
| 81 return createdtime > 0 |
| 82 |
| 83 def _enter(self): |
| 84 value_fn = self.value_and_created_fn |
| 85 |
| 86 try: |
| 87 value = value_fn() |
| 88 value, createdtime = value |
| 89 except NeedRegenerationException: |
| 90 log.debug("NeedRegenerationException") |
| 91 value = NOT_REGENERATED |
| 92 createdtime = -1 |
| 93 |
| 94 generated = self._enter_create(createdtime) |
| 95 |
| 96 if generated is not NOT_REGENERATED: |
| 97 generated, createdtime = generated |
| 98 return generated |
| 99 elif value is NOT_REGENERATED: |
| 100 try: |
| 101 value, createdtime = value_fn() |
| 102 return value |
| 103 except NeedRegenerationException: |
| 104 raise Exception("Generation function should " |
| 105 "have just been called by a concurrent " |
| 106 "thread.") |
| 107 else: |
| 108 return value |
| 109 |
| 110 def _enter_create(self, createdtime): |
| 111 |
| 112 if not self._is_expired(createdtime): |
| 113 return NOT_REGENERATED |
| 114 |
| 115 async = False |
| 116 |
| 117 if self._has_value(createdtime): |
| 118 if not self.mutex.acquire(False): |
| 119 log.debug("creation function in progress " |
| 120 "elsewhere, returning") |
| 121 return NOT_REGENERATED |
| 122 else: |
| 123 log.debug("no value, waiting for create lock") |
| 124 self.mutex.acquire() |
| 125 |
| 126 try: |
| 127 log.debug("value creation lock %r acquired" % self.mutex) |
| 128 |
| 129 # see if someone created the value already |
| 130 try: |
| 131 value, createdtime = self.value_and_created_fn() |
| 132 except NeedRegenerationException: |
| 133 pass |
| 134 else: |
| 135 if not self._is_expired(createdtime): |
| 136 log.debug("value already present") |
| 137 return value, createdtime |
| 138 elif self.async_creator: |
| 139 log.debug("Passing creation lock to async runner") |
| 140 self.async_creator(self.mutex) |
| 141 async = True |
| 142 return value, createdtime |
| 143 |
| 144 log.debug("Calling creation function") |
| 145 created = self.creator() |
| 146 return created |
| 147 finally: |
| 148 if not async: |
| 149 self.mutex.release() |
| 150 log.debug("Released creation lock") |
| 151 |
| 152 |
| 153 def __enter__(self): |
| 154 return self._enter() |
| 155 |
| 156 def __exit__(self, type, value, traceback): |
| 157 pass |
| 158 |
OLD | NEW |