Skip to content

Commit 44573be

Browse files
authored
Merge pull request #62 from launchdarkly/dr/initImprovements
Add backoff to stream reconnect
2 parents 4ed7a03 + 7bd10a2 commit 44573be

File tree

4 files changed

+33
-26
lines changed

4 files changed

+33
-26
lines changed

ldclient/client.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ class Config(object):
3535
def __init__(self,
3636
base_uri='https://app.launchdarkly.com',
3737
events_uri='https://events.launchdarkly.com',
38-
connect_timeout=2,
39-
read_timeout=10,
38+
connect_timeout=10,
39+
read_timeout=15,
4040
events_upload_max_batch_size=100,
4141
events_max_pending=10000,
4242
stream_uri='https://stream.launchdarkly.com',
@@ -149,10 +149,11 @@ def __init__(self, sdk_key, config=None, start_wait=5):
149149
log.info("Waiting up to " + str(start_wait) + " seconds for LaunchDarkly client to initialize...")
150150
update_processor_ready.wait(start_wait)
151151

152-
if self._update_processor.initialized:
152+
if self._update_processor.initialized() is True:
153153
log.info("Started LaunchDarkly Client: OK")
154154
else:
155-
log.info("Initialization timeout exceeded for LaunchDarkly Client. Feature Flags may not yet be available.")
155+
log.warn("Initialization timeout exceeded for LaunchDarkly Client or an error occurred. "
156+
"Feature Flags may not yet be available.")
156157

157158
@property
158159
def sdk_key(self):
@@ -215,7 +216,7 @@ def send_event(value, version=None):
215216
'user': user, 'value': value, 'default': default, 'version': version})
216217

217218
if not self.is_initialized():
218-
log.warn("Feature Flag evaluation attempted before client has finished initializing! Returning default: "
219+
log.warn("Feature Flag evaluation attempted before client has initialized! Returning default: "
219220
+ str(default) + " for feature key: " + key)
220221
send_event(default)
221222
return default

ldclient/polling.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ def run(self):
2323
while self._running:
2424
start_time = time.time()
2525
self._store.init(self._requester.get_all())
26-
if not self._ready.is_set() and self._store.initialized:
26+
if not self._ready.is_set() is True and self._store.initialized is True:
2727
log.info("PollingUpdateProcessor initialized ok")
2828
self._ready.set()
2929
elapsed = time.time() - start_time
3030
if elapsed < self._config.poll_interval:
3131
time.sleep(self._config.poll_interval - elapsed)
3232

3333
def initialized(self):
34-
return self._running and self._ready.is_set() and self._store.initialized
34+
return self._running and self._ready.is_set() is True and self._store.initialized is True
3535

3636
def stop(self):
3737
log.info("Stopping PollingUpdateProcessor")

ldclient/streaming.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from __future__ import absolute_import
2+
13
import json
24
from threading import Thread
35

4-
import time
6+
import backoff
7+
import requests
58
from sseclient import SSEClient
6-
79
from ldclient.interfaces import UpdateProcessor
810
from ldclient.util import _stream_headers, log
911

@@ -13,44 +15,47 @@ def __init__(self, sdk_key, config, requester, store, ready):
1315
Thread.__init__(self)
1416
self.daemon = True
1517
self._sdk_key = sdk_key
18+
self._uri = config.stream_uri
1619
self._config = config
1720
self._requester = requester
1821
self._store = store
1922
self._running = False
2023
self._ready = ready
24+
self._headers = _stream_headers(self._sdk_key)
2125

2226
def run(self):
23-
log.info("Starting StreamingUpdateProcessor connecting to uri: " + self._config.stream_uri)
27+
log.info("Starting StreamingUpdateProcessor connecting to uri: " + self._uri)
2428
self._running = True
25-
hdrs = _stream_headers(self._sdk_key)
26-
uri = self._config.stream_uri
2729
while self._running:
28-
try:
29-
messages = SSEClient(uri, verify=self._config.verify_ssl, headers=hdrs)
30-
for msg in messages:
31-
if not self._running:
32-
break
33-
if self.process_message(self._store, self._requester, msg, self._ready) is True:
34-
self._ready.set()
35-
except Exception as e:
36-
log.error("Could not connect to LaunchDarkly stream: " + str(e.message) +
37-
" waiting 1 second before trying again.")
38-
time.sleep(1)
30+
self._connect()
31+
32+
def _backoff_expo():
33+
return backoff.expo(max_value=30)
34+
35+
@backoff.on_exception(_backoff_expo, requests.exceptions.RequestException, max_tries=None, jitter=backoff.full_jitter)
36+
def _connect(self):
37+
messages = SSEClient(self._uri, verify=self._config.verify_ssl, headers=self._headers)
38+
for msg in messages:
39+
if not self._running:
40+
break
41+
message_ok = self.process_message(self._store, self._requester, msg, self._ready)
42+
if message_ok is True and self._ready.is_set() is False:
43+
self._ready.set()
3944

4045
def stop(self):
4146
log.info("Stopping StreamingUpdateProcessor")
4247
self._running = False
4348

4449
def initialized(self):
45-
return self._running and self._ready.is_set() and self._store.initialized
50+
return self._running and self._ready.is_set() is True and self._store.initialized is True
4651

4752
@staticmethod
4853
def process_message(store, requester, msg, ready):
4954
log.debug("Received stream event {} with data: {}".format(msg.event, msg.data))
5055
if msg.event == 'put':
5156
payload = json.loads(msg.data)
5257
store.init(payload)
53-
if not ready.is_set() and store.initialized:
58+
if not ready.is_set() is True and store.initialized is True:
5459
log.info("StreamingUpdateProcessor initialized ok")
5560
return True
5661
elif msg.event == 'patch':
@@ -63,7 +68,7 @@ def process_message(store, requester, msg, ready):
6368
store.upsert(key, requester.get_one(key))
6469
elif msg.event == "indirect/put":
6570
store.init(requester.get_all())
66-
if not ready.is_set() and store.initialized:
71+
if not ready.is_set() is True and store.initialized is True:
6772
log.info("StreamingUpdateProcessor initialized ok")
6873
return True
6974
elif msg.event == 'delete':

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
backoff>=1.3.1
12
CacheControl>=0.10.2
23
requests>=2.10.0
34
sseclient>=0.0.12

0 commit comments

Comments
 (0)