@splitsoftware/splitio-commons 1.5.1-rc.0 → 1.5.1-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +4 -2
- package/cjs/integrations/ga/GaToSplit.js +1 -1
- package/cjs/services/splitApi.js +4 -4
- package/cjs/sync/polling/fetchers/segmentChangesFetcher.js +5 -5
- package/cjs/sync/polling/fetchers/splitChangesFetcher.js +2 -2
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +34 -34
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +4 -3
- package/cjs/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +46 -46
- package/cjs/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +82 -64
- package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +74 -58
- package/cjs/sync/streaming/UpdateWorkers/constants.js +6 -0
- package/cjs/sync/streaming/pushManager.js +6 -7
- package/cjs/sync/syncTask.js +13 -16
- package/cjs/utils/Backoff.js +3 -2
- package/esm/integrations/ga/GaToSplit.js +1 -1
- package/esm/services/splitApi.js +4 -4
- package/esm/sync/polling/fetchers/segmentChangesFetcher.js +5 -5
- package/esm/sync/polling/fetchers/splitChangesFetcher.js +2 -2
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +34 -34
- package/esm/sync/polling/updaters/splitChangesUpdater.js +4 -3
- package/esm/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +46 -47
- package/esm/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +82 -65
- package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +74 -59
- package/esm/sync/streaming/UpdateWorkers/constants.js +3 -0
- package/esm/sync/streaming/pushManager.js +6 -7
- package/esm/sync/syncTask.js +13 -16
- package/esm/utils/Backoff.js +3 -2
- package/package.json +1 -5
- package/src/integrations/ga/GaToSplit.ts +1 -1
- package/src/integrations/ga/autoRequire.js +16 -16
- package/src/services/splitApi.ts +4 -4
- package/src/services/types.ts +2 -2
- package/src/sync/polling/fetchers/segmentChangesFetcher.ts +5 -4
- package/src/sync/polling/fetchers/splitChangesFetcher.ts +2 -1
- package/src/sync/polling/fetchers/types.ts +2 -0
- package/src/sync/polling/pollingManagerCS.ts +5 -5
- package/src/sync/polling/syncTasks/mySegmentsSyncTask.ts +2 -2
- package/src/sync/polling/types.ts +14 -6
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +4 -4
- package/src/sync/polling/updaters/segmentChangesUpdater.ts +34 -32
- package/src/sync/polling/updaters/splitChangesUpdater.ts +5 -4
- package/src/sync/streaming/SSEHandler/types.ts +0 -7
- package/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts +45 -54
- package/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts +78 -63
- package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +73 -61
- package/src/sync/streaming/UpdateWorkers/constants.ts +3 -0
- package/src/sync/streaming/UpdateWorkers/types.ts +2 -4
- package/src/sync/streaming/pushManager.ts +12 -12
- package/src/sync/streaming/types.ts +2 -2
- package/src/sync/syncTask.ts +16 -18
- package/src/utils/Backoff.ts +7 -2
- package/types/services/types.d.ts +2 -2
- package/types/sync/polling/fetchers/types.d.ts +2 -2
- package/types/sync/polling/syncTasks/mySegmentsSyncTask.d.ts +2 -2
- package/types/sync/polling/types.d.ts +11 -6
- package/types/sync/polling/updaters/segmentChangesUpdater.d.ts +1 -1
- package/types/sync/polling/updaters/splitChangesUpdater.d.ts +1 -1
- package/types/sync/streaming/SSEHandler/types.d.ts +0 -4
- package/types/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.d.ts +3 -24
- package/types/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.d.ts +3 -23
- package/types/sync/streaming/UpdateWorkers/SplitsUpdateWorker.d.ts +6 -33
- package/types/sync/streaming/UpdateWorkers/types.d.ts +1 -2
- package/types/sync/streaming/types.d.ts +2 -2
- package/types/sync/syncTask.d.ts +2 -3
- package/types/utils/Backoff.d.ts +2 -0
- package/cjs/sync/offline/LocalhostFromFile.js +0 -13
- package/cjs/sync/offline/splitsParser/splitsParserFromFile.js +0 -151
- package/esm/sync/offline/LocalhostFromFile.js +0 -9
- package/esm/sync/offline/splitsParser/splitsParserFromFile.js +0 -146
- package/src/sync/offline/LocalhostFromFile.ts +0 -12
- package/src/sync/offline/splitsParser/splitsParserFromFile.ts +0 -182
|
@@ -8,7 +8,7 @@ import { ILogger } from '../../../logger/types';
|
|
|
8
8
|
import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants';
|
|
9
9
|
import { thenable } from '../../../utils/promise/thenable';
|
|
10
10
|
|
|
11
|
-
type ISegmentChangesUpdater = (
|
|
11
|
+
type ISegmentChangesUpdater = (fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number) => Promise<boolean>
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Factory of SegmentChanges updater, a task that:
|
|
@@ -30,53 +30,56 @@ export function segmentChangesUpdaterFactory(
|
|
|
30
30
|
|
|
31
31
|
let readyOnAlreadyExistentState = true;
|
|
32
32
|
|
|
33
|
+
function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean) {
|
|
34
|
+
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing segment ${segmentName}`);
|
|
35
|
+
let sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
|
|
36
|
+
|
|
37
|
+
return sincePromise.then(since => {
|
|
38
|
+
// if fetchOnlyNew flag, avoid processing already fetched segments
|
|
39
|
+
if (fetchOnlyNew && since !== -1) return -1;
|
|
40
|
+
|
|
41
|
+
return segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
|
|
42
|
+
let changeNumber = -1;
|
|
43
|
+
const results: MaybeThenable<boolean | void>[] = [];
|
|
44
|
+
changes.forEach(x => {
|
|
45
|
+
if (x.added.length > 0) results.push(segments.addToSegment(segmentName, x.added));
|
|
46
|
+
if (x.removed.length > 0) results.push(segments.removeFromSegment(segmentName, x.removed));
|
|
47
|
+
if (x.added.length > 0 || x.removed.length > 0) {
|
|
48
|
+
results.push(segments.setChangeNumber(segmentName, x.till));
|
|
49
|
+
changeNumber = x.till;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processed ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
|
|
53
|
+
});
|
|
54
|
+
// If at least one storage operation result is a promise, join all in a single promise.
|
|
55
|
+
if (results.some(result => thenable(result))) return Promise.all(results).then(() => changeNumber);
|
|
56
|
+
return changeNumber;
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
33
60
|
/**
|
|
34
61
|
* Segments updater returns a promise that resolves with a `false` boolean value if it fails at least to fetch a segment or synchronize it with the storage.
|
|
35
62
|
* Thus, a false result doesn't imply that SDK_SEGMENTS_ARRIVED was not emitted.
|
|
36
63
|
* Returned promise will not be rejected.
|
|
37
64
|
*
|
|
38
|
-
* @param {string[] | undefined} segmentNames list of segment names to fetch. By passing `undefined` it fetches the list of segments registered at the storage
|
|
39
|
-
* @param {boolean | undefined} noCache true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
|
|
40
65
|
* @param {boolean | undefined} fetchOnlyNew if true, only fetch the segments that not exists, i.e., which `changeNumber` is equal to -1.
|
|
41
66
|
* This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE notifications.
|
|
67
|
+
* @param {string | undefined} segmentName segment name to fetch. By passing `undefined` it fetches the list of segments registered at the storage
|
|
68
|
+
* @param {boolean | undefined} noCache true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
|
|
69
|
+
* @param {number | undefined} till till target for the provided segmentName, for CDN bypass.
|
|
42
70
|
*/
|
|
43
|
-
return function segmentChangesUpdater(
|
|
71
|
+
return function segmentChangesUpdater(fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number) {
|
|
44
72
|
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Started segments update`);
|
|
45
73
|
|
|
46
74
|
// If not a segment name provided, read list of available segments names to be updated.
|
|
47
|
-
let segmentsPromise = Promise.resolve(
|
|
75
|
+
let segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());
|
|
48
76
|
|
|
49
77
|
return segmentsPromise.then(segmentNames => {
|
|
50
78
|
// Async fetchers are collected here.
|
|
51
79
|
const updaters: Promise<number>[] = [];
|
|
52
80
|
|
|
53
81
|
for (let index = 0; index < segmentNames.length; index++) {
|
|
54
|
-
|
|
55
|
-
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing segment ${segmentName}`);
|
|
56
|
-
let sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
|
|
57
|
-
|
|
58
|
-
updaters.push(sincePromise.then(since => {
|
|
59
|
-
// if fetchOnlyNew flag, avoid processing already fetched segments
|
|
60
|
-
if (fetchOnlyNew && since !== -1) return -1;
|
|
61
|
-
|
|
62
|
-
return segmentChangesFetcher(since, segmentName, noCache).then(function (changes) {
|
|
63
|
-
let changeNumber = -1;
|
|
64
|
-
const results: MaybeThenable<boolean | void>[] = [];
|
|
65
|
-
changes.forEach(x => {
|
|
66
|
-
if (x.added.length > 0) results.push(segments.addToSegment(segmentName, x.added));
|
|
67
|
-
if (x.removed.length > 0) results.push(segments.removeFromSegment(segmentName, x.removed));
|
|
68
|
-
if (x.added.length > 0 || x.removed.length > 0) {
|
|
69
|
-
results.push(segments.setChangeNumber(segmentName, x.till));
|
|
70
|
-
changeNumber = x.till;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processed ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
|
|
74
|
-
});
|
|
75
|
-
// If at least one storage operation result is a promise, join all in a single promise.
|
|
76
|
-
if (results.some(result => thenable(result))) return Promise.all(results).then(() => changeNumber);
|
|
77
|
-
return changeNumber;
|
|
78
|
-
});
|
|
79
|
-
}));
|
|
82
|
+
updaters.push(updateSegment(segmentNames[index], noCache, till, fetchOnlyNew));
|
|
80
83
|
|
|
81
84
|
}
|
|
82
85
|
|
|
@@ -103,5 +106,4 @@ export function segmentChangesUpdaterFactory(
|
|
|
103
106
|
return false;
|
|
104
107
|
});
|
|
105
108
|
};
|
|
106
|
-
|
|
107
109
|
}
|
|
@@ -8,7 +8,7 @@ import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/
|
|
|
8
8
|
import { ILogger } from '../../../logger/types';
|
|
9
9
|
import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
|
|
10
10
|
|
|
11
|
-
type ISplitChangesUpdater = (noCache?: boolean) => Promise<boolean>
|
|
11
|
+
type ISplitChangesUpdater = (noCache?: boolean, till?: number) => Promise<boolean>
|
|
12
12
|
|
|
13
13
|
// Checks that all registered segments have been fetched (changeNumber !== -1 for every segment).
|
|
14
14
|
// Returns a promise that could be rejected.
|
|
@@ -109,17 +109,18 @@ export function splitChangesUpdaterFactory(
|
|
|
109
109
|
* Returned promise will not be rejected.
|
|
110
110
|
*
|
|
111
111
|
* @param {boolean | undefined} noCache true to revalidate data to fetch
|
|
112
|
+
* @param {boolean | undefined} till query param to bypass CDN requests
|
|
112
113
|
*/
|
|
113
|
-
return function splitChangesUpdater(noCache?: boolean) {
|
|
114
|
+
return function splitChangesUpdater(noCache?: boolean, till?: number) {
|
|
114
115
|
|
|
115
116
|
/**
|
|
116
117
|
* @param {number} since current changeNumber at splitsCache
|
|
117
|
-
* @param {number} retry current number of retry
|
|
118
|
+
* @param {number} retry current number of retry attempts
|
|
118
119
|
*/
|
|
119
120
|
function _splitChangesUpdater(since: number, retry = 0): Promise<boolean> {
|
|
120
121
|
log.debug(SYNC_SPLITS_FETCH, [since]);
|
|
121
122
|
|
|
122
|
-
const fetcherPromise = splitChangesFetcher(since, noCache, _promiseDecorator)
|
|
123
|
+
const fetcherPromise = splitChangesFetcher(since, noCache, till, _promiseDecorator)
|
|
123
124
|
.then((splitChanges: ISplitChangesResponse) => {
|
|
124
125
|
startingUp = false;
|
|
125
126
|
|
|
@@ -68,10 +68,3 @@ export interface IOccupancyData {
|
|
|
68
68
|
export type INotificationData = IMySegmentsUpdateData | IMySegmentsUpdateV2Data | ISegmentUpdateData | ISplitUpdateData | ISplitKillData | IControlData | IOccupancyData
|
|
69
69
|
export type INotificationMessage = { parsedData: INotificationData, channel: string, timestamp: number, data: string }
|
|
70
70
|
export type INotificationError = Event & { parsedData?: any, message?: string }
|
|
71
|
-
|
|
72
|
-
export type SegmentsData = string[] | {
|
|
73
|
-
/* segment name */
|
|
74
|
-
name: string,
|
|
75
|
-
/* action: `true` for add, and `false` for delete */
|
|
76
|
-
add: boolean
|
|
77
|
-
}
|
|
@@ -1,71 +1,62 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IMySegmentsSyncTask, MySegmentsData } from '../../polling/types';
|
|
2
2
|
import { Backoff } from '../../../utils/Backoff';
|
|
3
3
|
import { IUpdateWorker } from './types';
|
|
4
|
-
import { SegmentsData } from '../SSEHandler/types';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* MySegmentsUpdateWorker
|
|
6
|
+
* MySegmentsUpdateWorker factory
|
|
8
7
|
*/
|
|
9
|
-
export
|
|
8
|
+
export function MySegmentsUpdateWorker(mySegmentsSyncTask: IMySegmentsSyncTask): IUpdateWorker {
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
let maxChangeNumber = 0; // keeps the maximum changeNumber among queued events
|
|
11
|
+
let currentChangeNumber = -1;
|
|
12
|
+
let handleNewEvent = false;
|
|
13
|
+
let isHandlingEvent: boolean;
|
|
14
|
+
let _segmentsData: MySegmentsData | undefined; // keeps the segmentsData (if included in notification payload) from the queued event with maximum changeNumber
|
|
15
|
+
const backoff = new Backoff(__handleMySegmentsUpdateCall);
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.maxChangeNumber = 0; // keeps the maximum changeNumber among queued events
|
|
24
|
-
this.handleNewEvent = false;
|
|
25
|
-
this.segmentsData = undefined; // keeps the segmentsData (if included in notification payload) from the queued event with maximum changeNumber
|
|
26
|
-
this.currentChangeNumber = -1; // @TODO: remove once `/mySegments` endpoint provides the changeNumber
|
|
27
|
-
this.put = this.put.bind(this);
|
|
28
|
-
this.__handleMySegmentsUpdateCall = this.__handleMySegmentsUpdateCall.bind(this);
|
|
29
|
-
this.backoff = new Backoff(this.__handleMySegmentsUpdateCall);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Private method
|
|
33
|
-
// Precondition: this.mySegmentsSyncTask.isSynchronizingMySegments === false
|
|
34
|
-
__handleMySegmentsUpdateCall() {
|
|
35
|
-
if (this.maxChangeNumber > this.currentChangeNumber) {
|
|
36
|
-
this.handleNewEvent = false;
|
|
37
|
-
const currentMaxChangeNumber = this.maxChangeNumber;
|
|
17
|
+
function __handleMySegmentsUpdateCall() {
|
|
18
|
+
isHandlingEvent = true;
|
|
19
|
+
if (maxChangeNumber > currentChangeNumber) {
|
|
20
|
+
handleNewEvent = false;
|
|
21
|
+
const currentMaxChangeNumber = maxChangeNumber;
|
|
38
22
|
|
|
39
23
|
// fetch mySegments revalidating data if cached
|
|
40
|
-
|
|
24
|
+
mySegmentsSyncTask.execute(_segmentsData, true).then((result) => {
|
|
25
|
+
if (!isHandlingEvent) return; // halt if `stop` has been called
|
|
41
26
|
if (result !== false) // Unlike `Splits|SegmentsUpdateWorker`, we cannot use `mySegmentsCache.getChangeNumber` since `/mySegments` endpoint doesn't provide this value.
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
27
|
+
currentChangeNumber = Math.max(currentChangeNumber, currentMaxChangeNumber); // use `currentMaxChangeNumber`, in case that `maxChangeNumber` was updated during fetch.
|
|
28
|
+
if (handleNewEvent) {
|
|
29
|
+
__handleMySegmentsUpdateCall();
|
|
45
30
|
} else {
|
|
46
|
-
|
|
31
|
+
backoff.scheduleCall();
|
|
47
32
|
}
|
|
48
33
|
});
|
|
34
|
+
} else {
|
|
35
|
+
isHandlingEvent = false;
|
|
49
36
|
}
|
|
50
37
|
}
|
|
51
38
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
39
|
+
return {
|
|
40
|
+
/**
|
|
41
|
+
* Invoked by NotificationProcessor on MY_SEGMENTS_UPDATE event
|
|
42
|
+
*
|
|
43
|
+
* @param {number} changeNumber change number of the MY_SEGMENTS_UPDATE notification
|
|
44
|
+
* @param {SegmentsData | undefined} segmentsData might be undefined
|
|
45
|
+
*/
|
|
46
|
+
put(changeNumber: number, segmentsData?: MySegmentsData) {
|
|
47
|
+
if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber) return;
|
|
48
|
+
|
|
49
|
+
maxChangeNumber = changeNumber;
|
|
50
|
+
handleNewEvent = true;
|
|
51
|
+
_segmentsData = segmentsData;
|
|
52
|
+
|
|
53
|
+
if (backoff.timeoutID || !isHandlingEvent) __handleMySegmentsUpdateCall();
|
|
54
|
+
backoff.reset();
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
stop() {
|
|
58
|
+
isHandlingEvent = false;
|
|
59
|
+
backoff.reset();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
71
62
|
}
|
|
@@ -1,84 +1,99 @@
|
|
|
1
|
+
import { ILogger } from '../../../logger/types';
|
|
1
2
|
import { ISegmentsCacheSync } from '../../../storages/types';
|
|
2
3
|
import { Backoff } from '../../../utils/Backoff';
|
|
3
4
|
import { ISegmentsSyncTask } from '../../polling/types';
|
|
4
5
|
import { ISegmentUpdateData } from '../SSEHandler/types';
|
|
6
|
+
import { FETCH_BACKOFF_BASE, FETCH_BACKOFF_MAX_RETRIES, FETCH_BACKOFF_MAX_WAIT } from './constants';
|
|
5
7
|
import { IUpdateWorker } from './types';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
|
-
*
|
|
10
|
+
* SegmentsUpdateWorker factory
|
|
9
11
|
*/
|
|
10
|
-
export
|
|
12
|
+
export function SegmentsUpdateWorker(log: ILogger, segmentsSyncTask: ISegmentsSyncTask, segmentsCache: ISegmentsCacheSync): IUpdateWorker {
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
// Handles retries with CDN bypass per segment name
|
|
15
|
+
function SegmentUpdateWorker(segment: string) {
|
|
16
|
+
let maxChangeNumber = 0;
|
|
17
|
+
let handleNewEvent = false;
|
|
18
|
+
let isHandlingEvent: boolean;
|
|
19
|
+
let cdnBypass: boolean;
|
|
20
|
+
const backoff = new Backoff(__handleSegmentUpdateCall, FETCH_BACKOFF_BASE, FETCH_BACKOFF_MAX_WAIT);
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
constructor(segmentsSyncTask: ISegmentsSyncTask, segmentsCache: ISegmentsCacheSync) {
|
|
23
|
-
this.segmentsCache = segmentsCache;
|
|
24
|
-
this.segmentsSyncTask = segmentsSyncTask;
|
|
25
|
-
this.maxChangeNumbers = {};
|
|
26
|
-
this.handleNewEvent = false;
|
|
27
|
-
this.put = this.put.bind(this);
|
|
28
|
-
this.__handleSegmentUpdateCall = this.__handleSegmentUpdateCall.bind(this);
|
|
29
|
-
this.backoff = new Backoff(this.__handleSegmentUpdateCall);
|
|
30
|
-
}
|
|
22
|
+
function __handleSegmentUpdateCall() {
|
|
23
|
+
isHandlingEvent = true;
|
|
24
|
+
if (maxChangeNumber > segmentsCache.getChangeNumber(segment)) {
|
|
25
|
+
handleNewEvent = false;
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (segmentsToFetch.length > 0) {
|
|
40
|
-
this.handleNewEvent = false;
|
|
41
|
-
const currentMaxChangeNumbers = segmentsToFetch.map(segmentToFetch => this.maxChangeNumbers[segmentToFetch]);
|
|
27
|
+
// fetch segments revalidating data if cached
|
|
28
|
+
segmentsSyncTask.execute(false, segment, true, cdnBypass ? maxChangeNumber : undefined).then(() => {
|
|
29
|
+
if (!isHandlingEvent) return; // halt if `stop` has been called
|
|
30
|
+
if (handleNewEvent) {
|
|
31
|
+
__handleSegmentUpdateCall();
|
|
32
|
+
} else {
|
|
33
|
+
const attempts = backoff.attempts + 1;
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
segmentsToFetch.forEach((fetchedSegment, index) => {
|
|
49
|
-
if (this.maxChangeNumbers[fetchedSegment] === currentMaxChangeNumbers[index]) this.maxChangeNumbers[fetchedSegment] = -1;
|
|
50
|
-
});
|
|
51
|
-
} else {
|
|
52
|
-
// recursive invocation with backoff if there was some error
|
|
53
|
-
this.backoff.scheduleCall();
|
|
54
|
-
}
|
|
35
|
+
if (maxChangeNumber <= segmentsCache.getChangeNumber(segment)) {
|
|
36
|
+
log.debug(`Refresh completed${cdnBypass ? ' bypassing the CDN' : ''} in ${attempts} attempts.`);
|
|
37
|
+
isHandlingEvent = false;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
55
40
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
41
|
+
if (attempts < FETCH_BACKOFF_MAX_RETRIES) {
|
|
42
|
+
backoff.scheduleCall();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
63
45
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
46
|
+
if (cdnBypass) {
|
|
47
|
+
log.debug(`No changes fetched after ${attempts} attempts with CDN bypassed.`);
|
|
48
|
+
isHandlingEvent = false;
|
|
49
|
+
} else {
|
|
50
|
+
backoff.reset();
|
|
51
|
+
cdnBypass = true;
|
|
52
|
+
__handleSegmentUpdateCall();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
isHandlingEvent = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
72
60
|
|
|
73
|
-
|
|
61
|
+
return {
|
|
62
|
+
put(changeNumber: number) {
|
|
63
|
+
const currentChangeNumber = segmentsCache.getChangeNumber(segment);
|
|
74
64
|
|
|
75
|
-
|
|
76
|
-
this.handleNewEvent = true;
|
|
77
|
-
this.backoff.reset();
|
|
65
|
+
if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber) return;
|
|
78
66
|
|
|
79
|
-
|
|
67
|
+
maxChangeNumber = changeNumber;
|
|
68
|
+
handleNewEvent = true;
|
|
69
|
+
cdnBypass = false;
|
|
80
70
|
|
|
81
|
-
|
|
71
|
+
if (backoff.timeoutID || !isHandlingEvent) __handleSegmentUpdateCall();
|
|
72
|
+
backoff.reset();
|
|
73
|
+
},
|
|
74
|
+
stop() {
|
|
75
|
+
isHandlingEvent = false;
|
|
76
|
+
backoff.reset();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
82
79
|
}
|
|
83
80
|
|
|
81
|
+
const segments: Record<string, ReturnType<typeof SegmentUpdateWorker>> = {};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
/**
|
|
85
|
+
* Invoked by NotificationProcessor on SEGMENT_UPDATE event
|
|
86
|
+
*
|
|
87
|
+
* @param {number} changeNumber change number of the SEGMENT_UPDATE notification
|
|
88
|
+
* @param {string} segmentName segment name of the SEGMENT_UPDATE notification
|
|
89
|
+
*/
|
|
90
|
+
put({ changeNumber, segmentName }: ISegmentUpdateData) {
|
|
91
|
+
if (!segments[segmentName]) segments[segmentName] = SegmentUpdateWorker(segmentName);
|
|
92
|
+
segments[segmentName].put(changeNumber);
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
stop() {
|
|
96
|
+
Object.keys(segments).forEach(segmentName => segments[segmentName].stop());
|
|
97
|
+
}
|
|
98
|
+
};
|
|
84
99
|
}
|
|
@@ -1,58 +1,63 @@
|
|
|
1
|
+
import { ILogger } from '../../../logger/types';
|
|
1
2
|
import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
|
|
2
3
|
import { ISplitsEventEmitter } from '../../../readiness/types';
|
|
3
4
|
import { ISplitsCacheSync } from '../../../storages/types';
|
|
4
5
|
import { Backoff } from '../../../utils/Backoff';
|
|
5
6
|
import { ISegmentsSyncTask, ISplitsSyncTask } from '../../polling/types';
|
|
6
7
|
import { ISplitKillData, ISplitUpdateData } from '../SSEHandler/types';
|
|
8
|
+
import { FETCH_BACKOFF_BASE, FETCH_BACKOFF_MAX_WAIT, FETCH_BACKOFF_MAX_RETRIES } from './constants';
|
|
7
9
|
import { IUpdateWorker } from './types';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
|
-
* SplitsUpdateWorker
|
|
12
|
+
* SplitsUpdateWorker factory
|
|
11
13
|
*/
|
|
12
|
-
export
|
|
14
|
+
export function SplitsUpdateWorker(log: ILogger, splitsCache: ISplitsCacheSync, splitsSyncTask: ISplitsSyncTask, splitsEventEmitter: ISplitsEventEmitter, segmentsSyncTask?: ISegmentsSyncTask): IUpdateWorker & { killSplit(event: ISplitKillData): void } {
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
private handleNewEvent: boolean;
|
|
20
|
-
readonly backoff: Backoff;
|
|
16
|
+
let maxChangeNumber = 0;
|
|
17
|
+
let handleNewEvent = false;
|
|
18
|
+
let isHandlingEvent: boolean;
|
|
19
|
+
let cdnBypass: boolean;
|
|
20
|
+
const backoff = new Backoff(__handleSplitUpdateCall, FETCH_BACKOFF_BASE, FETCH_BACKOFF_MAX_WAIT);
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
constructor(splitsCache: ISplitsCacheSync, splitsSyncTask: ISplitsSyncTask, splitsEventEmitter: ISplitsEventEmitter, segmentsSyncTask?: ISegmentsSyncTask) {
|
|
28
|
-
this.splitsCache = splitsCache;
|
|
29
|
-
this.splitsSyncTask = splitsSyncTask;
|
|
30
|
-
this.splitsEventEmitter = splitsEventEmitter;
|
|
31
|
-
this.segmentsSyncTask = segmentsSyncTask;
|
|
32
|
-
this.maxChangeNumber = 0;
|
|
33
|
-
this.handleNewEvent = false;
|
|
34
|
-
this.put = this.put.bind(this);
|
|
35
|
-
this.killSplit = this.killSplit.bind(this);
|
|
36
|
-
this.__handleSplitUpdateCall = this.__handleSplitUpdateCall.bind(this);
|
|
37
|
-
this.backoff = new Backoff(this.__handleSplitUpdateCall);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Private method
|
|
41
|
-
// Preconditions: this.splitsSyncTask.isSynchronizingSplits === false
|
|
42
|
-
__handleSplitUpdateCall() {
|
|
43
|
-
if (this.maxChangeNumber > this.splitsCache.getChangeNumber()) {
|
|
44
|
-
this.handleNewEvent = false;
|
|
22
|
+
function __handleSplitUpdateCall() {
|
|
23
|
+
isHandlingEvent = true;
|
|
24
|
+
if (maxChangeNumber > splitsCache.getChangeNumber()) {
|
|
25
|
+
handleNewEvent = false;
|
|
45
26
|
|
|
46
27
|
// fetch splits revalidating data if cached
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
28
|
+
splitsSyncTask.execute(true, cdnBypass ? maxChangeNumber : undefined).then(() => {
|
|
29
|
+
if (!isHandlingEvent) return; // halt if `stop` has been called
|
|
30
|
+
if (handleNewEvent) {
|
|
31
|
+
__handleSplitUpdateCall();
|
|
50
32
|
} else {
|
|
51
33
|
// fetch new registered segments for server-side API. Not retrying on error
|
|
52
|
-
if (
|
|
53
|
-
|
|
34
|
+
if (segmentsSyncTask) segmentsSyncTask.execute(true);
|
|
35
|
+
|
|
36
|
+
const attempts = backoff.attempts + 1;
|
|
37
|
+
|
|
38
|
+
if (maxChangeNumber <= splitsCache.getChangeNumber()) {
|
|
39
|
+
log.debug(`Refresh completed${cdnBypass ? ' bypassing the CDN' : ''} in ${attempts} attempts.`);
|
|
40
|
+
isHandlingEvent = false;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (attempts < FETCH_BACKOFF_MAX_RETRIES) {
|
|
45
|
+
backoff.scheduleCall();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (cdnBypass) {
|
|
50
|
+
log.debug(`No changes fetched after ${attempts} attempts with CDN bypassed.`);
|
|
51
|
+
isHandlingEvent = false;
|
|
52
|
+
} else {
|
|
53
|
+
backoff.reset();
|
|
54
|
+
cdnBypass = true;
|
|
55
|
+
__handleSplitUpdateCall();
|
|
56
|
+
}
|
|
54
57
|
}
|
|
55
58
|
});
|
|
59
|
+
} else {
|
|
60
|
+
isHandlingEvent = false;
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -61,34 +66,41 @@ export class SplitsUpdateWorker implements IUpdateWorker {
|
|
|
61
66
|
*
|
|
62
67
|
* @param {number} changeNumber change number of the SPLIT_UPDATE notification
|
|
63
68
|
*/
|
|
64
|
-
put({ changeNumber }: Pick<ISplitUpdateData, 'changeNumber'>) {
|
|
65
|
-
const currentChangeNumber =
|
|
66
|
-
|
|
67
|
-
if (changeNumber <= currentChangeNumber || changeNumber <= this.maxChangeNumber) return;
|
|
69
|
+
function put({ changeNumber }: Pick<ISplitUpdateData, 'changeNumber'>) {
|
|
70
|
+
const currentChangeNumber = splitsCache.getChangeNumber();
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
this.handleNewEvent = true;
|
|
71
|
-
this.backoff.reset();
|
|
72
|
+
if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber) return;
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
maxChangeNumber = changeNumber;
|
|
75
|
+
handleNewEvent = true;
|
|
76
|
+
cdnBypass = false;
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
if (backoff.timeoutID || !isHandlingEvent) __handleSplitUpdateCall();
|
|
79
|
+
backoff.reset();
|
|
76
80
|
}
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
*
|
|
81
|
-
* @param {number} changeNumber change number of the SPLIT_UPDATE notification
|
|
82
|
-
* @param {string} splitName name of split to kill
|
|
83
|
-
* @param {string} defaultTreatment default treatment value
|
|
84
|
-
*/
|
|
85
|
-
killSplit({ changeNumber, splitName, defaultTreatment }: ISplitKillData) {
|
|
86
|
-
if (this.splitsCache.killLocally(splitName, defaultTreatment, changeNumber)) {
|
|
87
|
-
// trigger an SDK_UPDATE if Split was killed locally
|
|
88
|
-
this.splitsEventEmitter.emit(SDK_SPLITS_ARRIVED, true);
|
|
89
|
-
}
|
|
90
|
-
// queues the SplitChanges fetch (only if changeNumber is newer)
|
|
91
|
-
this.put({ changeNumber });
|
|
92
|
-
}
|
|
82
|
+
return {
|
|
83
|
+
put,
|
|
93
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Invoked by NotificationProcessor on SPLIT_KILL event
|
|
87
|
+
*
|
|
88
|
+
* @param {number} changeNumber change number of the SPLIT_UPDATE notification
|
|
89
|
+
* @param {string} splitName name of split to kill
|
|
90
|
+
* @param {string} defaultTreatment default treatment value
|
|
91
|
+
*/
|
|
92
|
+
killSplit({ changeNumber, splitName, defaultTreatment }: ISplitKillData) {
|
|
93
|
+
if (splitsCache.killLocally(splitName, defaultTreatment, changeNumber)) {
|
|
94
|
+
// trigger an SDK_UPDATE if Split was killed locally
|
|
95
|
+
splitsEventEmitter.emit(SDK_SPLITS_ARRIVED, true);
|
|
96
|
+
}
|
|
97
|
+
// queues the SplitChanges fetch (only if changeNumber is newer)
|
|
98
|
+
put({ changeNumber });
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
stop() {
|
|
102
|
+
isHandlingEvent = false;
|
|
103
|
+
backoff.reset();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
94
106
|
}
|