diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index e51622a5..4c0f329f 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -473,7 +473,9 @@ class LessSimpleCache(BaseCache): def __init__(self, threshold=500, default_timeout=300): BaseCache.__init__(self, default_timeout=default_timeout) + self._mutex = threading.RLock() self._cache = {} + self._bypassed = set() self.clear = self._cache.clear self._threshold = threshold @@ -482,27 +484,35 @@ class LessSimpleCache(BaseCache): now = time.time() for idx, (key, (expires, _)) in enumerate(self._cache.items()): if expires is not None and expires <= now or idx % 3 == 0: - self._cache.pop(key, None) + with self._mutex: + self._cache.pop(key, None) def get(self, key): import pickle now = time.time() - expires, value = self._cache.get(key, (0, None)) + with self._mutex: + expires, value = self._cache.get(key, (0, None)) if expires is None or expires > now: return pickle.loads(value) def set(self, key, value, timeout=None): import pickle - self._prune() - self._cache[key] = (self.calculate_timeout(timeout=timeout), - pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) + + with self._mutex: + self._prune() + self._cache[key] = (self.calculate_timeout(timeout=timeout), + pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) + if key in self._bypassed: + self._bypassed.remove(key) def add(self, key, value, timeout=None): - self.set(key, value, timeout=None) - self._cache.setdefault(key, self._cache[key]) + with self._mutex: + self.set(key, value, timeout=None) + self._cache.setdefault(key, self._cache[key]) def delete(self, key): - self._cache.pop(key, None) + with self._mutex: + self._cache.pop(key, None) def calculate_timeout(self, timeout=None): if timeout is None: @@ -514,7 +524,8 @@ class LessSimpleCache(BaseCache): def over_threshold(self): if self._threshold is None: return False - return len(self._cache) > self._threshold + with self._mutex: + return len(self._cache) > self._threshold def __getitem__(self, key): return self.get(key) @@ -526,7 +537,16 @@ class LessSimpleCache(BaseCache): return self.delete(key) def __contains__(self, key): - return key in self._cache + with self._mutex: + return key in self._cache + + def set_bypassed(self, key): + with self._mutex: + self._bypassed.add(key) + + def is_bypassed(self, key): + with self._mutex: + return key in self._bypassed _cache = LessSimpleCache() @@ -552,11 +572,13 @@ def cached(timeout=5 * 60, key=lambda: "view:%s" % flask.request.path, unless=No # bypass the cache if "unless" condition is true if callable(unless) and unless(): logger.debug("Cache for {path} bypassed, calling wrapped function".format(path=flask.request.path)) + _cache.set_bypassed(cache_key) return f_with_duration(*args, **kwargs) # also bypass the cache if it's disabled completely if not settings().getBoolean(["devel", "cache", "enabled"]): logger.debug("Cache for {path} disabled, calling wrapped function".format(path=flask.request.path)) + _cache.set_bypassed(cache_key) return f_with_duration(*args, **kwargs) rv = _cache.get(cache_key) @@ -575,6 +597,7 @@ def cached(timeout=5 * 60, key=lambda: "view:%s" % flask.request.path, unless=No # do not store if the "unless_response" condition is true if callable(unless_response) and unless_response(rv): logger.debug("Not caching result for {path} (key: {key}), bypassed".format(path=flask.request.path, key=cache_key)) + _cache.set_bypassed(cache_key) return rv # store it in the cache @@ -591,6 +614,11 @@ def is_in_cache(key=lambda: "view:%s" % flask.request.path): key = key() return key in _cache +def is_cache_bypassed(key=lambda: "view:%s" % flask.request.path): + if callable(key): + key = key() + return _cache.is_bypassed(key) + def cache_check_headers(): return "no-cache" in flask.request.cache_control or "no-cache" in flask.request.pragma diff --git a/src/octoprint/server/views.py b/src/octoprint/server/views.py index 7153ea55..9b35d47b 100644 --- a/src/octoprint/server/views.py +++ b/src/octoprint/server/views.py @@ -146,6 +146,9 @@ def in_cache(): elif util.flask.is_in_cache(key): _logger.info("Found path {} in cache (key: {}), signaling as cached".format(path, key)) return response + elif util.flask.is_cache_bypassed(key): + _logger.info("Path {} was bypassed from cache (key: {}), signaling as cached".format(path, key)) + return response else: _logger.debug("Path {} not yet cached (key: {}), signaling as missing".format(path, key)) return abort(404)