44from typing import Callable , Coroutine , Dict , List , Optional , Tuple
55from uuid import UUID
66
7+ from cachetools import TTLCache
78from sqlalchemy import delete , update
89from sqlalchemy .ext .asyncio import AsyncSession
910
@@ -187,27 +188,25 @@ async def delete_backends(
187188BackendTuple = Tuple [BackendModel , Backend ]
188189
189190
190- _BACKENDS_CACHE_LOCKS = {}
191- _BACKENDS_CACHE : Dict [UUID , Dict [BackendType , BackendTuple ]] = {}
191+ _BACKENDS_CACHE_LOCKS : Dict [ UUID , asyncio . Lock ] = {}
192+ _BACKENDS_CACHE = TTLCache [UUID , Dict [BackendType , BackendTuple ]]( maxsize = 1000 , ttl = 300 )
192193
193194
194195def _get_project_cache_lock (project_id : UUID ) -> asyncio .Lock :
195196 return _BACKENDS_CACHE_LOCKS .setdefault (project_id , asyncio .Lock ())
196197
197198
198199async def get_project_backends_with_models (project : ProjectModel ) -> List [BackendTuple ]:
199- backends = []
200200 async with _get_project_cache_lock (project .id ):
201201 key = project .id
202- project_backends_cache = _BACKENDS_CACHE .setdefault (key , {})
202+ project_backends = _BACKENDS_CACHE .get (key , {})
203203 for backend_model in project .backends :
204- cached_backend = project_backends_cache .get (backend_model .type )
204+ cached_backend = project_backends .get (backend_model .type )
205205 if (
206206 cached_backend is not None
207207 and cached_backend [0 ].config == backend_model .config
208208 and cached_backend [0 ].auth == backend_model .auth
209209 ):
210- backends .append (cached_backend )
211210 continue
212211 configurator = get_configurator (backend_model .type )
213212 if configurator is None :
@@ -231,9 +230,13 @@ async def get_project_backends_with_models(project: ProjectModel) -> List[Backen
231230 backend_model .type .value ,
232231 )
233232 continue
234- backends .append ((backend_model , backend ))
235- _BACKENDS_CACHE [key ][backend_model .type ] = (backend_model , backend )
236- return backends
233+ project_backends [backend_model .type ] = (backend_model , backend )
234+ # `__setitem__()` will also expire the cache.
235+ # Note that there is no global cache lock so a race condition is possible:
236+ # one coroutine updates/re-assigns backends expired by another coroutine.
237+ # This is ok since the only effect is that project's cache gets restored.
238+ _BACKENDS_CACHE [key ] = project_backends
239+ return list (project_backends .values ())
237240
238241
239242_get_project_backend_with_model_by_type = None
0 commit comments