Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cache expiration] Integrate with synchronization start flow #378

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions src/storages/AbstractSplitsCacheAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
return Promise.resolve(true);
}

/**
* Check if the splits information is already stored in cache.
* Noop, just keeping the interface. This is used by client-side implementations only.
*/
checkCache(): Promise<boolean> {
return Promise.resolve(false);
}

/**
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
* Used for SPLIT_KILL push notifications.
Expand Down
8 changes: 0 additions & 8 deletions src/storages/AbstractSplitsCacheSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,6 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {

abstract clear(): void

/**
* Check if the splits information is already stored in cache. This data can be preloaded.
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
*/
checkCache(): boolean {
return false;
}

/**
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
* Used for SPLIT_KILL push notifications.
Expand Down
11 changes: 1 addition & 10 deletions src/storages/inLocalStorage/SplitsCacheInLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
}
}

/**
* Check if the splits information is already stored in browser LocalStorage.
* In this function we could add more code to check if the data is valid.
* @override
*/
checkCache(): boolean {
return this.getChangeNumber() > -1;
}

/**
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
*
Expand All @@ -245,7 +236,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
this.updateNewFilter = true;

// if there is cache, clear it
if (this.checkCache()) this.clear();
if (this.getChangeNumber() > -1) this.clear();

} catch (e) {
this.log.error(LOG_PREFIX + e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,10 @@ test('SPLIT CACHE / LocalStorage', () => {
expect(cache.getSplit('lol1')).toEqual(null);
expect(cache.getSplit('lol2')).toEqual(somethingElse);

expect(cache.checkCache()).toBe(false); // checkCache should return false until localstorage has data.

expect(cache.getChangeNumber() === -1).toBe(true);

expect(cache.checkCache()).toBe(false); // checkCache should return false until localstorage has data.

cache.setChangeNumber(123);

expect(cache.checkCache()).toBe(true); // checkCache should return true once localstorage has data.

expect(cache.getChangeNumber() === 123).toBe(true);

});
Expand Down
5 changes: 5 additions & 0 deletions src/storages/inLocalStorage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,

// @TODO implement
validateCache() {
return splits.getChangeNumber() > -1;
},

destroy() { },

// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
Expand Down
5 changes: 1 addition & 4 deletions src/storages/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,6 @@ export interface ISplitsCacheBase {
// only for Client-Side. Returns true if the storage is not synchronized yet (getChangeNumber() === -1) or contains a FF using segments or large segments
usesSegments(): MaybeThenable<boolean>,
clear(): MaybeThenable<boolean | void>,
// should never reject or throw an exception. Instead return false by default, to avoid emitting SDK_READY_FROM_CACHE.
checkCache(): MaybeThenable<boolean>,
killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
getNamesByFlagSets(flagSets: string[]): MaybeThenable<Set<string>[]>
}
Expand All @@ -209,7 +207,6 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
trafficTypeExists(trafficType: string): boolean,
usesSegments(): boolean,
clear(): void,
checkCache(): boolean,
killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
getNamesByFlagSets(flagSets: string[]): Set<string>[]
}
Expand All @@ -226,7 +223,6 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
trafficTypeExists(trafficType: string): Promise<boolean>,
usesSegments(): Promise<boolean>,
clear(): Promise<boolean | void>,
checkCache(): Promise<boolean>,
killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
getNamesByFlagSets(flagSets: string[]): Promise<Set<string>[]>
}
Expand Down Expand Up @@ -457,6 +453,7 @@ export interface IStorageSync extends IStorageBase<
IUniqueKeysCacheSync
> {
// Defined in client-side
validateCache?: () => boolean, // @TODO support async
largeSegments?: ISegmentsCacheSync,
}

Expand Down
32 changes: 28 additions & 4 deletions src/sync/__tests__/syncManagerOnline.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.
import { syncTaskFactory } from './syncTask.mock';
import { syncManagerOnlineFactory } from '../syncManagerOnline';
import { IReadinessManager } from '../../readiness/types';
import { SDK_SPLITS_CACHE_LOADED } from '../../readiness/constants';

jest.mock('../submitters/submitterManager', () => {
return {
Expand Down Expand Up @@ -45,8 +46,10 @@ const pushManagerFactoryMock = jest.fn(() => pushManagerMock);
test('syncManagerOnline should start or not the submitter depending on user consent status', () => {
const settings = { ...fullSettings };

// @ts-ignore
const syncManager = syncManagerOnlineFactory()({ settings });
const syncManager = syncManagerOnlineFactory()({
settings, // @ts-ignore
storage: {},
});
const submitterManager = syncManager.submitterManager!;

syncManager.start();
Expand Down Expand Up @@ -95,7 +98,10 @@ test('syncManagerOnline should syncAll a single time when sync is disabled', ()

// @ts-ignore
// Test pushManager for main client
const syncManager = syncManagerOnlineFactory(() => pollingManagerMock, pushManagerFactoryMock)({ settings });
const syncManager = syncManagerOnlineFactory(() => pollingManagerMock, pushManagerFactoryMock)({
settings, // @ts-ignore
storage: { validateCache: () => false },
});

expect(pushManagerFactoryMock).not.toBeCalled();

Expand Down Expand Up @@ -161,7 +167,10 @@ test('syncManagerOnline should syncAll a single time when sync is disabled', ()
settings.sync.enabled = true;
// @ts-ignore
// pushManager instantiation control test
const testSyncManager = syncManagerOnlineFactory(() => pollingManagerMock, pushManagerFactoryMock)({ settings });
const testSyncManager = syncManagerOnlineFactory(() => pollingManagerMock, pushManagerFactoryMock)({
settings, // @ts-ignore
storage: { validateCache: () => false },
});

expect(pushManagerFactoryMock).toBeCalled();

Expand All @@ -173,3 +182,18 @@ test('syncManagerOnline should syncAll a single time when sync is disabled', ()
testSyncManager.stop();

});

test('syncManagerOnline should emit SDK_SPLITS_CACHE_LOADED if validateCache returns true', async () => {
const params = {
settings: fullSettings,
storage: { validateCache: () => true },
readiness: { splits: { emit: jest.fn() } }
}; // @ts-ignore
const syncManager = syncManagerOnlineFactory()(params);

await syncManager.start();

expect(params.readiness.splits.emit).toBeCalledWith(SDK_SPLITS_CACHE_LOADED);

syncManager.stop();
});
11 changes: 6 additions & 5 deletions src/sync/offline/syncTasks/fromObjectSyncTask.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forOwn } from '../../../utils/lang';
import { IReadinessManager } from '../../../readiness/types';
import { ISplitsCacheSync } from '../../../storages/types';
import { IStorageSync } from '../../../storages/types';
import { ISplitsParser } from '../splitsParser/types';
import { ISplit, ISplitPartial } from '../../../dtos/types';
import { syncTaskFactory } from '../../syncTask';
Expand All @@ -15,7 +15,7 @@ import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/c
*/
export function fromObjectUpdaterFactory(
splitsParser: ISplitsParser,
storage: { splits: ISplitsCacheSync },
storage: Pick<IStorageSync, 'splits' | 'validateCache'>,
readiness: IReadinessManager,
settings: ISettings,
): () => Promise<boolean> {
Expand Down Expand Up @@ -60,9 +60,10 @@ export function fromObjectUpdaterFactory(

if (startingUp) {
startingUp = false;
Promise.resolve(splitsCache.checkCache()).then(cacheReady => {
const isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
Promise.resolve().then(() => {
// Emits SDK_READY_FROM_CACHE
if (cacheReady) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
if (isCacheLoaded) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
// Emits SDK_READY
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
});
Expand All @@ -80,7 +81,7 @@ export function fromObjectUpdaterFactory(
*/
export function fromObjectSyncTaskFactory(
splitsParser: ISplitsParser,
storage: { splits: ISplitsCacheSync },
storage: Pick<IStorageSync, 'splits' | 'validateCache'>,
readiness: IReadinessManager,
settings: ISettings
): ISyncTask<[], boolean> {
Expand Down
13 changes: 2 additions & 11 deletions src/sync/polling/updaters/splitChangesUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ISplitChangesFetcher } from '../fetchers/types';
import { ISplit, ISplitChangesResponse, ISplitFiltersValidation } from '../../../dtos/types';
import { ISplitsEventEmitter } from '../../../readiness/types';
import { timeout } from '../../../utils/promise/timeout';
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
import { ILogger } from '../../../logger/types';
import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
import { startsWith } from '../../../utils/lang';
Expand Down Expand Up @@ -153,7 +153,7 @@ export function splitChangesUpdaterFactory(
*/
function _splitChangesUpdater(since: number, retry = 0): Promise<boolean> {
log.debug(SYNC_SPLITS_FETCH, [since]);
const fetcherPromise = Promise.resolve(splitUpdateNotification ?
return Promise.resolve(splitUpdateNotification ?
{ splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
splitChangesFetcher(since, noCache, till, _promiseDecorator)
)
Expand Down Expand Up @@ -200,15 +200,6 @@ export function splitChangesUpdaterFactory(
}
return false;
});

// After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
// Wrapping in a promise since checkCache can be async.
if (splitsEventEmitter && startingUp) {
Promise.resolve(splits.checkCache()).then(isCacheReady => {
if (isCacheReady) splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
});
}
return fetcherPromise;
}

let sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
Expand Down
12 changes: 9 additions & 3 deletions src/sync/syncManagerOnline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '..
import { isConsentGranted } from '../consent';
import { POLLING, STREAMING, SYNC_MODE_UPDATE } from '../utils/constants';
import { ISdkFactoryContextSync } from '../sdkFactory/types';
import { SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';

/**
* Online SyncManager factory.
Expand All @@ -28,7 +29,7 @@ export function syncManagerOnlineFactory(
*/
return function (params: ISdkFactoryContextSync): ISyncManagerCS {

const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } }, telemetryTracker } = params;
const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } }, telemetryTracker, storage, readiness } = params;

/** Polling Manager */
const pollingManager = pollingManagerFactory && pollingManagerFactory(params);
Expand Down Expand Up @@ -87,6 +88,11 @@ export function syncManagerOnlineFactory(
start() {
running = true;

if (startFirstTime) {
const isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
if (isCacheLoaded) Promise.resolve().then(() => { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
}

// start syncing splits and segments
if (pollingManager) {

Expand All @@ -96,7 +102,6 @@ export function syncManagerOnlineFactory(
// Doesn't call `syncAll` when the syncManager is resuming
if (startFirstTime) {
pollingManager.syncAll();
startFirstTime = false;
}
pushManager.start();
} else {
Expand All @@ -105,13 +110,14 @@ export function syncManagerOnlineFactory(
} else {
if (startFirstTime) {
pollingManager.syncAll();
startFirstTime = false;
}
}
}

// start periodic data recording (events, impressions, telemetry).
submitterManager.start(!isConsentGranted(settings));

startFirstTime = false;
},

/**
Expand Down
2 changes: 1 addition & 1 deletion src/utils/settingsValidation/storage/storageCS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { IStorageFactoryParams, IStorageSync } from '../../../storages/types';

export function __InLocalStorageMockFactory(params: IStorageFactoryParams): IStorageSync {
const result = InMemoryStorageCSFactory(params);
result.splits.checkCache = () => true; // to emit SDK_READY_FROM_CACHE
result.validateCache = () => true; // to emit SDK_READY_FROM_CACHE
return result;
}
__InLocalStorageMockFactory.type = STORAGE_MEMORY;
Expand Down
Loading