From c58cb4b3cb0a21cbf1ed343ff3119dd56900ebd4 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 14 Aug 2024 09:48:50 -0300 Subject: [PATCH] WIP --- .../android/client/SplitClientConfig.java | 14 +++-- .../android/client/SplitFactoryHelper.java | 3 +- .../android/client/SplitFactoryImpl.java | 3 +- .../client/events/SplitEventsManager.java | 6 ++- .../service/executor/SplitTaskFactory.java | 4 +- .../executor/SplitTaskFactoryImpl.java | 19 ++++++- .../mysegments/MySegmentsTaskFactory.java | 4 ++ .../mysegments/MySegmentsTaskFactoryImpl.java | 17 ++++++ .../service/splits/SplitChangeProcessor.java | 2 +- .../service/splits/SplitsSyncHelper.java | 11 ++-- .../FeatureFlagsSynchronizer.java | 2 + .../FeatureFlagsSynchronizerImpl.java | 16 ++++-- .../LastUpdateTimestampProvider.java | 8 +++ .../LastUpdateTimestampProviderImpl.java | 45 ++++++++++++++++ .../synchronizer/RolloutCacheManager.java | 54 +++++++++++++++++++ .../service/synchronizer/SyncManagerImpl.java | 4 +- .../service/synchronizer/Synchronizer.java | 2 +- .../synchronizer/SynchronizerImpl.java | 22 ++++++-- .../mysegments/MySegmentsSynchronizer.java | 2 + .../MySegmentsSynchronizerImpl.java | 5 ++ .../MySegmentsSynchronizerRegistry.java | 3 ++ .../MySegmentsSynchronizerRegistryImpl.java | 5 ++ .../storage/common/SplitStorageContainer.java | 14 +++-- .../client/storage/db/StorageFactory.java | 6 +++ .../storage/mysegments/MySegmentsStorage.java | 3 +- .../client/service/SynchronizerTest.java | 2 +- .../FeatureFlagsSynchronizerImplTest.java | 12 ++--- .../SynchronizerImplTelemetryTest.java | 2 +- 28 files changed, 252 insertions(+), 38 deletions(-) create mode 100644 src/main/java/io/split/android/client/service/synchronizer/LastUpdateTimestampProvider.java create mode 100644 src/main/java/io/split/android/client/service/synchronizer/LastUpdateTimestampProviderImpl.java create mode 100644 src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java diff --git a/src/main/java/io/split/android/client/SplitClientConfig.java b/src/main/java/io/split/android/client/SplitClientConfig.java index 2c2b1b845..c5b44e5d6 100644 --- a/src/main/java/io/split/android/client/SplitClientConfig.java +++ b/src/main/java/io/split/android/client/SplitClientConfig.java @@ -511,6 +511,10 @@ public boolean waitForLargeSegments() { return mLargeSegmentsEnabled && mWaitForLargeSegments; } + public boolean forceCacheExpiration() { + return true; // TODO + } + public static final class Builder { static final int PROXY_PORT_DEFAULT = 80; @@ -1168,11 +1172,11 @@ public Builder waitForLargeSegments(boolean enabled) { public SplitClientConfig build() { Logger.instance().setLevel(mLogLevel); - if (mFeaturesRefreshRate < MIN_FEATURES_REFRESH_RATE) { - Logger.w("Features refresh rate is lower than allowed. " + - "Setting to default value."); - mFeaturesRefreshRate = DEFAULT_FEATURES_REFRESH_RATE_SECS; - } +// TODO if (mFeaturesRefreshRate < MIN_FEATURES_REFRESH_RATE) { +// TODO Logger.w("Features refresh rate is lower than allowed. " + +// TODO "Setting to default value."); +// TODO mFeaturesRefreshRate = DEFAULT_FEATURES_REFRESH_RATE_SECS; +// TODO } if (mSegmentsRefreshRate < MIN_MY_SEGMENTS_REFRESH_RATE) { Logger.w("Segments refresh rate is lower than allowed. " + diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 05823320b..daf2d21ff 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -176,7 +176,8 @@ SplitStorageContainer buildStorageContainer(UserConsent userConsentStatus, StorageFactory.getAttributesStorage(), StorageFactory.getPersistentAttributesStorage(splitRoomDatabase, splitCipher), getTelemetryStorage(shouldRecordTelemetry, telemetryStorage), - StorageFactory.getImpressionsObserverCachePersistentStorage(splitRoomDatabase, observerCacheExpirationPeriod)); + StorageFactory.getImpressionsObserverCachePersistentStorage(splitRoomDatabase, observerCacheExpirationPeriod), + StorageFactory.getLastUpdateTimestampProvider(splitRoomDatabase)); } SplitApiFacade buildApiFacade(SplitClientConfig splitClientConfig, diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index da1db63ff..1d9b6f06a 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -215,7 +215,8 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { impressionManager, mStorageContainer.getEventsStorage(), mEventsManagerCoordinator, - streamingComponents.getPushManagerEventBroadcaster() + streamingComponents.getPushManagerEventBroadcaster(), + mStorageContainer.getLastUpdateTimestampProvider() ); // Only available for integration tests if (synchronizerSpy != null) { diff --git a/src/main/java/io/split/android/client/events/SplitEventsManager.java b/src/main/java/io/split/android/client/events/SplitEventsManager.java index 8c0fb2a2a..396f04c29 100644 --- a/src/main/java/io/split/android/client/events/SplitEventsManager.java +++ b/src/main/java/io/split/android/client/events/SplitEventsManager.java @@ -2,6 +2,8 @@ import static io.split.android.client.utils.Utils.checkNotNull; +import android.os.SystemClock; + import androidx.annotation.VisibleForTesting; import java.util.ArrayList; @@ -29,6 +31,7 @@ public class SplitEventsManager extends BaseEventsManager implements ISplitEvent private final SplitTaskExecutor mSplitTaskExecutor; private final AtomicBoolean mLargeSegmentsEnabled; private final AtomicBoolean mWaitForLargeSegments; + private final long mStartTime; public SplitEventsManager(SplitClientConfig config, SplitTaskExecutor splitTaskExecutor) { this(splitTaskExecutor, config.blockUntilReady(), config.largeSegmentsEnabled(), config.waitForLargeSegments()); @@ -36,6 +39,7 @@ public SplitEventsManager(SplitClientConfig config, SplitTaskExecutor splitTaskE public SplitEventsManager(SplitTaskExecutor splitTaskExecutor, final int blockUntilReady, boolean largeSegmentsEnabled, boolean waitForLargeSegments) { super(); + mStartTime = SystemClock.uptimeMillis(); mSplitTaskExecutor = splitTaskExecutor; mSubscriptions = new ConcurrentHashMap<>(); mExecutionTimes = new ConcurrentHashMap<>(); @@ -222,7 +226,7 @@ private void trigger(SplitEvent event) { // If executionTimes is grater than zero, maximum executions decrease 1 } else if (mExecutionTimes.get(event) > 0) { if (event != null) { - Logger.d(event.name() + " event triggered"); + Logger.d(event.name() + " event triggered in " + (SystemClock.uptimeMillis() - mStartTime) + " ms"); } mExecutionTimes.put(event, mExecutionTimes.get(event) - 1); } //If executionTimes is lower than zero, execute it without limitation diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java index e23407f55..ab0fa1bd9 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java @@ -16,7 +16,7 @@ public interface SplitTaskFactory extends TelemetryTaskFactory, ImpressionsTaskF EventsRecorderTask createEventsRecorderTask(); - SplitsSyncTask createSplitsSyncTask(boolean checkCacheExpiration); + SplitsSyncTask createSplitsSyncTask(boolean checkCacheExpiration, boolean forceCacheExpiration); LoadSplitsTask createLoadSplitsTask(); @@ -29,4 +29,6 @@ public interface SplitTaskFactory extends TelemetryTaskFactory, ImpressionsTaskF FilterSplitsInCacheTask createFilterSplitsInCacheTask(); CleanUpDatabaseTask createCleanUpDatabaseTask(long maxTimestamp); + + SplitTask createExpireSplitsTask(); } diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 2c167cda0..119de235c 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -47,6 +47,7 @@ import io.split.android.client.storage.common.SplitStorageContainer; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; import io.split.android.client.telemetry.storage.TelemetryStorage; +import io.split.android.client.utils.logger.Logger; public class SplitTaskFactoryImpl implements SplitTaskFactory { @@ -124,9 +125,10 @@ public ImpressionsRecorderTask createImpressionsRecorderTask() { } @Override - public SplitsSyncTask createSplitsSyncTask(boolean checkCacheExpiration) { + public SplitsSyncTask createSplitsSyncTask(boolean checkCacheExpiration, boolean forceCacheExpiration) { return SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorageContainer.getSplitsStorage(), checkCacheExpiration, - mSplitClientConfig.cacheExpirationInSeconds(), mSplitsFilterQueryStringFromConfig, mEventsManager, mSplitsStorageContainer.getTelemetryStorage()); + mSplitClientConfig.cacheExpirationInSeconds(), mSplitsFilterQueryStringFromConfig, mEventsManager, mSplitsStorageContainer.getTelemetryStorage(), + forceCacheExpiration); } @Override @@ -229,4 +231,17 @@ private TelemetryTaskFactory initializeTelemetryTaskFactory(@NonNull SplitClient invalidFlagSetCount); return mTelemetryTaskFactory; } + + @Override + public SplitTask createExpireSplitsTask() { + return new SplitTask() { + @NonNull + @Override + public SplitTaskExecutionInfo execute() { + Logger.e("EXPIRING CACHE OF FLAGS"); + mSplitsStorageContainer.getSplitsStorage().clear(); + return SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK); + } + }; + } } diff --git a/src/main/java/io/split/android/client/service/mysegments/MySegmentsTaskFactory.java b/src/main/java/io/split/android/client/service/mysegments/MySegmentsTaskFactory.java index f9eb3ab22..14f1af85a 100644 --- a/src/main/java/io/split/android/client/service/mysegments/MySegmentsTaskFactory.java +++ b/src/main/java/io/split/android/client/service/mysegments/MySegmentsTaskFactory.java @@ -3,6 +3,8 @@ import java.util.List; import java.util.Set; +import io.split.android.client.service.executor.SplitTask; + public interface MySegmentsTaskFactory { MySegmentsSyncTask createMySegmentsSyncTask(boolean avoidCache); @@ -12,4 +14,6 @@ public interface MySegmentsTaskFactory { MySegmentsOverwriteTask createMySegmentsOverwriteTask(List segments, Long changeNumber); MySegmentsUpdateTask createMySegmentsUpdateTask(boolean add, Set segmentNames, Long changeNumber); + + SplitTask createExpireMySegmentsTask(); } diff --git a/src/main/java/io/split/android/client/service/mysegments/MySegmentsTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/mysegments/MySegmentsTaskFactoryImpl.java index 93a35199c..ad387a1fe 100644 --- a/src/main/java/io/split/android/client/service/mysegments/MySegmentsTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/mysegments/MySegmentsTaskFactoryImpl.java @@ -7,7 +7,11 @@ import java.util.List; import java.util.Set; +import io.split.android.client.service.executor.SplitTask; +import io.split.android.client.service.executor.SplitTaskExecutionInfo; +import io.split.android.client.service.executor.SplitTaskType; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; +import io.split.android.client.utils.logger.Logger; public class MySegmentsTaskFactoryImpl implements MySegmentsTaskFactory { @@ -44,4 +48,17 @@ public MySegmentsOverwriteTask createMySegmentsOverwriteTask(List segmen public MySegmentsUpdateTask createMySegmentsUpdateTask(boolean add, Set segmentNames, Long changeNumber) { return new MySegmentsUpdateTask(mConfiguration.getStorage(), add, segmentNames, changeNumber, mConfiguration.getEventsManager(), mTelemetryRuntimeProducer, mConfiguration.getMySegmentsUpdateTaskConfig()); } + + @Override + public SplitTask createExpireMySegmentsTask() { + return new SplitTask() { + @NonNull + @Override + public SplitTaskExecutionInfo execute() { + Logger.e("EXPIRING SEGMENTS CACHE"); + mConfiguration.getStorage().clear(); + return SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK); + } + }; + } } diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index 6ca3a5c05..2c4fe4545 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -74,7 +74,7 @@ private ProcessedSplitChange buildProcessedSplitChange(List featureFlags, processStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); } - return new ProcessedSplitChange(activeFeatureFlags, archivedFeatureFlags, changeNumber, System.currentTimeMillis() / 100); + return new ProcessedSplitChange(activeFeatureFlags, archivedFeatureFlags, changeNumber, System.currentTimeMillis()); } private FeatureFlagProcessStrategy getProcessStrategy(SplitFilter splitFilter) { diff --git a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java index df8258a7a..39e78df6e 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java +++ b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java @@ -184,14 +184,15 @@ private void updateStorage(boolean clearBeforeUpdate, SplitChange splitChange) { } public boolean cacheHasExpired(long storedChangeNumber, long updateTimestamp, long cacheExpirationInSeconds) { - long elapsed = now() - updateTimestamp; - return storedChangeNumber > -1 + long elapsed = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) - TimeUnit.MILLISECONDS.toSeconds(updateTimestamp); + boolean expired = storedChangeNumber > -1 && updateTimestamp > 0 && (elapsed > cacheExpirationInSeconds); - } - private long now() { - return System.currentTimeMillis() / 1000; + if (expired) { + Logger.e("Cache is expired"); + } + return expired; } private void logError(String message) { diff --git a/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizer.java b/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizer.java index 6b48eeccd..ba6b1004e 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizer.java +++ b/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizer.java @@ -17,4 +17,6 @@ public interface FeatureFlagsSynchronizer { void stopSynchronization(); void submitLoadingTask(SplitTaskExecutionListener listener); + + void expireCache(); } diff --git a/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImpl.java index d29d98094..c06656242 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImpl.java @@ -39,6 +39,7 @@ public class FeatureFlagsSynchronizerImpl implements FeatureFlagsSynchronizer { @Nullable private final SplitTaskExecutionListener mSplitsSyncListener; private final AtomicBoolean mIsSynchronizing = new AtomicBoolean(true); + private final AtomicBoolean mForceCacheExpiration = new AtomicBoolean(false); public FeatureFlagsSynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitTaskExecutor taskExecutor, @@ -62,6 +63,10 @@ public FeatureFlagsSynchronizerImpl(@NonNull SplitClientConfig splitClientConfig public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { if (taskInfo.getStatus() == SplitTaskExecutionStatus.SUCCESS) { pushManagerEventBroadcaster.pushMessage(new PushStatusEvent(PushStatusEvent.EventType.SUCCESSFUL_SYNC)); + + if (mForceCacheExpiration.compareAndSet(true, false)) { + mSplitsSyncRetryTimer.setTask(mSplitTaskFactory.createSplitsSyncTask(false, false), mSplitsSyncListener); + } } else { avoidRetries(taskInfo.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY)); } @@ -77,8 +82,8 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { } }; } - - mSplitsSyncRetryTimer.setTask(mSplitTaskFactory.createSplitsSyncTask(true), mSplitsSyncListener); + mForceCacheExpiration.set(mSplitClientConfig.forceCacheExpiration()); + mSplitsSyncRetryTimer.setTask(mSplitTaskFactory.createSplitsSyncTask(true, mForceCacheExpiration.get()), mSplitsSyncListener); mLoadLocalSplitsListener = new LoadLocalDataListener( splitEventsManager, SplitInternalEvent.SPLITS_LOADED_FROM_STORAGE); } @@ -134,13 +139,18 @@ public void submitLoadingTask(SplitTaskExecutionListener listener) { listener); } + @Override + public void expireCache() { + mTaskExecutor.submit(mSplitTaskFactory.createExpireSplitsTask(), null); + } + private void scheduleSplitsFetcherTask() { if (mSplitsFetcherTaskId != null) { mSplitsTaskExecutor.stopTask(mSplitsFetcherTaskId); } mSplitsFetcherTaskId = mSplitsTaskExecutor.schedule( - mSplitTaskFactory.createSplitsSyncTask(false), + mSplitTaskFactory.createSplitsSyncTask(false, false), mSplitClientConfig.featuresRefreshRate(), mSplitClientConfig.featuresRefreshRate(), mSplitsSyncListener); diff --git a/src/main/java/io/split/android/client/service/synchronizer/LastUpdateTimestampProvider.java b/src/main/java/io/split/android/client/service/synchronizer/LastUpdateTimestampProvider.java new file mode 100644 index 000000000..86c32eace --- /dev/null +++ b/src/main/java/io/split/android/client/service/synchronizer/LastUpdateTimestampProvider.java @@ -0,0 +1,8 @@ +package io.split.android.client.service.synchronizer; + +public interface LastUpdateTimestampProvider { + + long getLastUpdateTimestamp(); + + void setLastUpdateTimestamp(long timestamp); +} diff --git a/src/main/java/io/split/android/client/service/synchronizer/LastUpdateTimestampProviderImpl.java b/src/main/java/io/split/android/client/service/synchronizer/LastUpdateTimestampProviderImpl.java new file mode 100644 index 000000000..f1892ef5f --- /dev/null +++ b/src/main/java/io/split/android/client/service/synchronizer/LastUpdateTimestampProviderImpl.java @@ -0,0 +1,45 @@ +package io.split.android.client.service.synchronizer; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; + +import io.split.android.client.storage.db.GeneralInfoDao; +import io.split.android.client.storage.db.GeneralInfoEntity; +import io.split.android.client.utils.logger.Logger; + +public class LastUpdateTimestampProviderImpl implements LastUpdateTimestampProvider { + + private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); + private final AtomicLong mTimestamp = new AtomicLong(-1); + private final Object mLock = new Object(); + private final GeneralInfoDao mGeneralInfoDao; + + public LastUpdateTimestampProviderImpl(GeneralInfoDao generalInfoDao) { + mGeneralInfoDao = generalInfoDao; + mExecutor.submit(() -> { + synchronized (mLock) { + Logger.e("Retrieving timestamp for initialization"); + mTimestamp.set(mGeneralInfoDao.getByName(GeneralInfoEntity.SPLITS_UPDATE_TIMESTAMP).getLongValue()); + Logger.e("Initialized timestamp to " + mTimestamp.get()); + } + }); + } + + @Override + public long getLastUpdateTimestamp() { + synchronized (mLock) { + return mTimestamp.get(); + } + } + + @Override + public void setLastUpdateTimestamp(long timestamp) { + mTimestamp.set(timestamp); + mExecutor.submit(() -> { + synchronized (mLock) { + mGeneralInfoDao.update(new GeneralInfoEntity(GeneralInfoEntity.SPLITS_UPDATE_TIMESTAMP, timestamp)); + } + }); + } +} diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java new file mode 100644 index 000000000..abf5026d4 --- /dev/null +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java @@ -0,0 +1,54 @@ +package io.split.android.client.service.synchronizer; + +import java.util.concurrent.TimeUnit; + +import io.split.android.client.service.synchronizer.mysegments.MySegmentsSynchronizerRegistry; +import io.split.android.client.utils.logger.Logger; + +class RolloutCacheManagerImpl implements RolloutCacheManager { + + private final LastUpdateTimestampProvider mTimestampProvider; + private final FeatureFlagsSynchronizer mFeatureFlagsSynchronizer; + private final MySegmentsSynchronizerRegistry.Tasks mMySegmentsSynchronizerRegistry; + private final boolean mForceCacheExpiration; + private final long mCacheExpirationPeriod; + + RolloutCacheManagerImpl(LastUpdateTimestampProvider timestampProvider, + FeatureFlagsSynchronizer featureFlagsSynchronizer, + MySegmentsSynchronizerRegistry.Tasks mySegmentsSynchronizerRegistry, + boolean forceCacheExpiration, + long cacheExpirationPeriod) { + mTimestampProvider = timestampProvider; + mFeatureFlagsSynchronizer = featureFlagsSynchronizer; + mMySegmentsSynchronizerRegistry = mySegmentsSynchronizerRegistry; + mForceCacheExpiration = forceCacheExpiration; + mCacheExpirationPeriod = cacheExpirationPeriod; + } + + @Override + public void validateCache() { + long lastUpdateTimestamp = mTimestampProvider.getLastUpdateTimestamp(); + long currentTime = System.currentTimeMillis() / 1000; + long elapsedTime = currentTime - lastUpdateTimestamp; + + if (mForceCacheExpiration || TimeUnit.MILLISECONDS.toSeconds(elapsedTime) > mCacheExpirationPeriod) { + if (mForceCacheExpiration) { + Logger.v("Forcing cache expiration"); + } else { + Logger.v("Cache expired due to time"); + } + clearCache(); + } + } + + private void clearCache() { + mFeatureFlagsSynchronizer.expireCache(); + mMySegmentsSynchronizerRegistry.expireCache(); + } +} + +interface RolloutCacheManager { + + void validateCache(); +} + diff --git a/src/main/java/io/split/android/client/service/synchronizer/SyncManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/SyncManagerImpl.java index 5ba6ed0a0..9ffa7d37b 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/SyncManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/SyncManagerImpl.java @@ -82,12 +82,14 @@ public SyncManagerImpl(@NonNull SplitClientConfig splitClientConfig, @Override public void start() { +// mSynchronizer.validateCache(); mSynchronizer.loadAndSynchronizeSplits(); mSynchronizer.loadMySegmentsFromCache(); + if (mSplitClientConfig.largeSegmentsEnabled()) mSynchronizer.loadMyLargeSegmentsFromCache(); mSynchronizer.loadAttributesFromCache(); + mSynchronizer.synchronizeMySegments(); if (mSplitClientConfig.largeSegmentsEnabled()) { - mSynchronizer.loadMyLargeSegmentsFromCache(); mSynchronizer.synchronizeMyLargeSegments(); } if (mSplitClientConfig.userConsent() == UserConsent.GRANTED) { diff --git a/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java b/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java index b93c19fd2..6c4bae2ab 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java +++ b/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java @@ -4,7 +4,7 @@ import io.split.android.client.impressions.Impression; import io.split.android.client.lifecycle.SplitLifecycleAware; -public interface Synchronizer extends SplitLifecycleAware { +public interface Synchronizer extends SplitLifecycleAware, RolloutCacheManager { void loadAndSynchronizeSplits(); diff --git a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java index 8dcfd1d6d..d4053ba50 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java @@ -57,6 +57,7 @@ public class SynchronizerImpl implements Synchronizer, SplitTaskExecutionListene private final MySegmentsSynchronizerRegistryImpl mMySegmentsSynchronizerRegistry; private final AtomicBoolean mIsSynchronizingEvents = new AtomicBoolean(true); private final SplitTaskExecutionListener mEventsTaskExecutionListener; + private final RolloutCacheManager mRolloutCacheManager; public SynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitTaskExecutor taskExecutor, @@ -70,7 +71,8 @@ public SynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull ImpressionManager impressionManager, @NonNull StoragePusher eventsStorage, @NonNull ISplitEventsManager eventsManagerCoordinator, - @Nullable PushManagerEventBroadcaster pushManagerEventBroadcaster) { + @Nullable PushManagerEventBroadcaster pushManagerEventBroadcaster, + @NonNull LastUpdateTimestampProvider lastUpdateTimestampProvider) { this(splitClientConfig, taskExecutor, splitSingleThreadTaskExecutor, @@ -88,7 +90,8 @@ public SynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, retryBackoffCounterTimerFactory, pushManagerEventBroadcaster ), - eventsStorage); + eventsStorage, + lastUpdateTimestampProvider); } @VisibleForTesting @@ -103,7 +106,8 @@ public SynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull MySegmentsSynchronizerRegistryImpl mySegmentsSynchronizerRegistry, @NonNull ImpressionManager impressionManager, @NonNull FeatureFlagsSynchronizer featureFlagsSynchronizer, - @NonNull StoragePusher eventsStorage) { + @NonNull StoragePusher eventsStorage, + @NonNull LastUpdateTimestampProvider lastUpdateTimestampProvider) { mTaskExecutor = checkNotNull(taskExecutor); mSingleThreadTaskExecutor = checkNotNull(splitSingleThreadTaskExecutor); @@ -137,6 +141,18 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { } else { workManagerWrapper.removeWork(); } + + mRolloutCacheManager = new RolloutCacheManagerImpl( + lastUpdateTimestampProvider, + mFeatureFlagsSynchronizer, + mMySegmentsSynchronizerRegistry, + mSplitClientConfig.forceCacheExpiration(), + mSplitClientConfig.cacheExpirationInSeconds()); + } + + @Override + public void validateCache() { + mRolloutCacheManager.validateCache(); } @Override diff --git a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizer.java b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizer.java index 3e5377452..96d8e8c13 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizer.java +++ b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizer.java @@ -15,4 +15,6 @@ public interface MySegmentsSynchronizer { void submitMySegmentsLoadingTask(); void stopPeriodicFetching(); + + void expireCache(); } diff --git a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerImpl.java index 4773fd5c1..f51cca128 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerImpl.java @@ -104,6 +104,11 @@ public void submitMySegmentsLoadingTask() { submitMySegmentsLoadingTask(null); } + @Override + public void expireCache() { + mTaskExecutor.submit(mSplitTaskFactory.createExpireMySegmentsTask(), null); + } + private void submitMySegmentsLoadingTask(SplitTaskExecutionListener executionListener) { mTaskExecutor.submit(mSplitTaskFactory.createLoadMySegmentsTask(), executionListener); } diff --git a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistry.java b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistry.java index 0470eded6..14f9ebb0c 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistry.java +++ b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistry.java @@ -9,6 +9,7 @@ public interface MySegmentsSynchronizerRegistry { void unregisterMySegmentsSynchronizer(String userKey); interface Tasks { + enum SegmentType { SEGMENT, LARGE_SEGMENT @@ -27,5 +28,7 @@ enum SegmentType { void submitMySegmentsLoadingTask(SegmentType segmentType); void stopPeriodicFetching(); + + void expireCache(); } } diff --git a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImpl.java b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImpl.java index 21bc24ee3..d8b7e973e 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerRegistryImpl.java @@ -89,6 +89,11 @@ public synchronized void stopPeriodicFetching() { mStoppedPeriodicFetching.set(true); } + @Override + public void expireCache() { + executeForAll(MySegmentsSynchronizer::expireCache); + } + private void triggerPendingActions(MySegmentsSynchronizer mySegmentsSynchronizer) { if (mLoadedFromCache.get()) { mySegmentsSynchronizer.loadMySegmentsFromCache(); diff --git a/src/main/java/io/split/android/client/storage/common/SplitStorageContainer.java b/src/main/java/io/split/android/client/storage/common/SplitStorageContainer.java index af0f62f98..51c096fd9 100644 --- a/src/main/java/io/split/android/client/storage/common/SplitStorageContainer.java +++ b/src/main/java/io/split/android/client/storage/common/SplitStorageContainer.java @@ -1,8 +1,11 @@ package io.split.android.client.storage.common; +import static io.split.android.client.utils.Utils.checkNotNull; + import androidx.annotation.NonNull; import io.split.android.client.service.impressions.observer.PersistentImpressionsObserverCacheStorage; +import io.split.android.client.service.synchronizer.LastUpdateTimestampProvider; import io.split.android.client.storage.attributes.AttributesStorage; import io.split.android.client.storage.attributes.AttributesStorageContainer; import io.split.android.client.storage.attributes.PersistentAttributesStorage; @@ -18,8 +21,6 @@ import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.storage.TelemetryStorage; -import static io.split.android.client.utils.Utils.checkNotNull; - public class SplitStorageContainer { private final SplitsStorage mSplitStorage; @@ -36,6 +37,7 @@ public class SplitStorageContainer { private final TelemetryStorage mTelemetryStorage; private final PersistentImpressionsUniqueStorage mPersistentImpressionsUniqueStorage; private final PersistentImpressionsObserverCacheStorage mPersistentImpressionsObserverCacheStorage; + private final LastUpdateTimestampProvider mLastUpdateTimestampProvider; public SplitStorageContainer(@NonNull SplitsStorage splitStorage, @NonNull MySegmentsStorageContainer mySegmentsStorageContainer, @@ -50,7 +52,8 @@ public SplitStorageContainer(@NonNull SplitsStorage splitStorage, @NonNull AttributesStorageContainer attributesStorageContainer, @NonNull PersistentAttributesStorage persistentAttributesStorage, @NonNull TelemetryStorage telemetryStorage, - @NonNull PersistentImpressionsObserverCacheStorage persistentImpressionsObserverCacheStorage) { + @NonNull PersistentImpressionsObserverCacheStorage persistentImpressionsObserverCacheStorage, + @NonNull LastUpdateTimestampProvider lastUpdateTimestampProvider) { mSplitStorage = checkNotNull(splitStorage); mMySegmentsStorageContainer = checkNotNull(mySegmentsStorageContainer); @@ -66,6 +69,7 @@ public SplitStorageContainer(@NonNull SplitsStorage splitStorage, mTelemetryStorage = checkNotNull(telemetryStorage); mPersistentImpressionsUniqueStorage = checkNotNull(persistentImpressionsUniqueStorage); mPersistentImpressionsObserverCacheStorage = checkNotNull(persistentImpressionsObserverCacheStorage); + mLastUpdateTimestampProvider = checkNotNull(lastUpdateTimestampProvider); } public SplitsStorage getSplitsStorage() { @@ -135,4 +139,8 @@ public PersistentImpressionsUniqueStorage getPersistentImpressionsUniqueStorage( public PersistentImpressionsObserverCacheStorage getImpressionsObserverCachePersistentStorage() { return mPersistentImpressionsObserverCacheStorage; } + + public LastUpdateTimestampProvider getLastUpdateTimestampProvider() { + return mLastUpdateTimestampProvider; + } } diff --git a/src/main/java/io/split/android/client/storage/db/StorageFactory.java b/src/main/java/io/split/android/client/storage/db/StorageFactory.java index f4d6b9be1..87b3fb80b 100644 --- a/src/main/java/io/split/android/client/storage/db/StorageFactory.java +++ b/src/main/java/io/split/android/client/storage/db/StorageFactory.java @@ -7,6 +7,8 @@ import io.split.android.client.service.ServiceConstants; import io.split.android.client.service.impressions.observer.PersistentImpressionsObserverCacheStorage; import io.split.android.client.service.impressions.observer.SqlitePersistentImpressionsObserverCacheStorage; +import io.split.android.client.service.synchronizer.LastUpdateTimestampProvider; +import io.split.android.client.service.synchronizer.LastUpdateTimestampProviderImpl; import io.split.android.client.storage.attributes.AttributesStorageContainer; import io.split.android.client.storage.attributes.AttributesStorageContainerImpl; import io.split.android.client.storage.attributes.PersistentAttributesStorage; @@ -146,4 +148,8 @@ private static AttributesStorageContainer getAttributesStorageContainerInstance( public static PersistentImpressionsObserverCacheStorage getImpressionsObserverCachePersistentStorage(SplitRoomDatabase splitRoomDatabase, long expirationPeriod) { return new SqlitePersistentImpressionsObserverCacheStorage(splitRoomDatabase.impressionsObserverCacheDao(), expirationPeriod); } + + public static LastUpdateTimestampProvider getLastUpdateTimestampProvider(SplitRoomDatabase splitRoomDatabase) { + return new LastUpdateTimestampProviderImpl(splitRoomDatabase.generalInfoDao()); + } } diff --git a/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorage.java b/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorage.java index 4e87d6ae7..573c2517c 100644 --- a/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorage.java +++ b/src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorage.java @@ -1,7 +1,6 @@ package io.split.android.client.storage.mysegments; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import java.util.List; import java.util.Set; @@ -15,6 +14,6 @@ public interface MySegmentsStorage { long getTill(); - @VisibleForTesting +// @VisibleForTesting void clear(); } diff --git a/src/test/java/io/split/android/client/service/SynchronizerTest.java b/src/test/java/io/split/android/client/service/SynchronizerTest.java index 32b0ee9e4..17ae50941 100644 --- a/src/test/java/io/split/android/client/service/SynchronizerTest.java +++ b/src/test/java/io/split/android/client/service/SynchronizerTest.java @@ -181,7 +181,7 @@ public void setup(SplitClientConfig splitClientConfig, ImpressionManagerConfig.M when(mSplitStorageContainer.getEventsStorage()).thenReturn(mEventsStorage); when(mSplitStorageContainer.getImpressionsStorage()).thenReturn(mImpressionsStorage); - when(mTaskFactory.createSplitsSyncTask(anyBoolean())).thenReturn(Mockito.mock(SplitsSyncTask.class)); + when(mTaskFactory.createSplitsSyncTask(anyBoolean(), mTaskFactory.mSplitClientConfig.forceCacheExpiration())).thenReturn(Mockito.mock(SplitsSyncTask.class)); when(mMySegmentsTaskFactory.createMySegmentsSyncTask(anyBoolean())).thenReturn(Mockito.mock(MySegmentsSyncTask.class)); when(mTaskFactory.createImpressionsRecorderTask()).thenReturn(Mockito.mock(ImpressionsRecorderTask.class)); when(mTaskFactory.createEventsRecorderTask()).thenReturn(Mockito.mock(EventsRecorderTask.class)); diff --git a/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java b/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java index ae21ea58c..ff05caf58 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java +++ b/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java @@ -97,7 +97,7 @@ public void loadAndSynchronizeSplits() { SplitsSyncTask mockSplitSyncTask = mock(SplitsSyncTask.class); when(mockSplitSyncTask.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.SPLITS_SYNC)); - when(mTaskFactory.createSplitsSyncTask(true)).thenReturn(mockSplitSyncTask); + when(mTaskFactory.createSplitsSyncTask(true, mTaskFactory.mSplitClientConfig.forceCacheExpiration())).thenReturn(mockSplitSyncTask); when(mRetryBackoffCounterFactory.create(any(), anyInt())) .thenReturn(mRetryTimerSplitsSync) @@ -116,7 +116,7 @@ public void loadAndSynchronizeSplits() { @Test public void splitExecutorSchedule() { SplitsSyncTask mockTask = mock(SplitsSyncTask.class); - when(mTaskFactory.createSplitsSyncTask(false)).thenReturn(mockTask); + when(mTaskFactory.createSplitsSyncTask(false, mTaskFactory.mSplitClientConfig.forceCacheExpiration())).thenReturn(mockTask); mFeatureFlagsSynchronizer.startPeriodicFetching(); verify(mSingleThreadTaskExecutor).schedule( eq(mockTask), anyLong(), anyLong(), @@ -142,7 +142,7 @@ public void synchronize() { @Test public void stopPeriodicFetching() { SplitsSyncTask mockTask = mock(SplitsSyncTask.class); - when(mTaskFactory.createSplitsSyncTask(false)).thenReturn(mockTask); + when(mTaskFactory.createSplitsSyncTask(false, mTaskFactory.mSplitClientConfig.forceCacheExpiration())).thenReturn(mockTask); when(mSingleThreadTaskExecutor.schedule(eq(mockTask), anyLong(), anyLong(), any())).thenReturn("12"); // start periodic fetching to populate task id @@ -156,7 +156,7 @@ public void stopPeriodicFetching() { public void startPeriodicFetchingCancelsPreviousTaskIfExecutedSequentially() { SplitsSyncTask mockTask = mock(SplitsSyncTask.class); SplitsSyncTask mockTask2 = mock(SplitsSyncTask.class); - when(mTaskFactory.createSplitsSyncTask(false)) + when(mTaskFactory.createSplitsSyncTask(false, mTaskFactory.mSplitClientConfig.forceCacheExpiration())) .thenReturn(mockTask) .thenReturn(mockTask2); when(mSingleThreadTaskExecutor.schedule(eq(mockTask), anyLong(), anyLong(), any())) @@ -174,7 +174,7 @@ public void startPeriodicFetchingCancelsPreviousTaskIfExecutedSequentially() { @Test public void splitsSyncTaskIsStoppedWhenTaskResultIsDoNotRetry() throws InterruptedException { SplitsSyncTask mockTask = mock(SplitsSyncTask.class); - when(mTaskFactory.createSplitsSyncTask(false)).thenReturn(mockTask); + when(mTaskFactory.createSplitsSyncTask(false, mTaskFactory.mSplitClientConfig.forceCacheExpiration())).thenReturn(mockTask); when(mockTask.execute()).thenReturn(SplitTaskExecutionInfo.error( SplitTaskType.SPLITS_SYNC, Collections.singletonMap(SplitTaskExecutionInfo.DO_NOT_RETRY, true))); @@ -208,7 +208,7 @@ public String answer(InvocationOnMock invocation) { @Test public void splitsSyncTaskIsNotRestartedWhenTaskResultIsDoNotRetry() throws InterruptedException { SplitsSyncTask mockTask = mock(SplitsSyncTask.class); - when(mTaskFactory.createSplitsSyncTask(false)).thenReturn(mockTask); + when(mTaskFactory.createSplitsSyncTask(false, mTaskFactory.mSplitClientConfig.forceCacheExpiration())).thenReturn(mockTask); when(mockTask.execute()).thenReturn(SplitTaskExecutionInfo.error( SplitTaskType.SPLITS_SYNC, Collections.singletonMap(SplitTaskExecutionInfo.DO_NOT_RETRY, true))); diff --git a/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java b/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java index 367282052..5c75ab0a8 100644 --- a/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java +++ b/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java @@ -75,7 +75,7 @@ public void setUp() { SplitTaskFactory mTaskFactory = mock(SplitTaskFactory.class); when(mTaskFactory.createEventsRecorderTask()).thenReturn(eventsRecorderTask); - when(mTaskFactory.createSplitsSyncTask(anyBoolean())).thenReturn(mock(SplitsSyncTask.class)); + when(mTaskFactory.createSplitsSyncTask(anyBoolean(), mTaskFactory.mSplitClientConfig.forceCacheExpiration())).thenReturn(mock(SplitsSyncTask.class)); SplitTaskExecutor mTaskExecutor = mock(SplitTaskExecutor.class); SplitTaskExecutor mSingleThreadTaskExecutor = mock(SplitTaskExecutor.class);