|
2 | 2 | Support classes shared by the sync and async FDv2 data system coordinators. |
3 | 3 |
|
4 | 4 | These are synchronous (thread-based) components used identically by both |
5 | | -``FDv2`` and ``AsyncFDv2``: status providers, the persistent-store wrapper, |
6 | | -and the condition directive enum. |
| 5 | +``FDv2`` and ``AsyncFDv2``: status providers, and the condition directive |
| 6 | +enum. |
7 | 7 | """ |
8 | 8 |
|
9 | 9 | import time |
10 | 10 | from copy import copy |
11 | 11 | from enum import Enum |
12 | | -from typing import Any, Callable, Dict, Mapping, Optional |
| 12 | +from typing import Callable, Optional |
13 | 13 |
|
14 | | -from ldclient.feature_store import _FeatureStoreDataSetSorter |
15 | 14 | from ldclient.impl.datasystem import DataAvailability, DiagnosticAccumulator |
16 | 15 | from ldclient.impl.datasystem.store import _StoreBase |
17 | 16 | from ldclient.impl.listeners import Listeners |
18 | | -from ldclient.impl.repeating_task import RepeatingTask |
19 | 17 | from ldclient.impl.rwlock import ReadWriteLock |
20 | | -from ldclient.impl.util import log |
21 | 18 | from ldclient.interfaces import ( |
22 | 19 | DataSourceErrorInfo, |
23 | 20 | DataSourceState, |
|
27 | 24 | DataStoreStatusProvider, |
28 | 25 | FeatureStore |
29 | 26 | ) |
30 | | -from ldclient.versioned_data_kind import VersionedDataKind |
31 | 27 |
|
32 | 28 |
|
33 | 29 | class DataSourceStatusProviderImpl(DataSourceStatusProvider): |
@@ -112,154 +108,6 @@ def remove_listener(self, listener: Callable[[DataStoreStatus], None]): |
112 | 108 | self.__listeners.remove(listener) |
113 | 109 |
|
114 | 110 |
|
115 | | -class FeatureStoreClientWrapper(FeatureStore): |
116 | | - """Provides additional behavior that the client requires before or after feature store operations. |
117 | | - Currently this just means sorting the data set for init() and dealing with data store status listeners. |
118 | | - """ |
119 | | - |
120 | | - def __init__(self, store: FeatureStore, store_update_sink: DataStoreStatusProviderImpl): |
121 | | - self.store = store |
122 | | - self.__store_update_sink = store_update_sink |
123 | | - self.__monitoring_enabled = self.is_monitoring_enabled() |
124 | | - |
125 | | - # Covers the following variables |
126 | | - self.__lock = ReadWriteLock() |
127 | | - self.__last_available = True |
128 | | - self.__poller: Optional[RepeatingTask] = None |
129 | | - self.__closed = False |
130 | | - |
131 | | - def init(self, all_data: Mapping[VersionedDataKind, Mapping[str, Dict[Any, Any]]]): |
132 | | - return self.__wrapper(lambda: self.store.init(_FeatureStoreDataSetSorter.sort_all_collections(all_data))) |
133 | | - |
134 | | - def get(self, kind, key, callback): |
135 | | - return self.__wrapper(lambda: self.store.get(kind, key, callback)) |
136 | | - |
137 | | - def all(self, kind, callback): |
138 | | - return self.__wrapper(lambda: self.store.all(kind, callback)) |
139 | | - |
140 | | - def delete(self, kind, key, version): |
141 | | - return self.__wrapper(lambda: self.store.delete(kind, key, version)) |
142 | | - |
143 | | - def upsert(self, kind, item): |
144 | | - return self.__wrapper(lambda: self.store.upsert(kind, item)) |
145 | | - |
146 | | - @property |
147 | | - def initialized(self) -> bool: |
148 | | - return self.store.initialized |
149 | | - |
150 | | - def disable_cache(self) -> None: |
151 | | - def _do_disable(): |
152 | | - try: |
153 | | - inner = self.store |
154 | | - if hasattr(inner, "disable_cache"): |
155 | | - inner.disable_cache() # type: ignore[attr-defined] |
156 | | - except Exception as e: |
157 | | - log.warning("disable_cache failed on inner store: %s", e) |
158 | | - |
159 | | - self.__wrapper(_do_disable) |
160 | | - |
161 | | - def __wrapper(self, fn: Callable): |
162 | | - try: |
163 | | - return fn() |
164 | | - except BaseException: |
165 | | - if self.__monitoring_enabled: |
166 | | - self.__update_availability(False) |
167 | | - raise |
168 | | - |
169 | | - def __update_availability(self, available: bool): |
170 | | - state_changed = False |
171 | | - poller_to_stop = None |
172 | | - task_to_start = None |
173 | | - |
174 | | - with self.__lock.write(): |
175 | | - if self.__closed: |
176 | | - return |
177 | | - if available == self.__last_available: |
178 | | - return |
179 | | - |
180 | | - state_changed = True |
181 | | - self.__last_available = available |
182 | | - |
183 | | - if available: |
184 | | - poller_to_stop = self.__poller |
185 | | - self.__poller = None |
186 | | - elif self.__poller is None: |
187 | | - task_to_start = RepeatingTask("ldclient.check-availability", 0.5, 0, self.__check_availability) |
188 | | - self.__poller = task_to_start |
189 | | - |
190 | | - if available: |
191 | | - log.warning("Persistent store is available again") |
192 | | - else: |
193 | | - log.warning("Detected persistent store unavailability; updates will be cached until it recovers") |
194 | | - |
195 | | - status = DataStoreStatus(available, True) |
196 | | - self.__store_update_sink.update_status(status) |
197 | | - |
198 | | - if poller_to_stop is not None: |
199 | | - poller_to_stop.stop() |
200 | | - |
201 | | - if task_to_start is not None: |
202 | | - task_to_start.start() |
203 | | - |
204 | | - def __check_availability(self): |
205 | | - try: |
206 | | - if self.store.is_available(): |
207 | | - self.__update_availability(True) |
208 | | - except BaseException as e: |
209 | | - log.error("Unexpected error from data store status function: %s", e) |
210 | | - |
211 | | - def is_monitoring_enabled(self) -> bool: |
212 | | - """ |
213 | | - This methods determines whether the wrapped store can support enabling monitoring. |
214 | | -
|
215 | | - The wrapped store must provide a monitoring_enabled method, which must |
216 | | - be true. But this alone is not sufficient. |
217 | | -
|
218 | | - Because this class wraps all interactions with a provided store, it can |
219 | | - technically "monitor" any store. However, monitoring also requires that |
220 | | - we notify listeners when the store is available again. |
221 | | -
|
222 | | - We determine this by checking the store's `available?` method, so this |
223 | | - is also a requirement for monitoring support. |
224 | | -
|
225 | | - These extra checks won't be necessary once `available` becomes a part |
226 | | - of the core interface requirements and this class no longer wraps every |
227 | | - feature store. |
228 | | - """ |
229 | | - |
230 | | - if not hasattr(self.store, 'is_monitoring_enabled'): |
231 | | - return False |
232 | | - |
233 | | - if not hasattr(self.store, 'is_available'): |
234 | | - return False |
235 | | - |
236 | | - monitoring_enabled = getattr(self.store, 'is_monitoring_enabled') |
237 | | - if not callable(monitoring_enabled): |
238 | | - return False |
239 | | - |
240 | | - return monitoring_enabled() |
241 | | - |
242 | | - def close(self): |
243 | | - """ |
244 | | - Close the wrapper and stop the repeating task poller if it's running. |
245 | | - Also forwards the close call to the underlying store if it has a close method. |
246 | | - """ |
247 | | - poller_to_stop = None |
248 | | - |
249 | | - with self.__lock.write(): |
250 | | - if self.__closed: |
251 | | - return |
252 | | - self.__closed = True |
253 | | - poller_to_stop = self.__poller |
254 | | - self.__poller = None |
255 | | - |
256 | | - if poller_to_stop is not None: |
257 | | - poller_to_stop.stop() |
258 | | - |
259 | | - if hasattr(self.store, "close"): |
260 | | - self.store.close() |
261 | | - |
262 | | - |
263 | 111 | class ConditionDirective(str, Enum): |
264 | 112 | """ |
265 | 113 | ConditionDirective represents the possible directives that can be returned from a condition check. |
@@ -416,7 +264,6 @@ def target_availability(self) -> DataAvailability: |
416 | 264 | 'ConditionDirective', |
417 | 265 | 'DataSourceStatusProviderImpl', |
418 | 266 | 'DataStoreStatusProviderImpl', |
419 | | - 'FeatureStoreClientWrapper', |
420 | 267 | 'fallback_condition', |
421 | 268 | 'recovery_condition', |
422 | 269 | ] |
0 commit comments