Skip to content

Commit ca1aee6

Browse files
lmignonlembregtse
authored andcommitted
[FIX] fastapi: Ensure thread safety of the FastAPI app cache
defaultdict in python is not thread safe. Since this data structure is used to store the cache of FastAPI apps, we must ensure that the access to this cache is thread safe. This is done by using a lock to protect the access to the cache.
1 parent 1f7d17e commit ca1aee6

1 file changed

Lines changed: 15 additions & 6 deletions

File tree

fastapi/pools/fastapi_app.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# License LGPL-3.0 or later (http://www.gnu.org/licenses/LGPL).
33

44
import queue
5+
import threading
56
from collections import defaultdict
67
from contextlib import contextmanager
78
from typing import Generator
@@ -16,18 +17,25 @@ def __init__(self):
1617
self._queue_by_db_by_root_path: dict[
1718
str, dict[str, queue.Queue[FastAPI]]
1819
] = defaultdict(lambda: defaultdict(queue.Queue))
20+
self._lock = threading.Lock()
1921

20-
def __get_app(self, env: Environment, root_path: str) -> FastAPI:
22+
def __get_pool(self, env: Environment, root_path: str) -> queue.Queue[FastAPI]:
2123
db_name = env.cr.dbname
24+
with self._lock:
25+
# default dict is not thread safe but the use
26+
return self._queue_by_db_by_root_path[db_name][root_path]
27+
28+
def __get_app(self, env: Environment, root_path: str) -> FastAPI:
29+
pool = self.__get_pool(env, root_path)
2230
try:
23-
return self._queue_by_db_by_root_path[db_name][root_path].get_nowait()
31+
return pool.get_nowait()
2432
except queue.Empty:
2533
env["fastapi.endpoint"].sudo()
2634
return env["fastapi.endpoint"].sudo().get_app(root_path)
2735

2836
def __return_app(self, env: Environment, app: FastAPI, root_path: str) -> None:
29-
db_name = env.cr.dbname
30-
self._queue_by_db_by_root_path[db_name][root_path].put(app)
37+
pool = self.__get_pool(env, root_path)
38+
pool.put(app)
3139

3240
@contextmanager
3341
def get_app(
@@ -48,5 +56,6 @@ def get_app(
4856
self.__return_app(env, app, root_path)
4957

5058
def invalidate(self, root_path: str, env: Environment) -> None:
51-
db_name = env.cr.dbname
52-
self._queue_by_db_by_root_path[db_name][root_path] = queue.Queue()
59+
with self._lock:
60+
db_name = env.cr.dbname
61+
self._queue_by_db_by_root_path[db_name][root_path] = queue.Queue()

0 commit comments

Comments
 (0)