Skip to content

Commit 9a76c33

Browse files
eplusminusashanbrown
authored andcommitted
can use feature store even if update processor not inited
If redis is initialized at start time (even if fetching flags from the service failed), allow the clients to use the values from redis.
1 parent 1b985a5 commit 9a76c33

3 files changed

Lines changed: 44 additions & 17 deletions

File tree

ldclient/client.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,13 @@ def send_event(value, version=None):
154154
'user': user, 'value': value, 'default': default, 'version': version})
155155

156156
if not self.is_initialized():
157-
log.warn("Feature Flag evaluation attempted before client has initialized! Returning default: "
158-
+ str(default) + " for feature key: " + key)
159-
send_event(default)
160-
return default
157+
if self._store.initialized:
158+
log.warn("Feature Flag evaluation attempted before client has initialized - using last known values from feature store for feature key: " + key)
159+
else:
160+
log.warn("Feature Flag evaluation attempted before client has initialized! Feature store unavailable - returning default: "
161+
+ str(default) + " for feature key: " + key)
162+
send_event(default)
163+
return default
161164

162165
if user is None or user.get('key') is None:
163166
log.warn("Missing user or user key when evaluating Feature Flag key: " + key + ". Returning default.")
@@ -203,8 +206,11 @@ def all_flags(self, user):
203206
return None
204207

205208
if not self.is_initialized():
206-
log.warn("all_flags() called before client has finished initializing! Returning None")
207-
return None
209+
if self._store.initialized:
210+
log.warn("all_flags() called before client has finished initializing! Using last known values from feature store")
211+
else:
212+
log.warn("all_flags() called before client has finished initializing! Feature store unavailable - returning None")
213+
return None
208214

209215
if user is None or user.get('key') is None:
210216
log.warn("User or user key is None when calling all_flags(). Returning None.")

ldclient/memoized_value.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'''
2+
Simple implementation of a thread-safe memoized value whose generator function will never be
3+
run more than once, and whose value can be overridden by explicit assignment.
4+
'''
5+
6+
from threading import RLock
7+
8+
class MemoizedValue(object):
9+
10+
def __init__(self, generator):
11+
self.generator = generator
12+
self.inited = False
13+
self.value = None
14+
self.lock = RLock()
15+
16+
def get(self):
17+
with self.lock:
18+
if not self.inited:
19+
self.value = self.generator()
20+
self.inited = True
21+
return self.value
22+
23+
def set(self, value):
24+
with self.lock:
25+
self.value = value
26+
self.inited = True

ldclient/redis_feature_store.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
from ldclient import log
77
from ldclient.expiringdict import ExpiringDict
88
from ldclient.interfaces import FeatureStore
9-
10-
INIT_KEY = "$initialized$"
9+
from ldclient.memoized_value import MemoizedValue
1110

1211

1312
class ForgetfulDict(dict):
@@ -27,6 +26,7 @@ def __init__(self,
2726
self._cache = ForgetfulDict() if expiration == 0 else ExpiringDict(max_len=capacity,
2827
max_age_seconds=expiration)
2928
self._pool = redis.ConnectionPool.from_url(url=url, max_connections=max_connections)
29+
self._inited = MemoizedValue(lambda: self._query_init())
3030
log.info("Started RedisFeatureStore connected to URL: " + url + " using prefix: " + prefix)
3131

3232
def init(self, features):
@@ -41,6 +41,7 @@ def init(self, features):
4141
self._cache[k] = f
4242
pipe.execute()
4343
log.info("Initialized RedisFeatureStore with " + str(len(features)) + " feature flags")
44+
self._inited.set(True)
4445

4546
def all(self, callback):
4647
r = redis.Redis(connection_pool=self._pool)
@@ -109,17 +110,11 @@ def delete(self, key, version):
109110

110111
@property
111112
def initialized(self):
112-
initialized = self._cache.get(INIT_KEY)
113-
if initialized:
114-
# reset ttl
115-
self._cache[INIT_KEY] = True
116-
return True
113+
return self._inited.get()
117114

115+
def _query_init(self):
118116
r = redis.Redis(connection_pool=self._pool)
119-
if r.exists(self._features_key):
120-
self._cache[INIT_KEY] = True
121-
return True
122-
return False
117+
return r.exists(self._features_key)
123118

124119
def upsert(self, key, feature):
125120
r = redis.Redis(connection_pool=self._pool)

0 commit comments

Comments
 (0)