diff options
author | Andrew Morgan <andrew@amorgan.xyz> | 2023-03-08 12:18:31 +0000 |
---|---|---|
committer | Andrew Morgan <andrew@amorgan.xyz> | 2023-03-08 12:18:31 +0000 |
commit | 687c9cf0657762d02841028e3749ccd1a4a11c74 (patch) | |
tree | ace1b09545e34b144953dc6cba96a2f30375fa8b | |
parent | Add support for devenv developer environments (diff) | |
download | synapse-687c9cf0657762d02841028e3749ccd1a4a11c74.tar.xz |
Extract @cache_in_self decorator to its own utils file
-rw-r--r-- | synapse/util/caches/cache_in_self_decorator.py | 44 |
1 files changed, 44 insertions, 0 deletions
diff --git a/synapse/util/caches/cache_in_self_decorator.py b/synapse/util/caches/cache_in_self_decorator.py new file mode 100644 index 0000000000..fea41b9d08 --- /dev/null +++ b/synapse/util/caches/cache_in_self_decorator.py @@ -0,0 +1,44 @@ +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, TypeVar, cast + +T = TypeVar("T") + + +def cache_in_self(builder: Callable[["HomeServer"], T]) -> Callable[["HomeServer"], T]: + """Wraps a function called e.g. `get_foo`, checking if `self.foo` exists and + returning if so. If not, calls the given function and sets `self.foo` to it. + + Also ensures that dependency cycles throw an exception correctly, rather + than overflowing the stack. + """ + + if not builder.__name__.startswith("get_"): + raise Exception( + "@cache_in_self can only be used on functions starting with `get_`" + ) + + # get_attr -> _attr + depname = builder.__name__[len("get") :] + + building = [False] + + @functools.wraps(builder) + def _get(self: "HomeServer") -> T: + try: + return getattr(self, depname) + except AttributeError: + pass + + # Prevent cyclic dependencies from deadlocking + if building[0]: + raise ValueError("Cyclic dependency while building %s" % (depname,)) + + building[0] = True + try: + dep = builder(self) + setattr(self, depname, dep) + finally: + building[0] = False + + return dep + + return _get |