1010from splitio .util .backoff import Backoff
1111from splitio .optional .loaders import asyncio , aiofiles
1212from splitio .sync import util
13+ from splitio .optional .loaders import asyncio
1314
1415_LOGGER = logging .getLogger (__name__ )
1516
1617
1718_ON_DEMAND_FETCH_BACKOFF_BASE = 10 # backoff base starting at 10 seconds
1819_ON_DEMAND_FETCH_BACKOFF_MAX_WAIT = 60 # don't sleep for more than 1 minute
1920_ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES = 10
21+ _MAX_WORKERS = 10
2022
2123
2224class SegmentSynchronizer (object ):
23- def __init__ (self , segment_api , split_storage , segment_storage ):
25+ def __init__ (self , segment_api , feature_flag_storage , segment_storage ):
2426 """
2527 Class constructor.
2628
2729 :param segment_api: API to retrieve segments from backend.
2830 :type segment_api: splitio.api.SegmentApi
2931
30- :param split_storage : Feature Flag Storage.
31- :type split_storage : splitio.storage.InMemorySplitStorage
32+ :param feature_flag_storage : Feature Flag Storage.
33+ :type feature_flag_storage : splitio.storage.InMemorySplitStorage
3234
3335 :param segment_storage: Segment storage reference.
3436 :type segment_storage: splitio.storage.SegmentStorage
3537
3638 """
3739 self ._api = segment_api
38- self ._split_storage = split_storage
40+ self ._feature_flag_storage = feature_flag_storage
3941 self ._segment_storage = segment_storage
40- self ._worker_pool = workerpool .WorkerPool (10 , self .synchronize_segment )
42+ self ._worker_pool = workerpool .WorkerPool (_MAX_WORKERS , self .synchronize_segment )
4143 self ._worker_pool .start ()
4244 self ._backoff = Backoff (
4345 _ON_DEMAND_FETCH_BACKOFF_BASE ,
@@ -48,7 +50,7 @@ def recreate(self):
4850 Create worker_pool on forked processes.
4951
5052 """
51- self ._worker_pool = workerpool .WorkerPool (10 , self .synchronize_segment )
53+ self ._worker_pool = workerpool .WorkerPool (_MAX_WORKERS , self .synchronize_segment )
5254 self ._worker_pool .start ()
5355
5456 def shutdown (self ):
@@ -176,7 +178,7 @@ def synchronize_segments(self, segment_names = None, dont_wait = False):
176178 :rtype: bool
177179 """
178180 if segment_names is None :
179- segment_names = self ._split_storage .get_segment_names ()
181+ segment_names = self ._feature_flag_storage .get_segment_names ()
180182
181183 for segment_name in segment_names :
182184 self ._worker_pool .submit_work (segment_name )
@@ -196,6 +198,184 @@ def segment_exist_in_storage(self, segment_name):
196198 """
197199 return self ._segment_storage .get (segment_name ) != None
198200
201+
202+ class SegmentSynchronizerAsync (object ):
203+ def __init__ (self , segment_api , feature_flag_storage , segment_storage ):
204+ """
205+ Class constructor.
206+
207+ :param segment_api: API to retrieve segments from backend.
208+ :type segment_api: splitio.api.SegmentApi
209+
210+ :param feature_flag_storage: Feature Flag Storage.
211+ :type feature_flag_storage: splitio.storage.InMemorySplitStorage
212+
213+ :param segment_storage: Segment storage reference.
214+ :type segment_storage: splitio.storage.SegmentStorage
215+
216+ """
217+ self ._api = segment_api
218+ self ._feature_flag_storage = feature_flag_storage
219+ self ._segment_storage = segment_storage
220+ self ._worker_pool = workerpool .WorkerPoolAsync (_MAX_WORKERS , self .synchronize_segment )
221+ self ._worker_pool .start ()
222+ self ._backoff = Backoff (
223+ _ON_DEMAND_FETCH_BACKOFF_BASE ,
224+ _ON_DEMAND_FETCH_BACKOFF_MAX_WAIT )
225+
226+ def recreate (self ):
227+ """
228+ Create worker_pool on forked processes.
229+
230+ """
231+ self ._worker_pool = workerpool .WorkerPoolAsync (_MAX_WORKERS , self .synchronize_segment )
232+ self ._worker_pool .start ()
233+
234+ async def shutdown (self ):
235+ """
236+ Shutdown worker_pool
237+
238+ """
239+ await self ._worker_pool .stop ()
240+
241+ async def _fetch_until (self , segment_name , fetch_options , till = None ):
242+ """
243+ Hit endpoint, update storage and return when since==till.
244+
245+ :param segment_name: Name of the segment to update.
246+ :type segment_name: str
247+
248+ :param fetch_options Fetch options for getting segment definitions.
249+ :type fetch_options splitio.api.FetchOptions
250+
251+ :param till: Passed till from Streaming.
252+ :type till: int
253+
254+ :return: last change number
255+ :rtype: int
256+ """
257+ while True : # Fetch until since==till
258+ change_number = await self ._segment_storage .get_change_number (segment_name )
259+ if change_number is None :
260+ change_number = - 1
261+ if till is not None and till < change_number :
262+ # the passed till is less than change_number, no need to perform updates
263+ return change_number
264+
265+ try :
266+ segment_changes = await self ._api .fetch_segment (segment_name , change_number ,
267+ fetch_options )
268+ except APIException as exc :
269+ _LOGGER .error ('Exception raised while fetching segment %s' , segment_name )
270+ _LOGGER .debug ('Exception information: ' , exc_info = True )
271+ raise exc
272+
273+ if change_number == - 1 : # first time fetching the segment
274+ new_segment = segments .from_raw (segment_changes )
275+ await self ._segment_storage .put (new_segment )
276+ else :
277+ await self ._segment_storage .update (
278+ segment_name ,
279+ segment_changes ['added' ],
280+ segment_changes ['removed' ],
281+ segment_changes ['till' ]
282+ )
283+
284+ if segment_changes ['till' ] == segment_changes ['since' ]:
285+ return segment_changes ['till' ]
286+
287+ async def _attempt_segment_sync (self , segment_name , fetch_options , till = None ):
288+ """
289+ Hit endpoint, update storage and return True if sync is complete.
290+
291+ :param segment_name: Name of the segment to update.
292+ :type segment_name: str
293+
294+ :param fetch_options Fetch options for getting feature flag definitions.
295+ :type fetch_options splitio.api.FetchOptions
296+
297+ :param till: Passed till from Streaming.
298+ :type till: int
299+
300+ :return: Flags to check if it should perform bypass or operation ended
301+ :rtype: bool, int, int
302+ """
303+ self ._backoff .reset ()
304+ remaining_attempts = _ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES
305+ while True :
306+ remaining_attempts -= 1
307+ change_number = await self ._fetch_until (segment_name , fetch_options , till )
308+ if till is None or till <= change_number :
309+ return True , remaining_attempts , change_number
310+ elif remaining_attempts <= 0 :
311+ return False , remaining_attempts , change_number
312+ how_long = self ._backoff .get ()
313+ await asyncio .sleep (how_long )
314+
315+ async def synchronize_segment (self , segment_name , till = None ):
316+ """
317+ Update a segment from queue
318+
319+ :param segment_name: Name of the segment to update.
320+ :type segment_name: str
321+
322+ :param till: ChangeNumber received.
323+ :type till: int
324+
325+ :return: True if no error occurs. False otherwise.
326+ :rtype: bool
327+ """
328+ fetch_options = FetchOptions (True ) # Set Cache-Control to no-cache
329+ successful_sync , remaining_attempts , change_number = await self ._attempt_segment_sync (segment_name , fetch_options , till )
330+ attempts = _ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES - remaining_attempts
331+ if successful_sync : # succedeed sync
332+ _LOGGER .debug ('Refresh completed in %d attempts.' , attempts )
333+ return True
334+ with_cdn_bypass = FetchOptions (True , change_number ) # Set flag for bypassing CDN
335+ without_cdn_successful_sync , remaining_attempts , change_number = await self ._attempt_segment_sync (segment_name , with_cdn_bypass , till )
336+ without_cdn_attempts = _ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES - remaining_attempts
337+ if without_cdn_successful_sync :
338+ _LOGGER .debug ('Refresh completed bypassing the CDN in %d attempts.' ,
339+ without_cdn_attempts )
340+ return True
341+ _LOGGER .debug ('No changes fetched after %d attempts with CDN bypassed.' ,
342+ without_cdn_attempts )
343+ return False
344+
345+ async def synchronize_segments (self , segment_names = None , dont_wait = False ):
346+ """
347+ Submit all current segments and wait for them to finish depend on dont_wait flag, then set the ready flag.
348+
349+ :param segment_names: Optional, array of segment names to update.
350+ :type segment_name: {str}
351+
352+ :param dont_wait: Optional, instruct the function to not wait for task completion
353+ :type segment_name: boolean
354+
355+ :return: True if no error occurs or dont_wait flag is True. False otherwise.
356+ :rtype: bool
357+ """
358+ if segment_names is None :
359+ segment_names = await self ._feature_flag_storage .get_segment_names ()
360+
361+ jobs = await self ._worker_pool .submit_work (segment_names )
362+ if (dont_wait ):
363+ return True
364+ return await jobs .await_completion ()
365+
366+ async def segment_exist_in_storage (self , segment_name ):
367+ """
368+ Check if a segment exists in the storage
369+
370+ :param segment_name: Name of the segment
371+ :type segment_name: str
372+
373+ :return: True if segment exist. False otherwise.
374+ :rtype: bool
375+ """
376+ return await self ._segment_storage .get (segment_name ) != None
377+
378+
199379class LocalSegmentSynchronizerBase (object ):
200380 """Localhost mode segment base synchronizer."""
201381
0 commit comments