From fbbe011a8d42153d49b7abf4074d9d0b388a55ec Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sat, 30 May 2026 11:19:32 +0800 Subject: [PATCH] fix: rp_caches race condition causing intermittent 403 load_roleperms() was setting self.rp_caches = {} before the async DB query. During the await, other coroutines saw {} (not None), skipped the load, and checked permissions against an empty dict, causing intermittent 403 on random paths. Fix: build in local dict first, assign atomically when complete. --- rbac/userperm.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rbac/userperm.py b/rbac/userperm.py index 46fd228..5408687 100644 --- a/rbac/userperm.py +++ b/rbac/userperm.py @@ -237,7 +237,9 @@ class UserPermissions: if _cache_enabled('rbac') and self.rp_caches is not None and (now - self.rp_cache_loaded_at) < self.rp_cache_ttl: return - self.rp_caches = {} + # Build in local dict first, assign atomically when complete. + # Otherwise other coroutines see {} during the await and get 403. + new_caches = {} sql_all = """select c.id, c.orgtypeid, c.name, b.path from rolepermission a, permission b, role c where a.permid = b.id @@ -253,9 +255,11 @@ order by c.orgtypeid, c.name""" k = 'logined' else: k = f'{r.orgtypeid}.{r.name}' - arr = self.rp_caches.get(k, []) + arr = new_caches.get(k, []) arr.append(r.path) - self.rp_caches[k] = arr + new_caches[k] = arr + # Atomic swap: other coroutines see old cache or fully-loaded new cache, never {} + self.rp_caches = new_caches self.rp_cache_loaded_at = now async def get_userroles(self, sor, userid):