Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 14 additions & 23 deletions ldclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,16 @@ def set_config(config: Config):
global __config
global __client
global __lock
try:
__lock.lock()
if __client:
log.info("Reinitializing LaunchDarkly Client " + VERSION + " with new config")
new_client = LDClient(config=config, start_wait=start_wait)
old_client = __client
__client = new_client
old_client.close()
finally:
__config = config
__lock.unlock()
with __lock.write():
try:
if __client:
log.info("Reinitializing LaunchDarkly Client " + VERSION + " with new config")
new_client = LDClient(config=config, start_wait=start_wait)
old_client = __client
__client = new_client
old_client.close()
finally:
__config = config


def get() -> LDClient:
Expand All @@ -63,35 +62,27 @@ def get() -> LDClient:
global __config
global __client
global __lock
try:
__lock.rlock()
with __lock.read():
if __client:
return __client
if __config is None:
raise Exception("set_config was not called")
finally:
__lock.runlock()

try:
__lock.lock()
with __lock.write():
if not __client:
log.info("Initializing LaunchDarkly Client " + VERSION)
__client = LDClient(config=__config, start_wait=start_wait)
return __client
finally:
__lock.unlock()


# for testing only
def _reset_client():
global __client
global __lock
try:
__lock.lock()
c = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A thing of beauty.

with __lock.write():
c = __client
__client = None
finally:
__lock.unlock()
if c:
c.close()

Expand Down
28 changes: 8 additions & 20 deletions ldclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,10 @@ def __wrapper(self, fn: Callable):
raise

def __update_availability(self, available: bool):
try:
self.__lock.lock()
with self.__lock.write():
if available == self.__last_available:
return
self.__last_available = available
finally:
self.__lock.unlock()

status = DataStoreStatus(available, False)

Expand All @@ -127,23 +124,19 @@ def __update_availability(self, available: bool):
self.__store_update_sink.update_status(status)

if available:
try:
self.__lock.lock()
with self.__lock.write():
if self.__poller is not None:
self.__poller.stop()
self.__poller = None
finally:
self.__lock.unlock()

return

log.warn("Detected persistent store unavailability; updates will be cached until it recovers")
task = RepeatingTask("ldclient.check-availability", 0.5, 0, self.__check_availability)

self.__lock.lock()
self.__poller = task
self.__poller.start()
self.__lock.unlock()
with self.__lock.write():
self.__poller = task
self.__poller.start()

def __check_availability(self):
try:
Expand Down Expand Up @@ -717,9 +710,8 @@ def add_hook(self, hook: Hook):
if not isinstance(hook, Hook):
return

self.__hooks_lock.lock()
self.__hooks.append(hook)
self.__hooks_lock.unlock()
with self.__hooks_lock.write():
self.__hooks.append(hook)

def __evaluate_with_hooks(self, key: str, context: Context, default_value: Any, method: str, block: Callable[[], _EvaluationWithHookResult]) -> _EvaluationWithHookResult:
"""
Expand All @@ -733,15 +725,11 @@ def __evaluate_with_hooks(self, key: str, context: Context, default_value: Any,
# :return:
"""
hooks = [] # type: List[Hook]
try:
self.__hooks_lock.rlock()

with self.__hooks_lock.read():
if len(self.__hooks) == 0:
return block()

hooks = self.__hooks.copy()
finally:
self.__hooks_lock.runlock()

series_context = EvaluationSeriesContext(key=key, context=context, default_value=default_value, method=method)
hook_data = self.__execute_before_evaluation(hooks, series_context)
Expand Down
30 changes: 6 additions & 24 deletions ldclient/feature_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ def is_available(self) -> bool:

