Skip to content

Commit 65c3d55

Browse files
author
Pijush Chakraborty
committed
Adding re-entrant lock to make template cache updates/reads atomic
1 parent 67ef909 commit 65c3d55

File tree

1 file changed

+19
-5
lines changed

1 file changed

+19
-5
lines changed

firebase_admin/remote_config.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import asyncio
2020
import json
2121
import logging
22+
import threading
2223
from typing import Dict, Optional, Literal, Union, Any
2324
from enum import Enum
2425
import re
@@ -138,6 +139,7 @@ def __init__(self, app: App = None, default_config: Optional[Dict[str, str]] = N
138139
# fetched from RC servers via the load API, or via the set API.
139140
self._cache = None
140141
self._stringified_default_config: Dict[str, str] = {}
142+
self._lock = threading.RLock()
141143

142144
# RC stores all remote values as string, but it's more intuitive
143145
# to declare default values with specific types, so this converts
@@ -148,7 +150,9 @@ def __init__(self, app: App = None, default_config: Optional[Dict[str, str]] = N
148150

149151
async def load(self):
150152
"""Fetches the server template and caches the data."""
151-
self._cache = await self._rc_service.get_server_template()
153+
rc_server_template = await self._rc_service.get_server_template()
154+
with self._lock:
155+
self._cache = rc_server_template
152156

153157
def evaluate(self, context: Optional[Dict[str, Union[str, int]]] = None) -> 'ServerConfig':
154158
"""Evaluates the cached server template to produce a ServerConfig.
@@ -167,12 +171,17 @@ def evaluate(self, context: Optional[Dict[str, Union[str, int]]] = None) -> 'Ser
167171
Call load() before calling evaluate().""")
168172
context = context or {}
169173
config_values = {}
174+
175+
with self._lock:
176+
template_conditions = self._cache.conditions
177+
template_parameters = self._cache.parameters
178+
170179
# Initializes config Value objects with default values.
171180
if self._stringified_default_config is not None:
172181
for key, value in self._stringified_default_config.items():
173182
config_values[key] = _Value('default', value)
174-
self._evaluator = _ConditionEvaluator(self._cache.conditions,
175-
self._cache.parameters, context,
183+
self._evaluator = _ConditionEvaluator(template_conditions,
184+
template_parameters, context,
176185
config_values)
177186
return ServerConfig(config_values=self._evaluator.evaluate())
178187

@@ -183,14 +192,19 @@ def set(self, template_data_json: str):
183192
template_data_json: A json string representing ServerTemplateData to be cached.
184193
"""
185194
template_data_map = json.loads(template_data_json)
186-
self._cache = _ServerTemplateData(template_data_map)
195+
template_data = _ServerTemplateData(template_data_map)
196+
197+
with self._lock:
198+
self._cache = template_data
187199

188200
def to_json(self):
189201
"""Provides the server template in a JSON format to be used for initialization later."""
190202
if not self._cache:
191203
raise ValueError("""No Remote Config Server template in cache.
192204
Call load() before calling toJSON().""")
193-
return self._cache.template_data_json
205+
with self._lock:
206+
template_json = self._cache.template_data_json
207+
return template_json
194208

195209

196210
class ServerConfig:

0 commit comments

Comments
 (0)