-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
314 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package io.split.android.client.service.synchronizer; | ||
|
||
import io.split.android.client.service.executor.SplitTaskExecutionListener; | ||
|
||
interface RolloutCacheManager { | ||
|
||
void validateCache(SplitTaskExecutionListener listener); | ||
} |
29 changes: 29 additions & 0 deletions
29
src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package io.split.android.client.service.synchronizer; | ||
|
||
import androidx.annotation.VisibleForTesting; | ||
|
||
import io.split.android.client.SplitClientConfig; | ||
|
||
class RolloutCacheManagerConfig { | ||
|
||
private final long mCacheExpirationInDays; | ||
private final boolean mClearOnInit; | ||
|
||
@VisibleForTesting | ||
RolloutCacheManagerConfig(long cacheExpirationInDays, boolean clearOnInit) { | ||
mCacheExpirationInDays = cacheExpirationInDays; | ||
mClearOnInit = clearOnInit; | ||
} | ||
|
||
static RolloutCacheManagerConfig from(SplitClientConfig splitClientConfig) { | ||
return new RolloutCacheManagerConfig(splitClientConfig.cacheExpirationInSeconds(), splitClientConfig.clearOnInit()); | ||
} | ||
|
||
long getCacheExpirationInDays() { | ||
return mCacheExpirationInDays; | ||
} | ||
|
||
boolean isClearOnInit() { | ||
return mClearOnInit; | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package io.split.android.client.service.synchronizer; | ||
|
||
import static io.split.android.client.utils.Utils.checkNotNull; | ||
|
||
import androidx.annotation.NonNull; | ||
import androidx.annotation.VisibleForTesting; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
import io.split.android.client.SplitClientConfig; | ||
import io.split.android.client.service.executor.SplitTask; | ||
import io.split.android.client.service.executor.SplitTaskExecutionInfo; | ||
import io.split.android.client.service.executor.SplitTaskExecutionListener; | ||
import io.split.android.client.service.executor.SplitTaskExecutor; | ||
import io.split.android.client.service.executor.SplitTaskType; | ||
import io.split.android.client.storage.RolloutDefinitionsCache; | ||
import io.split.android.client.storage.common.SplitStorageContainer; | ||
import io.split.android.client.storage.general.GeneralInfoStorage; | ||
import io.split.android.client.utils.logger.Logger; | ||
|
||
public class RolloutCacheManagerImpl implements RolloutCacheManager, SplitTask { | ||
|
||
public static final int MIN_CACHE_CLEAR_DAYS = 1; // TODO | ||
|
||
@NonNull | ||
private final GeneralInfoStorage mGeneralInfoStorage; | ||
@NonNull | ||
private final RolloutCacheManagerConfig mConfig; | ||
@NonNull | ||
private final SplitTaskExecutor mTaskExecutor; | ||
@NonNull | ||
private final RolloutDefinitionsCache[] mStorages; | ||
|
||
public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitTaskExecutor splitTaskExecutor, @NonNull SplitStorageContainer storageContainer) { | ||
this(storageContainer.getGeneralInfoStorage(), | ||
RolloutCacheManagerConfig.from(splitClientConfig), | ||
splitTaskExecutor, | ||
storageContainer.getSplitsStorage(), | ||
storageContainer.getMySegmentsStorageContainer(), | ||
storageContainer.getMyLargeSegmentsStorageContainer()); | ||
} | ||
|
||
@VisibleForTesting | ||
RolloutCacheManagerImpl(@NonNull GeneralInfoStorage generalInfoStorage, @NonNull RolloutCacheManagerConfig config, @NonNull SplitTaskExecutor splitTaskExecutor, @NonNull RolloutDefinitionsCache... storages) { | ||
mGeneralInfoStorage = checkNotNull(generalInfoStorage); | ||
mStorages = checkNotNull(storages); | ||
mConfig = checkNotNull(config); | ||
mTaskExecutor = checkNotNull(splitTaskExecutor); | ||
} | ||
|
||
@Override | ||
public void validateCache(SplitTaskExecutionListener listener) { | ||
mTaskExecutor.submit(this, listener); | ||
} | ||
|
||
@NonNull | ||
@Override | ||
public SplitTaskExecutionInfo execute() { | ||
try { | ||
boolean expired = validateExpiration(); | ||
if (expired) { | ||
clear(); | ||
} | ||
} catch (Exception e) { | ||
Logger.e("Error occurred validating cache: " + e.getMessage()); | ||
|
||
return SplitTaskExecutionInfo.error(SplitTaskType.GENERIC_TASK); | ||
} | ||
return SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK); | ||
} | ||
|
||
private boolean validateExpiration() { | ||
// calculate elapsed time since last update | ||
long lastUpdateTimestamp = mGeneralInfoStorage.getSplitsUpdateTimestamp(); | ||
long daysSinceLastUpdate = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUpdateTimestamp); | ||
|
||
if (daysSinceLastUpdate > mConfig.getCacheExpirationInDays()) { | ||
Logger.v("Clearing rollout definitions cache due to expiration"); | ||
return true; | ||
} else if (mConfig.isClearOnInit()) { | ||
long lastCacheClearTimestamp = mGeneralInfoStorage.getRolloutCacheLastClearTimestamp(); | ||
long daysSinceCacheClear = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastCacheClearTimestamp); | ||
|
||
// don't clear too soon | ||
if (daysSinceCacheClear > MIN_CACHE_CLEAR_DAYS) { | ||
Logger.v("Forcing rollout definitions cache clear"); | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private void clear() { | ||
for (RolloutDefinitionsCache storage : mStorages) { | ||
storage.clear(); | ||
} | ||
mGeneralInfoStorage.setRolloutCacheLastClearTimestamp(System.currentTimeMillis()); | ||
Logger.v("Rollout definitions cache cleared"); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
src/main/java/io/split/android/client/storage/RolloutDefinitionsCache.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package io.split.android.client.storage; | ||
|
||
public interface RolloutDefinitionsCache { | ||
|
||
void clear(); | ||
} |
8 changes: 2 additions & 6 deletions
8
src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,16 @@ | ||
package io.split.android.client.storage.mysegments; | ||
|
||
import androidx.annotation.VisibleForTesting; | ||
|
||
import java.util.Set; | ||
|
||
import io.split.android.client.dtos.SegmentsChange; | ||
import io.split.android.client.storage.RolloutDefinitionsCache; | ||
|
||
public interface MySegmentsStorage { | ||
public interface MySegmentsStorage extends RolloutDefinitionsCache { | ||
void loadLocal(); | ||
|
||
Set<String> getAll(); | ||
|
||
void set(SegmentsChange segmentsChange); | ||
|
||
long getChangeNumber(); | ||
|
||
@VisibleForTesting | ||
void clear(); | ||
} |
4 changes: 3 additions & 1 deletion
4
src/main/java/io/split/android/client/storage/mysegments/MySegmentsStorageContainer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
145 changes: 145 additions & 0 deletions
145
src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package io.split.android.client.service.synchronizer | ||
|
||
import io.split.android.client.service.executor.SplitTaskExecutionListener | ||
import io.split.android.client.service.executor.SplitTaskExecutor | ||
import io.split.android.client.storage.RolloutDefinitionsCache | ||
import io.split.android.client.storage.general.GeneralInfoStorage | ||
import io.split.android.fake.SplitTaskExecutorStub | ||
import org.junit.Before | ||
import org.junit.Test | ||
import org.mockito.ArgumentMatchers.any | ||
import org.mockito.ArgumentMatchers.anyLong | ||
import org.mockito.ArgumentMatchers.argThat | ||
import org.mockito.Mockito.longThat | ||
import org.mockito.Mockito.mock | ||
import org.mockito.Mockito.times | ||
import org.mockito.Mockito.verify | ||
import org.mockito.Mockito.`when` | ||
import java.util.concurrent.TimeUnit | ||
|
||
class RolloutCacheManagerTest { | ||
|
||
private lateinit var mRolloutCacheManager: RolloutCacheManager | ||
private lateinit var mGeneralInfoStorage: GeneralInfoStorage | ||
private lateinit var mSplitTaskExecutor: SplitTaskExecutor | ||
private lateinit var mSplitsCache: RolloutDefinitionsCache | ||
private lateinit var mSegmentsCache: RolloutDefinitionsCache | ||
|
||
@Before | ||
fun setup() { | ||
mGeneralInfoStorage = mock(GeneralInfoStorage::class.java) | ||
mSplitTaskExecutor = SplitTaskExecutorStub() | ||
mSplitsCache = mock(RolloutDefinitionsCache::class.java) | ||
mSegmentsCache = mock(RolloutDefinitionsCache::class.java) | ||
} | ||
|
||
@Test | ||
fun `validateCache calls listener`() { | ||
mRolloutCacheManager = getCacheManager(10L, false) | ||
|
||
val listener = mock(SplitTaskExecutionListener::class.java) | ||
mRolloutCacheManager.validateCache(listener) | ||
|
||
verify(listener).taskExecuted(any()) | ||
} | ||
|
||
@Test | ||
fun `validateCache calls clear on storages when expiration is surpassed`() { | ||
val mockedTimestamp = createMockedTimestamp(10L) | ||
`when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) | ||
mRolloutCacheManager = getCacheManager(9L, false) | ||
|
||
mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) | ||
|
||
verify(mSplitsCache).clear() | ||
verify(mSegmentsCache).clear() | ||
} | ||
|
||
@Test | ||
fun `validateCache does not call clear on storages when expiration is not surpassed and clearOnInit is false`() { | ||
val mockedTimestamp = createMockedTimestamp(1L) | ||
`when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) | ||
mRolloutCacheManager = getCacheManager(10L, false) | ||
|
||
mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) | ||
|
||
verify(mSplitsCache, times(0)).clear() | ||
verify(mSegmentsCache, times(0)).clear() | ||
} | ||
|
||
@Test | ||
fun `validateCache calls clear on storages when expiration is not surpassed and clearOnInit is true`() { | ||
val mockedTimestamp = createMockedTimestamp(1L) | ||
`when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) | ||
mRolloutCacheManager = getCacheManager(10L, true) | ||
|
||
mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) | ||
|
||
verify(mSplitsCache).clear() | ||
verify(mSegmentsCache).clear() | ||
} | ||
|
||
@Test | ||
fun `validateCache calls clear on storage only once when executed consecutively`() { | ||
val mockedTimestamp = createMockedTimestamp(1L) | ||
`when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) | ||
`when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) | ||
mRolloutCacheManager = getCacheManager(10L, true) | ||
|
||
mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) | ||
mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) | ||
|
||
verify(mSplitsCache, times(1)).clear() | ||
verify(mSegmentsCache, times(1)).clear() | ||
} | ||
|
||
@Test | ||
fun `exception during clear still calls listener`() { | ||
val mockedTimestamp = createMockedTimestamp(1L) | ||
`when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) | ||
`when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) | ||
mRolloutCacheManager = getCacheManager(10L, true) | ||
|
||
val listener = mock(SplitTaskExecutionListener::class.java) | ||
`when`(mSplitsCache.clear()).thenThrow(RuntimeException("Exception during clear")) | ||
|
||
mRolloutCacheManager.validateCache(listener) | ||
|
||
verify(listener).taskExecuted(any()) | ||
} | ||
|
||
@Test | ||
fun `validateCache updates last clear timestamp when storages are cleared`() { | ||
val mockedTimestamp = createMockedTimestamp(1L) | ||
`when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) | ||
`when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) | ||
mRolloutCacheManager = getCacheManager(10L, true) | ||
|
||
mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) | ||
|
||
verify(mGeneralInfoStorage).setRolloutCacheLastClearTimestamp(longThat { it > 0 }) | ||
} | ||
|
||
@Test | ||
fun `validateCache does not update last clear timestamp when storages are not cleared`() { | ||
val mockedTimestamp = createMockedTimestamp(1L) | ||
`when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) | ||
`when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) | ||
mRolloutCacheManager = getCacheManager(10L, false) | ||
|
||
mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) | ||
|
||
verify(mGeneralInfoStorage, times(0)).setRolloutCacheLastClearTimestamp(anyLong()) | ||
} | ||
|
||
private fun getCacheManager(expiration: Long, clearOnInit: Boolean): RolloutCacheManager { | ||
return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheManagerConfig(expiration, clearOnInit), mSplitTaskExecutor, mSplitsCache, mSegmentsCache) | ||
} | ||
|
||
private fun createMockedTimestamp(period: Long): Long { | ||
val currentTimeMillis = System.currentTimeMillis() | ||
val mockedTimestamp = | ||
TimeUnit.DAYS.toMillis(TimeUnit.MILLISECONDS.toDays(currentTimeMillis) - period) | ||
return mockedTimestamp | ||
} | ||
} |