diff --git a/frappe/tests/test_perf.py b/frappe/tests/test_perf.py index 66db3eeb37..d441ac65ce 100644 --- a/frappe/tests/test_perf.py +++ b/frappe/tests/test_perf.py @@ -204,6 +204,16 @@ class TestPerformance(IntegrationTestCase): with self.assertRedisCallCounts(1): frappe.get_doc("User", "Administrator") + def test_one_time_setup(self): + site = frappe.local.site + frappe.init(site, force=True) + run = frappe.qb._BuilderClasss.run + + frappe.init(site, force=True) + patched_run = frappe.qb._BuilderClasss.run + + self.assertIs(run, patched_run, "frappe.init should run one-time patching code just once") + @run_only_if(db_type_is.MARIADB) class TestOverheadCalls(FrappeAPITestCase): diff --git a/frappe/utils/caching.py b/frappe/utils/caching.py index 2299c95236..13897fe49a 100644 --- a/frappe/utils/caching.py +++ b/frappe/utils/caching.py @@ -1,8 +1,7 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. Check LICENSE -import datetime -import json +import time from collections import defaultdict from collections.abc import Callable from functools import wraps @@ -113,33 +112,28 @@ def site_cache(ttl: int | None = None, maxsize: int | None = None) -> Callable: if ttl is not None and not callable(ttl): func.ttl = ttl - func.expiration = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta( - seconds=func.ttl - ) + func.expiration = time.monotonic() + func.ttl if maxsize is not None and not callable(maxsize): func.maxsize = maxsize @wraps(func) def site_cache_wrapper(*args, **kwargs): - if getattr(frappe.local, "initialised", None): - func_call_key = json.dumps((args, kwargs)) + if site := getattr(frappe.local, "site", None): + func_call_key = __generate_request_cache_key(args, kwargs) - if hasattr(func, "ttl") and datetime.datetime.now(datetime.timezone.utc) >= func.expiration: + if hasattr(func, "ttl") and time.monotonic() >= func.expiration: func.clear_cache() - func.expiration = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta( - seconds=func.ttl - ) + func.expiration = time.monotonic() + func.ttl - if hasattr(func, "maxsize") and len(_SITE_CACHE[func_key][frappe.local.site]) >= func.maxsize: - _SITE_CACHE[func_key][frappe.local.site].pop( - next(iter(_SITE_CACHE[func_key][frappe.local.site])), None - ) + if hasattr(func, "maxsize") and len(_SITE_CACHE[func_key][site]) >= func.maxsize: + # Note: This implements FIFO eviction policty + _SITE_CACHE[func_key][site].pop(next(iter(_SITE_CACHE[func_key][site])), None) - if func_call_key not in _SITE_CACHE[func_key][frappe.local.site]: - _SITE_CACHE[func_key][frappe.local.site][func_call_key] = func(*args, **kwargs) + if func_call_key not in _SITE_CACHE[func_key][site]: + _SITE_CACHE[func_key][site][func_call_key] = func(*args, **kwargs) - return _SITE_CACHE[func_key][frappe.local.site][func_call_key] + return _SITE_CACHE[func_key][site][func_call_key] return func(*args, **kwargs)