CacheInvalidation: fixes for corner cases
- call the `retriever` function at most once per component when a new generation is reached, not once for every out-of-date thread
- call the `retriever` function after the new generation value is retrieved. In case there's a race, at least the next `get` will be able to call the `retriever` function again
diff --git a/trac/cache.py b/trac/cache.py
|
a
|
b
|
|
| 131 | 131 | self._local.cache = local_cache = self._cache.copy() |
| 132 | 132 | |
| 133 | 133 | db_generation = local_meta.get(key, -1) |
| | 134 | generation = -1 |
| 134 | 135 | |
| 135 | 136 | # Try the thread-local copy first |
| 136 | 137 | try: |
| … |
… |
|
| 150 | 151 | except KeyError: |
| 151 | 152 | pass |
| 152 | 153 | |
| 153 | | # Retrieve data from the database |
| | 154 | if generation > db_generation: |
| | 155 | # component's cache is newer than per-thread cache |
| | 156 | local_meta[key] = generation |
| | 157 | return data |
| | 158 | |
| | 159 | # Retrieve data using the retriever callback |
| 154 | 160 | self.log.debug("***** Retrieving data for '%s'", key) |
| | 161 | # retrieve current generation |
| 155 | 162 | if db is None: |
| 156 | 163 | db = self.env.get_db_cnx() |
| 157 | | data = retriever(db) |
| 158 | 164 | cursor = db.cursor() |
| 159 | 165 | cursor.execute("SELECT generation FROM cache WHERE key=%s", (key,)) |
| 160 | 166 | row = cursor.fetchone() |
| 161 | 167 | generation = row and row[0] or -1 |
| | 168 | # retrieve data for at least this generation |
| | 169 | data = retriever(db) |
| 162 | 170 | local_cache[key] = self._cache[key] = (data, generation) |
| 163 | 171 | local_meta[key] = generation |
| 164 | 172 | return data |