Skip to content

Commit

Permalink
Merge pull request #103 from launchdarkly/eb/ch36211/close-ldd
Browse files Browse the repository at this point in the history
ensure that client components are cleaned up correctly in every configuration
  • Loading branch information
eli-darkly authored Apr 9, 2019
2 parents dd67a7c + ddfb3c2 commit 4ca26c7
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 154 deletions.
81 changes: 43 additions & 38 deletions ldclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import traceback

from ldclient.config import Config as Config
from ldclient.event_processor import NullEventProcessor
from ldclient.feature_requester import FeatureRequesterImpl
from ldclient.feature_store import _FeatureStoreDataSetSorter
from ldclient.flag import EvaluationDetail, evaluate, error_reason
from ldclient.flags_state import FeatureFlagsState
from ldclient.impl.stubs import NullEventProcessor, NullUpdateProcessor
from ldclient.interfaces import FeatureStore
from ldclient.polling import PollingUpdateProcessor
from ldclient.streaming import StreamingUpdateProcessor
Expand Down Expand Up @@ -94,52 +94,54 @@ def __init__(self, sdk_key=None, config=None, start_wait=5):
self._store = _FeatureStoreClientWrapper(self._config.feature_store)
""" :type: FeatureStore """

if self._config.offline or not self._config.send_events:
self._event_processor = NullEventProcessor()
else:
self._event_processor = self._config.event_processor_class(self._config)

if self._config.offline:
log.info("Started LaunchDarkly Client in offline mode")
return

if self._config.use_ldd:
log.info("Started LaunchDarkly Client in LDD mode")
return

update_processor_ready = threading.Event()

if self._config.update_processor_class:
log.info("Using user-specified update processor: " + str(self._config.update_processor_class))
self._update_processor = self._config.update_processor_class(
self._config, self._store, update_processor_ready)
else:
if self._config.feature_requester_class:
feature_requester = self._config.feature_requester_class(self._config)
else:
feature_requester = FeatureRequesterImpl(self._config)
""" :type: FeatureRequester """

if self._config.stream:
self._update_processor = StreamingUpdateProcessor(
self._config, feature_requester, self._store, update_processor_ready)
else:
log.info("Disabling streaming API")
log.warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support")
self._update_processor = PollingUpdateProcessor(
self._config, feature_requester, self._store, update_processor_ready)
""" :type: UpdateProcessor """
self._event_processor = self._make_event_processor(self._config)

update_processor_ready = threading.Event()
self._update_processor = self._make_update_processor(self._config, self._store, update_processor_ready)
self._update_processor.start()
log.info("Waiting up to " + str(start_wait) + " seconds for LaunchDarkly client to initialize...")
update_processor_ready.wait(start_wait)

if start_wait > 0 and not self._config.offline and not self._config.use_ldd:
log.info("Waiting up to " + str(start_wait) + " seconds for LaunchDarkly client to initialize...")
update_processor_ready.wait(start_wait)

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

def _make_event_processor(self, config):
if config.offline or not config.send_events:
return NullEventProcessor()
return config.event_processor_class(config)

def _make_update_processor(self, config, store, ready):
if config.update_processor_class:
log.info("Using user-specified update processor: " + str(config.update_processor_class))
return self._config.update_processor_class(config, store, ready)

if config.offline or config.use_ldd:
return NullUpdateProcessor(config, store, ready)

if config.feature_requester_class:
feature_requester = config.feature_requester_class(config)
else:
feature_requester = FeatureRequesterImpl(config)
""" :type: FeatureRequester """

if config.stream:
return StreamingUpdateProcessor(config, feature_requester, store, ready)

log.info("Disabling streaming API")
log.warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support")
return PollingUpdateProcessor(config, feature_requester, store, ready)

def get_sdk_key(self):
"""Returns the configured SDK key.
Expand All @@ -153,13 +155,16 @@ def close(self):
Do not attempt to use the client after calling this method.
"""
log.info("Closing LaunchDarkly client..")
if self.is_offline():
return
if self._event_processor:
self._event_processor.stop()
if self._update_processor and self._update_processor.is_alive():
self._update_processor.stop()
self._event_processor.stop()
self._update_processor.stop()

# These magic methods allow a client object to be automatically cleaned up by the "with" scope operator
def __enter__(self):
return self

def __exit__(self, type, value, traceback):
self.close()

def _send_event(self, event):
self._event_processor.send_event(event)

Expand Down
20 changes: 0 additions & 20 deletions ldclient/event_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,6 @@
__USER_ATTRS_TO_STRINGIFY_FOR_EVENTS__ = [ "key", "secondary", "ip", "country", "email", "firstName", "lastName", "avatar", "name" ]


class NullEventProcessor(EventProcessor):
def __init__(self):
pass

def start(self):
pass

def stop(self):
pass

def is_alive(self):
return False

def send_event(self, event):
pass

def flush(self):
pass


EventProcessorMessage = namedtuple('EventProcessorMessage', ['type', 'param'])


Expand Down
39 changes: 39 additions & 0 deletions ldclient/impl/stubs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

from ldclient.interfaces import EventProcessor, UpdateProcessor


class NullEventProcessor(EventProcessor):
def __init__(self):
pass

def start(self):
pass

def stop(self):
pass

def is_alive(self):
return False

def send_event(self, event):
pass

def flush(self):
pass


class NullUpdateProcessor(UpdateProcessor):
def __init__(self, config, store, ready):
self._ready = ready

def start(self):
self._ready.set()

def stop(self):
pass

def is_alive(self):
return False

def initialized(self):
return True
Loading

0 comments on commit 4ca26c7

Please sign in to comment.