def get(self, kind: VersionedDataKind, key: str, callback: Callable[[Any], Any] = lambda x: x) -> Any:
""" """
try:
self._lock.rlock()
with self._lock.read():
itemsOfKind = self._items[kind]
item = itemsOfKind.get(key)
if item is None:
Expand All @@ -88,17 +87,12 @@ def get(self, kind: VersionedDataKind, key: str, callback: Callable[[Any], Any]
log.debug("Attempted to get deleted key %s in '%s', returning None", key, kind.namespace)
return callback(None)
return callback(item)
finally:
self._lock.runlock()

def all(self, kind, callback):
""" """
try:
self._lock.rlock()
with self._lock.read():
itemsOfKind = self._items[kind]
return callback(dict((k, i) for k, i in itemsOfKind.items() if ('deleted' not in i) or not i['deleted']))
finally:
self._lock.runlock()

def init(self, all_data):
""" """
Expand All @@ -108,51 +102,39 @@ def init(self, all_data):
for key, item in items.items():
items_decoded[key] = kind.decode(item)
all_decoded[kind] = items_decoded
try:
self._lock.rlock()
with self._lock.write():
self._items.clear()
self._items.update(all_decoded)
self._initialized = True
for k in all_data:
log.debug("Initialized '%s' store with %d items", k.namespace, len(all_data[k]))
finally:
self._lock.runlock()

# noinspection PyShadowingNames
def delete(self, kind, key: str, version: int):
""" """
try:
self._lock.rlock()
with self._lock.write():
itemsOfKind = self._items[kind]
i = itemsOfKind.get(key)
if i is None or i['version'] < version:
i = {'deleted': True, 'version': version}
itemsOfKind[key] = i
finally:
self._lock.runlock()

def upsert(self, kind, item):
""" """
decoded_item = kind.decode(item)
key = item['key']
try:
self._lock.rlock()
with self._lock.write():
itemsOfKind = self._items[kind]
i = itemsOfKind.get(key)
if i is None or i['version'] < item['version']:
itemsOfKind[key] = decoded_item
log.debug("Updated %s in '%s' to version %d", key, kind.namespace, item['version'])
finally:
self._lock.runlock()

@property
def initialized(self) -> bool:
""" """
try:
self._lock.rlock()
with self._lock.read():
return self._initialized
finally:
self._lock.runlock()

def describe_configuration(self, config):
return 'memory'
Expand Down
10 changes: 2 additions & 8 deletions ldclient/impl/datasource/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,8 @@ def __init__(self, store: FeatureStore, status_listeners: Listeners, flag_change

@property
def status(self) -> DataSourceStatus:
try:
self.__lock.rlock()
with self.__lock.read():
return self.__status
finally:
self.__lock.runlock()

def init(self, all_data: Mapping[VersionedDataKind, Mapping[str, dict]]):
old_data = None
Expand Down Expand Up @@ -70,8 +67,7 @@ def delete(self, kind: VersionedDataKind, key: str, version: int):
def update_status(self, new_state: DataSourceState, new_error: Optional[DataSourceErrorInfo]):
status_to_broadcast = None

try:
self.__lock.lock()
with self.__lock.write():
old_status = self.__status

if new_state == DataSourceState.INTERRUPTED and old_status.state == DataSourceState.INITIALIZING:
Expand All @@ -83,8 +79,6 @@ def update_status(self, new_state: DataSourceState, new_error: Optional[DataSour
self.__status = DataSourceStatus(new_state, self.__status.since if new_state == self.__status.state else time.time(), self.__status.error if new_error is None else new_error)

status_to_broadcast = self.__status
finally:
self.__lock.unlock()

if status_to_broadcast is not None:
self.__status_listeners.notify(status_to_broadcast)
Expand Down
12 changes: 4 additions & 8 deletions ldclient/impl/datastore/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,12 @@ def listeners(self) -> Listeners:
return self.__listeners

def status(self) -> DataStoreStatus:
self.__lock.rlock()
status = copy(self.__status)
self.__lock.runlock()

return status
with self.__lock.read():
return copy(self.__status)

def update_status(self, status: DataStoreStatus):
self.__lock.lock()
old_value, self.__status = self.__status, status
self.__lock.unlock()
with self.__lock.write():
old_value, self.__status = self.__status, status

if old_value != status:
self.__listeners.notify(status)
Expand Down
Loading