Add cancellation support to `@cached` and `@cachedList` decorators (#12183)
These decorators mostly support cancellation already. Add cancellation
tests and fix use of finished logging contexts by delaying cancellation,
as suggested by @erikjohnston.
Signed-off-by: Sean Quah <seanq@element.io>
1 files changed, 11 insertions, 0 deletions
diff --git a/synapse/util/caches/descriptors.py b/synapse/util/caches/descriptors.py
index c3c5c16db9..eda92d864d 100644
--- a/synapse/util/caches/descriptors.py
+++ b/synapse/util/caches/descriptors.py
@@ -41,6 +41,7 @@ from twisted.python.failure import Failure
from synapse.logging.context import make_deferred_yieldable, preserve_fn
from synapse.util import unwrapFirstError
+from synapse.util.async_helpers import delay_cancellation
from synapse.util.caches.deferred_cache import DeferredCache
from synapse.util.caches.lrucache import LruCache
@@ -350,6 +351,11 @@ class DeferredCacheDescriptor(_CacheDescriptorBase):
ret = defer.maybeDeferred(preserve_fn(self.orig), obj, *args, **kwargs)
ret = cache.set(cache_key, ret, callback=invalidate_callback)
+ # We started a new call to `self.orig`, so we must always wait for it to
+ # complete. Otherwise we might mark our current logging context as
+ # finished while `self.orig` is still using it in the background.
+ ret = delay_cancellation(ret)
+
return make_deferred_yieldable(ret)
wrapped = cast(_CachedFunction, _wrapped)
@@ -510,6 +516,11 @@ class DeferredCacheListDescriptor(_CacheDescriptorBase):
d = defer.gatherResults(cached_defers, consumeErrors=True).addCallbacks(
lambda _: results, unwrapFirstError
)
+ if missing:
+ # We started a new call to `self.orig`, so we must always wait for it to
+ # complete. Otherwise we might mark our current logging context as
+ # finished while `self.orig` is still using it in the background.
+ d = delay_cancellation(d)
return make_deferred_yieldable(d)
else:
return defer.succeed(results)
|