Skip to content
12 changes: 12 additions & 0 deletions ldclient/impl/datasystem/fdv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,18 @@ def is_monitoring_enabled(self) -> bool:
return False

return monitoring_enabled()

def close(self):
"""
Close the wrapper and stop the repeating task poller if it's running.
Also forwards the close call to the underlying store if it has a close method.
"""
with self.__lock.write():
if self.__poller is not None:
self.__poller.stop()
self.__poller = None
if hasattr(self.store, "close"):
self.store.close()


class FDv2(DataSystem):
Expand Down
17 changes: 8 additions & 9 deletions ldclient/impl/datasystem/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
ChangeSet applications and flag change notifications.
"""

import threading
from collections import defaultdict
from typing import Any, Callable, Dict, List, Optional, Set

Expand Down Expand Up @@ -194,7 +193,7 @@ def __init__(
self._selector = Selector.no_selector()

# Thread synchronization
self._lock = threading.RLock()
self._lock = ReadWriteLock()

def with_persistence(
self,
Expand All @@ -213,7 +212,7 @@ def with_persistence(
Returns:
Self for method chaining
"""
with self._lock:
with self._lock.write():
self._persistent_store = persistent_store
self._persistent_store_writable = writable
self._persistent_store_status_provider = status_provider
Expand All @@ -225,12 +224,12 @@ def with_persistence(

def selector(self) -> Selector:
"""Returns the current selector."""
with self._lock:
with self._lock.read():
return self._selector

def close(self) -> Optional[Exception]:
"""Close the store and any persistent store if configured."""
with self._lock:
with self._lock.write():
if self._persistent_store is not None:
try:
# Most FeatureStore implementations don't have close methods
Expand All @@ -251,7 +250,7 @@ def apply(self, change_set: ChangeSet, persist: bool) -> None:
"""
collections = self._changes_to_store_data(change_set.changes)

with self._lock:
with self._lock.write():
try:
if change_set.intent_code == IntentCode.TRANSFER_FULL:
self._set_basis(collections, change_set.selector, persist)
Expand Down Expand Up @@ -443,7 +442,7 @@ def __mapping(data: Dict[str, ModelEntity]) -> Dict[str, Dict[str, Any]]:

return __mapping

with self._lock:
with self._lock.write():
if self._should_persist():
try:
# Get all data from memory store and write to persistent store
Expand All @@ -457,7 +456,7 @@ def __mapping(data: Dict[str, ModelEntity]) -> Dict[str, Dict[str, Any]]:

def get_active_store(self) -> ReadOnlyStore:
"""Get the currently active store for reading data."""
with self._lock:
with self._lock.read():
return self._active_store

def is_initialized(self) -> bool:
Expand All @@ -466,5 +465,5 @@ def is_initialized(self) -> bool:

def get_data_store_status_provider(self) -> Optional[DataStoreStatusProvider]:
"""Get the data store status provider for the persistent store, if configured."""
with self._lock:
with self._lock.read():
return self._persistent_store_status_provider
22 changes: 22 additions & 0 deletions ldclient/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,28 @@ def initialized(self) -> bool:
# :return: true if the underlying data store is reachable
# """

# WARN: This isn't a required method on a FeatureStore. The SDK will
# check if the provided store responds to this method, and if it does,
# will call it during shutdown to release any resources (such as database
# connections or connection pools) that the store may be using.
#
# @abstractmethod
# def close(self):
# """
# Releases any resources used by the data store implementation.
#
# This method will be called by the SDK during shutdown to ensure proper
# cleanup of resources such as database connections, connection pools,
# network sockets, or other resources that should be explicitly released.
#
# Implementations should be idempotent - calling close() multiple times
# should be safe and have no additional effect after the first call.
#
# This is particularly important for persistent data stores that maintain
# connection pools or other long-lived resources that should be properly
# cleaned up when the SDK is shut down.
# """


class FeatureStoreCore:
"""
Expand Down
Loading