Skip to content

Commit 468afdf

Browse files
authored
fix: Stop FeatureStoreClientWrapper poller on close (#397)
1 parent beca0fa commit 468afdf

File tree

2 files changed

+45
-0
lines changed

2 files changed

+45
-0
lines changed

ldclient/impl/datasystem/fdv2.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ def __init__(self, store: FeatureStore, store_update_sink: DataStoreStatusProvid
130130
self.__lock = ReadWriteLock()
131131
self.__last_available = True
132132
self.__poller: Optional[RepeatingTask] = None
133+
self.__closed = False
133134

134135
def init(self, all_data: Mapping[VersionedDataKind, Mapping[str, Dict[Any, Any]]]):
135136
return self.__wrapper(lambda: self.store.init(_FeatureStoreDataSetSorter.sort_all_collections(all_data)))
@@ -164,6 +165,8 @@ def __update_availability(self, available: bool):
164165
task_to_start = None
165166

166167
with self.__lock.write():
168+
if self.__closed:
169+
return
167170
if available == self.__last_available:
168171
return
169172

@@ -229,6 +232,26 @@ def is_monitoring_enabled(self) -> bool:
229232

230233
return monitoring_enabled()
231234

235+
def close(self):
236+
"""
237+
Close the wrapper and stop the repeating task poller if it's running.
238+
Also forwards the close call to the underlying store if it has a close method.
239+
"""
240+
poller_to_stop = None
241+
242+
with self.__lock.write():
243+
if self.__closed:
244+
return
245+
self.__closed = True
246+
poller_to_stop = self.__poller
247+
self.__poller = None
248+
249+
if poller_to_stop is not None:
250+
poller_to_stop.stop()
251+
252+
if hasattr(self.store, "close"):
253+
self.store.close()
254+
232255

233256
class FDv2(DataSystem):
234257
"""

ldclient/interfaces.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,28 @@ def initialized(self) -> bool:
189189
# :return: true if the underlying data store is reachable
190190
# """
191191

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

193215
class FeatureStoreCore:
194216
"""

0 commit comments

Comments
 (0)