Skip to content

Commit 84261a4

Browse files
authored
FFM-6549 Add wait_for_initialization functionality (#55)
* FFM-6549 Add threading.Event as CfClient instance variable and pass to PollingProcessor * FFM-6549 Set thread when flags and groups are cached * FFM-6549 Move debug log to after client run has loaded and add an example * FFM-6549 Linter * FFM-6549 Edit comment * FFM-6549 1.1.7 release prep * FFM-6549 Debug log * FFM-6549 Move initialized complete log to poll processor * FFM-6549 Refactor initial polling * FFM-6549 Refactor initial polling
1 parent 289cf95 commit 84261a4

File tree

3 files changed

+76
-11
lines changed

3 files changed

+76
-11
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import time
2+
3+
from featureflags.config import with_base_url, with_events_url
4+
from featureflags.evaluations.auth_target import Target
5+
from featureflags.client import CfClient
6+
from featureflags.util import log
7+
8+
9+
def main():
10+
log.debug("Starting example")
11+
api_key = "Your API Key"
12+
client = CfClient(api_key)
13+
# Don't continue until all flags and groups have been loaded into the cache.
14+
log.debug("Waiting to load all flags and groups before proceeding")
15+
client.wait_for_initialization()
16+
17+
# If required you can check the initialized status of the Client at anytime
18+
log.debug("Client is_initialized status is %s", client.is_initialized())
19+
target = Target(identifier='harness')
20+
21+
while True:
22+
result = client.bool_variation('identifier_of_your_bool_flag', target, False)
23+
log.debug("Result %s", result)
24+
time.sleep(10)
25+
26+
27+
if __name__ == "__main__":
28+
main()

featureflags/client.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ def __init__(
2828
config: Optional[Config] = None
2929
):
3030
self._client: Optional[Client] = None
31+
# The Client is considered initialized when flags and groups
32+
# are loaded into cache.
33+
self._initialized = threading.Event()
3134
self._auth_token: Optional[str] = None
3235
self._environment_id: Optional[str] = None
3336
self._sdk_key: Optional[str] = sdk_key
@@ -47,9 +50,9 @@ def __init__(
4750
self._repository = Repository(self._config.cache)
4851
self._evaluator = Evaluator(self._repository)
4952

50-
log.debug("CfClient initialized")
5153
self.run()
5254

55+
5356
def run(self):
5457
self.authenticate()
5558

@@ -60,6 +63,10 @@ def run(self):
6063
client=self._client,
6164
config=self._config,
6265
environment_id=self._environment_id,
66+
# PollingProcessor is responsible for doing the initial
67+
# flag/group fetch and cache. So we allocate it the responsibility
68+
# for setting the Client is_initialized variable.
69+
wait_for_initialization=self._initialized,
6370
ready=polling_event,
6471
stream_ready=streaming_event,
6572
repository=self._repository
@@ -87,6 +94,13 @@ def run(self):
8794
environment=self._environment_id
8895
)
8996

97+
def wait_for_initialization(self):
98+
log.debug("Waiting for initialization to finish")
99+
self._initialized.wait()
100+
101+
def is_initialized(self):
102+
return self._initialized.is_set()
103+
90104
def get_environment_id(self):
91105
return self._environment_id
92106

featureflags/polling.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@
1313
class PollingProcessor(Thread):
1414

1515
def __init__(self, client: AuthenticatedClient, config: Config,
16-
environment_id: str, ready: Event,
17-
stream_ready: Event,
16+
environment_id: str, wait_for_initialization: Event,
17+
ready: Event, stream_ready: Event,
1818
repository: DataProviderInterface) -> None:
1919
Thread.__init__(self)
2020
self.daemon = True
2121
self.__environment_id = environment_id
2222
self.__client = client
2323
self.__config = config
2424
self.__running = False
25+
self.__wait_for_initialization = wait_for_initialization
2526
self.__ready = ready
2627
self.__stream_ready = stream_ready
2728
self.__repository = repository
@@ -34,19 +35,32 @@ def run(self):
3435
str(self.__config.pull_interval) +
3536
" setting to 60")
3637
self.__config.pull_interval = 60
38+
39+
self.__running = True
40+
# Get initial flags and groups
41+
try:
42+
log.info("Fetching initial target segments and flags")
43+
self.retrieve_flags_and_segments()
44+
log.info("Initial target segments and flags fetched. "
45+
"PollingProcessor will start in: " +
46+
str(self.__config.pull_interval) + " seconds")
47+
# Segments and flags have been cached so
48+
# mark the Client as initialised.
49+
self.__wait_for_initialization.set()
50+
log.debug("CfClient initialized")
51+
except Exception as ex:
52+
log.exception(
53+
'Error: Exception encountered when '
54+
'getting initial flags and segments. %s',
55+
ex
56+
)
57+
# Sleep for an interval before going into the polling loop.
58+
time.sleep(self.__config.pull_interval)
3759
log.info("Starting PollingProcessor with request interval: " +
3860
str(self.__config.pull_interval))
39-
self.__running = True
4061
while self.__running:
4162
start_time = time.time()
4263
try:
43-
t1 = Thread(target=self.__retrieve_segments)
44-
t2 = Thread(target=self.__retrieve_flags)
45-
t1.start()
46-
t2.start()
47-
t1.join()
48-
t2.join()
49-
5064
if self.__config.enable_stream and \
5165
self.__stream_ready.is_set():
5266
log.debug('Poller will be paused because' +
@@ -55,6 +69,7 @@ def run(self):
5569
self.__ready.wait()
5670
log.debug('Poller resuming ')
5771
else:
72+
self.retrieve_flags_and_segments()
5873
self.__ready.set()
5974
except Exception as e:
6075
log.exception(
@@ -73,6 +88,14 @@ def stop(self):
7388
log.info("Stopping PollingProcessor")
7489
self.__running = False
7590

91+
def retrieve_flags_and_segments(self):
92+
t1 = Thread(target=self.__retrieve_segments)
93+
t2 = Thread(target=self.__retrieve_flags)
94+
t1.start()
95+
t2.start()
96+
t1.join()
97+
t2.join()
98+
7699
def __retrieve_flags(self):
77100
log.debug("Loading feature flags")
78101
flags = retrieve_flags(

0 commit comments

Comments
 (0)