Skip to content

Commit 4ca26c7

Browse files
authored
Merge pull request #103 from launchdarkly/eb/ch36211/close-ldd
ensure that client components are cleaned up correctly in every configuration
2 parents dd67a7c + ddfb3c2 commit 4ca26c7

File tree

4 files changed

+232
-154
lines changed

4 files changed

+232
-154
lines changed

ldclient/client.py

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
import traceback
99

1010
from ldclient.config import Config as Config
11-
from ldclient.event_processor import NullEventProcessor
1211
from ldclient.feature_requester import FeatureRequesterImpl
1312
from ldclient.feature_store import _FeatureStoreDataSetSorter
1413
from ldclient.flag import EvaluationDetail, evaluate, error_reason
1514
from ldclient.flags_state import FeatureFlagsState
15+
from ldclient.impl.stubs import NullEventProcessor, NullUpdateProcessor
1616
from ldclient.interfaces import FeatureStore
1717
from ldclient.polling import PollingUpdateProcessor
1818
from ldclient.streaming import StreamingUpdateProcessor
@@ -94,52 +94,54 @@ def __init__(self, sdk_key=None, config=None, start_wait=5):
9494
self._store = _FeatureStoreClientWrapper(self._config.feature_store)
9595
""" :type: FeatureStore """
9696

97-
if self._config.offline or not self._config.send_events:
98-
self._event_processor = NullEventProcessor()
99-
else:
100-
self._event_processor = self._config.event_processor_class(self._config)
101-
10297
if self._config.offline:
10398
log.info("Started LaunchDarkly Client in offline mode")
104-
return
10599

106100
if self._config.use_ldd:
107101
log.info("Started LaunchDarkly Client in LDD mode")
108-
return
109102

110-
update_processor_ready = threading.Event()
111-
112-
if self._config.update_processor_class:
113-
log.info("Using user-specified update processor: " + str(self._config.update_processor_class))
114-
self._update_processor = self._config.update_processor_class(
115-
self._config, self._store, update_processor_ready)
116-
else:
117-
if self._config.feature_requester_class:
118-
feature_requester = self._config.feature_requester_class(self._config)
119-
else:
120-
feature_requester = FeatureRequesterImpl(self._config)
121-
""" :type: FeatureRequester """
122-
123-
if self._config.stream:
124-
self._update_processor = StreamingUpdateProcessor(
125-
self._config, feature_requester, self._store, update_processor_ready)
126-
else:
127-
log.info("Disabling streaming API")
128-
log.warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support")
129-
self._update_processor = PollingUpdateProcessor(
130-
self._config, feature_requester, self._store, update_processor_ready)
131-
""" :type: UpdateProcessor """
103+
self._event_processor = self._make_event_processor(self._config)
132104

105+
update_processor_ready = threading.Event()
106+
self._update_processor = self._make_update_processor(self._config, self._store, update_processor_ready)
133107
self._update_processor.start()
134-
log.info("Waiting up to " + str(start_wait) + " seconds for LaunchDarkly client to initialize...")
135-
update_processor_ready.wait(start_wait)
108+
109+
if start_wait > 0 and not self._config.offline and not self._config.use_ldd:
110+
log.info("Waiting up to " + str(start_wait) + " seconds for LaunchDarkly client to initialize...")
111+
update_processor_ready.wait(start_wait)
136112

137113
if self._update_processor.initialized() is True:
138114
log.info("Started LaunchDarkly Client: OK")
139115
else:
140116
log.warn("Initialization timeout exceeded for LaunchDarkly Client or an error occurred. "
141117
"Feature Flags may not yet be available.")
142118

119+
def _make_event_processor(self, config):
120+
if config.offline or not config.send_events:
121+
return NullEventProcessor()
122+
return config.event_processor_class(config)
123+
124+
def _make_update_processor(self, config, store, ready):
125+
if config.update_processor_class:
126+
log.info("Using user-specified update processor: " + str(config.update_processor_class))
127+
return self._config.update_processor_class(config, store, ready)
128+
129+
if config.offline or config.use_ldd:
130+
return NullUpdateProcessor(config, store, ready)
131+
132+
if config.feature_requester_class:
133+
feature_requester = config.feature_requester_class(config)
134+
else:
135+
feature_requester = FeatureRequesterImpl(config)
136+
""" :type: FeatureRequester """
137+
138+
if config.stream:
139+
return StreamingUpdateProcessor(config, feature_requester, store, ready)
140+
141+
log.info("Disabling streaming API")
142+
log.warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support")
143+
return PollingUpdateProcessor(config, feature_requester, store, ready)
144+
143145
def get_sdk_key(self):
144146
"""Returns the configured SDK key.
145147
@@ -153,13 +155,16 @@ def close(self):
153155
Do not attempt to use the client after calling this method.
154156
"""
155157
log.info("Closing LaunchDarkly client..")
156-
if self.is_offline():
157-
return
158-
if self._event_processor:
159-
self._event_processor.stop()
160-
if self._update_processor and self._update_processor.is_alive():
161-
self._update_processor.stop()
158+
self._event_processor.stop()
159+
self._update_processor.stop()
162160

161+
# These magic methods allow a client object to be automatically cleaned up by the "with" scope operator
162+
def __enter__(self):
163+
return self
164+
165+
def __exit__(self, type, value, traceback):
166+
self.close()
167+
163168
def _send_event(self, event):
164169
self._event_processor.send_event(event)
165170

ldclient/event_processor.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,26 +37,6 @@
3737
__USER_ATTRS_TO_STRINGIFY_FOR_EVENTS__ = [ "key", "secondary", "ip", "country", "email", "firstName", "lastName", "avatar", "name" ]
3838

3939

40-
class NullEventProcessor(EventProcessor):
41-
def __init__(self):
42-
pass
43-
44-
def start(self):
45-
pass
46-
47-
def stop(self):
48-
pass
49-
50-
def is_alive(self):
51-
return False
52-
53-
def send_event(self, event):
54-
pass
55-
56-
def flush(self):
57-
pass
58-
59-
6040
EventProcessorMessage = namedtuple('EventProcessorMessage', ['type', 'param'])
6141

6242

ldclient/impl/stubs.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
from ldclient.interfaces import EventProcessor, UpdateProcessor
3+
4+
5+
class NullEventProcessor(EventProcessor):
6+
def __init__(self):
7+
pass
8+
9+
def start(self):
10+
pass
11+
12+
def stop(self):
13+
pass
14+
15+
def is_alive(self):
16+
return False
17+
18+
def send_event(self, event):
19+
pass
20+
21+
def flush(self):
22+
pass
23+
24+
25+
class NullUpdateProcessor(UpdateProcessor):
26+
def __init__(self, config, store, ready):
27+
self._ready = ready
28+
29+
def start(self):
30+
self._ready.set()
31+
32+
def stop(self):
33+
pass
34+
35+
def is_alive(self):
36+
return False
37+
38+
def initialized(self):
39+
return True

0 commit comments

Comments
 (0)