@splitsoftware/splitio-commons 2.0.1-rc.0 → 2.0.1-rc.1
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 +2 -2
- package/cjs/readiness/readinessManager.js +18 -16
- package/cjs/sdkFactory/index.js +0 -1
- package/cjs/storages/inLocalStorage/index.js +18 -3
- package/cjs/storages/inMemory/InMemoryStorage.js +9 -1
- package/cjs/storages/inMemory/InMemoryStorageCS.js +16 -2
- package/cjs/storages/inMemory/SegmentsCacheInMemory.js +1 -1
- package/cjs/storages/inRedis/SegmentsCacheInRedis.js +2 -2
- package/cjs/storages/pluggable/SegmentsCachePluggable.js +2 -2
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +2 -2
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -1
- package/cjs/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +3 -3
- package/esm/readiness/readinessManager.js +18 -16
- package/esm/sdkFactory/index.js +0 -1
- package/esm/storages/inLocalStorage/index.js +18 -3
- package/esm/storages/inMemory/InMemoryStorage.js +9 -1
- package/esm/storages/inMemory/InMemoryStorageCS.js +16 -2
- package/esm/storages/inMemory/SegmentsCacheInMemory.js +1 -1
- package/esm/storages/inRedis/SegmentsCacheInRedis.js +2 -2
- package/esm/storages/pluggable/SegmentsCachePluggable.js +2 -2
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +2 -2
- package/esm/sync/polling/updaters/splitChangesUpdater.js +1 -1
- package/esm/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +3 -3
- package/package.json +1 -1
- package/src/readiness/readinessManager.ts +16 -15
- package/src/sdkFactory/index.ts +0 -1
- package/src/storages/AbstractMySegmentsCacheSync.ts +1 -1
- package/src/storages/inLocalStorage/index.ts +17 -3
- package/src/storages/inMemory/InMemoryStorage.ts +9 -1
- package/src/storages/inMemory/InMemoryStorageCS.ts +16 -2
- package/src/storages/inMemory/SegmentsCacheInMemory.ts +1 -1
- package/src/storages/inRedis/SegmentsCacheInRedis.ts +2 -2
- package/src/storages/pluggable/SegmentsCachePluggable.ts +2 -2
- package/src/storages/types.ts +3 -3
- package/src/sync/polling/updaters/segmentChangesUpdater.ts +2 -2
- package/src/sync/polling/updaters/splitChangesUpdater.ts +1 -1
- package/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts +5 -4
- package/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts +3 -3
package/CHANGES.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
2.0.1 (
|
|
2
|
-
-
|
|
1
|
+
2.0.1 (November 25, 2024)
|
|
2
|
+
- Bugfixing - Fixed an issue with the SDK_UPDATE event on server-side, where it was not being emitted if there was an empty segment and the SDK received a feature flag update notification.
|
|
3
3
|
|
|
4
4
|
2.0.0 (November 1, 2024)
|
|
5
5
|
- Added support for targeting rules based on large segments.
|
|
@@ -28,7 +28,7 @@ function segmentsEventEmitterFactory(EventEmitter) {
|
|
|
28
28
|
/**
|
|
29
29
|
* Factory of readiness manager, which handles the ready / update event propagation.
|
|
30
30
|
*/
|
|
31
|
-
function readinessManagerFactory(EventEmitter, settings, splits
|
|
31
|
+
function readinessManagerFactory(EventEmitter, settings, splits) {
|
|
32
32
|
if (splits === void 0) { splits = splitsEventEmitterFactory(EventEmitter); }
|
|
33
33
|
var readyTimeout = settings.startup.readyTimeout;
|
|
34
34
|
var segments = segmentsEventEmitterFactory(EventEmitter);
|
|
@@ -54,24 +54,22 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
|
|
|
54
54
|
syncLastUpdate();
|
|
55
55
|
gate.emit(constants_1.SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
|
|
56
56
|
}
|
|
57
|
+
var readyTimeoutId;
|
|
58
|
+
if (readyTimeout > 0) {
|
|
59
|
+
if (splits.hasInit)
|
|
60
|
+
readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
61
|
+
else
|
|
62
|
+
splits.initCallbacks.push(function () { readyTimeoutId = setTimeout(timeout, readyTimeout); });
|
|
63
|
+
}
|
|
57
64
|
// emit SDK_READY and SDK_UPDATE
|
|
58
65
|
var isReady = false;
|
|
59
66
|
splits.on(constants_1.SDK_SPLITS_ARRIVED, checkIsReadyOrUpdate);
|
|
60
67
|
segments.on(constants_1.SDK_SEGMENTS_ARRIVED, checkIsReadyOrUpdate);
|
|
61
68
|
var isDestroyed = false;
|
|
62
|
-
var readyTimeoutId;
|
|
63
|
-
function __init() {
|
|
64
|
-
isDestroyed = false;
|
|
65
|
-
if (readyTimeout > 0 && !isReady)
|
|
66
|
-
readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
67
|
-
}
|
|
68
|
-
splits.initCallbacks.push(__init);
|
|
69
|
-
if (splits.hasInit)
|
|
70
|
-
__init();
|
|
71
69
|
function checkIsReadyFromCache() {
|
|
72
70
|
isReadyFromCache = true;
|
|
73
71
|
// Don't emit SDK_READY_FROM_CACHE if SDK_READY has been emitted
|
|
74
|
-
if (!isReady
|
|
72
|
+
if (!isReady) {
|
|
75
73
|
try {
|
|
76
74
|
syncLastUpdate();
|
|
77
75
|
gate.emit(constants_1.SDK_READY_FROM_CACHE);
|
|
@@ -83,8 +81,6 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
|
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
83
|
function checkIsReadyOrUpdate(diff) {
|
|
86
|
-
if (isDestroyed)
|
|
87
|
-
return;
|
|
88
84
|
if (isReady) {
|
|
89
85
|
try {
|
|
90
86
|
syncLastUpdate();
|
|
@@ -110,12 +106,14 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
|
|
|
110
106
|
}
|
|
111
107
|
}
|
|
112
108
|
}
|
|
109
|
+
var refCount = 1;
|
|
113
110
|
return {
|
|
114
111
|
splits: splits,
|
|
115
112
|
segments: segments,
|
|
116
113
|
gate: gate,
|
|
117
114
|
shared: function () {
|
|
118
|
-
|
|
115
|
+
refCount++;
|
|
116
|
+
return readinessManagerFactory(EventEmitter, settings, splits);
|
|
119
117
|
},
|
|
120
118
|
// @TODO review/remove next methods when non-recoverable errors are reworked
|
|
121
119
|
// Called on consumer mode, when storage fails to connect
|
|
@@ -132,9 +130,13 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
|
|
|
132
130
|
destroy: function () {
|
|
133
131
|
isDestroyed = true;
|
|
134
132
|
syncLastUpdate();
|
|
133
|
+
segments.removeAllListeners();
|
|
134
|
+
gate.removeAllListeners();
|
|
135
135
|
clearTimeout(readyTimeoutId);
|
|
136
|
-
if (
|
|
137
|
-
|
|
136
|
+
if (refCount > 0)
|
|
137
|
+
refCount--;
|
|
138
|
+
if (refCount === 0)
|
|
139
|
+
splits.removeAllListeners();
|
|
138
140
|
},
|
|
139
141
|
isReady: function () { return isReady; },
|
|
140
142
|
isReadyFromCache: function () { return isReadyFromCache; },
|
package/cjs/sdkFactory/index.js
CHANGED
|
@@ -102,7 +102,6 @@ function sdkFactory(params) {
|
|
|
102
102
|
Logger: (0, sdkLogger_1.createLoggerAPI)(log),
|
|
103
103
|
settings: settings,
|
|
104
104
|
destroy: function () {
|
|
105
|
-
hasInit = false;
|
|
106
105
|
return Promise.all(Object.keys(clients).map(function (key) { return clients[key].destroy(); })).then(function () { });
|
|
107
106
|
}
|
|
108
107
|
}, extraProps && extraProps(ctx), lazyInit ? { init: init } : init());
|
|
@@ -9,6 +9,8 @@ var KeyBuilderCS_1 = require("../KeyBuilderCS");
|
|
|
9
9
|
var isLocalStorageAvailable_1 = require("../../utils/env/isLocalStorageAvailable");
|
|
10
10
|
var SplitsCacheInLocal_1 = require("./SplitsCacheInLocal");
|
|
11
11
|
var MySegmentsCacheInLocal_1 = require("./MySegmentsCacheInLocal");
|
|
12
|
+
var MySegmentsCacheInMemory_1 = require("../inMemory/MySegmentsCacheInMemory");
|
|
13
|
+
var SplitsCacheInMemory_1 = require("../inMemory/SplitsCacheInMemory");
|
|
12
14
|
var browser_1 = require("../../utils/constants/browser");
|
|
13
15
|
var InMemoryStorageCS_1 = require("../inMemory/InMemoryStorageCS");
|
|
14
16
|
var constants_1 = require("./constants");
|
|
@@ -28,7 +30,7 @@ function InLocalStorage(options) {
|
|
|
28
30
|
params.settings.log.warn(constants_1.LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
|
|
29
31
|
return (0, InMemoryStorageCS_1.InMemoryStorageCSFactory)(params);
|
|
30
32
|
}
|
|
31
|
-
var settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize,
|
|
33
|
+
var settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, _c = _a.sync, impressionsMode = _c.impressionsMode, __splitFiltersValidation = _c.__splitFiltersValidation;
|
|
32
34
|
var matchingKey = (0, key_1.getMatching)(settings.core.key);
|
|
33
35
|
var keys = new KeyBuilderCS_1.KeyBuilderCS(prefix, matchingKey);
|
|
34
36
|
var expirationTimestamp = Date.now() - browser_1.DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
@@ -44,7 +46,16 @@ function InLocalStorage(options) {
|
|
|
44
46
|
events: new EventsCacheInMemory_1.EventsCacheInMemory(eventsQueueSize),
|
|
45
47
|
telemetry: (0, TelemetryCacheInMemory_1.shouldRecordTelemetry)(params) ? new TelemetryCacheInMemory_1.TelemetryCacheInMemory(splits, segments) : undefined,
|
|
46
48
|
uniqueKeys: impressionsMode === constants_2.NONE ? new UniqueKeysCacheInMemoryCS_1.UniqueKeysCacheInMemoryCS() : undefined,
|
|
47
|
-
destroy: function () {
|
|
49
|
+
destroy: function () {
|
|
50
|
+
var _a;
|
|
51
|
+
this.splits = new SplitsCacheInMemory_1.SplitsCacheInMemory(__splitFiltersValidation);
|
|
52
|
+
this.segments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
|
|
53
|
+
this.largeSegments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
|
|
54
|
+
this.impressions.clear();
|
|
55
|
+
this.impressionCounts && this.impressionCounts.clear();
|
|
56
|
+
this.events.clear();
|
|
57
|
+
(_a = this.uniqueKeys) === null || _a === void 0 ? void 0 : _a.clear();
|
|
58
|
+
},
|
|
48
59
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
49
60
|
shared: function (matchingKey) {
|
|
50
61
|
return {
|
|
@@ -55,7 +66,11 @@ function InLocalStorage(options) {
|
|
|
55
66
|
impressionCounts: this.impressionCounts,
|
|
56
67
|
events: this.events,
|
|
57
68
|
telemetry: this.telemetry,
|
|
58
|
-
destroy: function () {
|
|
69
|
+
destroy: function () {
|
|
70
|
+
this.splits = new SplitsCacheInMemory_1.SplitsCacheInMemory(__splitFiltersValidation);
|
|
71
|
+
this.segments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
|
|
72
|
+
this.largeSegments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
|
|
73
|
+
}
|
|
59
74
|
};
|
|
60
75
|
},
|
|
61
76
|
};
|
|
@@ -26,7 +26,15 @@ function InMemoryStorageFactory(params) {
|
|
|
26
26
|
events: new EventsCacheInMemory_1.EventsCacheInMemory(eventsQueueSize),
|
|
27
27
|
telemetry: (0, TelemetryCacheInMemory_1.shouldRecordTelemetry)(params) ? new TelemetryCacheInMemory_1.TelemetryCacheInMemory(splits, segments) : undefined,
|
|
28
28
|
uniqueKeys: impressionsMode === constants_1.NONE ? new UniqueKeysCacheInMemory_1.UniqueKeysCacheInMemory() : undefined,
|
|
29
|
-
|
|
29
|
+
// When using MEMORY we should clean all the caches to leave them empty
|
|
30
|
+
destroy: function () {
|
|
31
|
+
this.splits.clear();
|
|
32
|
+
this.segments.clear();
|
|
33
|
+
this.impressions.clear();
|
|
34
|
+
this.impressionCounts && this.impressionCounts.clear();
|
|
35
|
+
this.events.clear();
|
|
36
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
37
|
+
}
|
|
30
38
|
};
|
|
31
39
|
// @TODO revisit storage logic in localhost mode
|
|
32
40
|
// No tracking data in localhost mode to avoid memory leaks
|
|
@@ -28,7 +28,16 @@ function InMemoryStorageCSFactory(params) {
|
|
|
28
28
|
events: new EventsCacheInMemory_1.EventsCacheInMemory(eventsQueueSize),
|
|
29
29
|
telemetry: (0, TelemetryCacheInMemory_1.shouldRecordTelemetry)(params) ? new TelemetryCacheInMemory_1.TelemetryCacheInMemory(splits, segments) : undefined,
|
|
30
30
|
uniqueKeys: impressionsMode === constants_1.NONE ? new UniqueKeysCacheInMemoryCS_1.UniqueKeysCacheInMemoryCS() : undefined,
|
|
31
|
-
|
|
31
|
+
// When using MEMORY we should clean all the caches to leave them empty
|
|
32
|
+
destroy: function () {
|
|
33
|
+
this.splits.clear();
|
|
34
|
+
this.segments.clear();
|
|
35
|
+
this.largeSegments.clear();
|
|
36
|
+
this.impressions.clear();
|
|
37
|
+
this.impressionCounts && this.impressionCounts.clear();
|
|
38
|
+
this.events.clear();
|
|
39
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
40
|
+
},
|
|
32
41
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
|
|
33
42
|
shared: function () {
|
|
34
43
|
return {
|
|
@@ -39,7 +48,12 @@ function InMemoryStorageCSFactory(params) {
|
|
|
39
48
|
impressionCounts: this.impressionCounts,
|
|
40
49
|
events: this.events,
|
|
41
50
|
telemetry: this.telemetry,
|
|
42
|
-
|
|
51
|
+
// Set a new splits cache to clean it for the client without affecting other clients
|
|
52
|
+
destroy: function () {
|
|
53
|
+
this.splits = new SplitsCacheInMemory_1.SplitsCacheInMemory(__splitFiltersValidation);
|
|
54
|
+
this.segments.clear();
|
|
55
|
+
this.largeSegments.clear();
|
|
56
|
+
}
|
|
43
57
|
};
|
|
44
58
|
},
|
|
45
59
|
};
|
|
@@ -52,7 +52,7 @@ var SegmentsCacheInMemory = /** @class */ (function () {
|
|
|
52
52
|
};
|
|
53
53
|
SegmentsCacheInMemory.prototype.getChangeNumber = function (name) {
|
|
54
54
|
var value = this.segmentChangeNumber[name];
|
|
55
|
-
return (0, lang_1.isIntegerNumber)(value) ? value :
|
|
55
|
+
return (0, lang_1.isIntegerNumber)(value) ? value : undefined;
|
|
56
56
|
};
|
|
57
57
|
// No-op. Not used in server-side
|
|
58
58
|
SegmentsCacheInMemory.prototype.resetSegments = function () { return false; };
|
|
@@ -31,10 +31,10 @@ var SegmentsCacheInRedis = /** @class */ (function () {
|
|
|
31
31
|
var _this = this;
|
|
32
32
|
return this.redis.get(this.keys.buildSegmentTillKey(name)).then(function (value) {
|
|
33
33
|
var i = parseInt(value, 10);
|
|
34
|
-
return (0, lang_1.isNaNNumber)(i) ?
|
|
34
|
+
return (0, lang_1.isNaNNumber)(i) ? undefined : i;
|
|
35
35
|
}).catch(function (e) {
|
|
36
36
|
_this.log.error(constants_1.LOG_PREFIX + 'Could not retrieve changeNumber from segments storage. Error: ' + e);
|
|
37
|
-
return
|
|
37
|
+
return undefined;
|
|
38
38
|
});
|
|
39
39
|
};
|
|
40
40
|
SegmentsCacheInRedis.prototype.registerSegments = function (segments) {
|
|
@@ -45,10 +45,10 @@ var SegmentsCachePluggable = /** @class */ (function () {
|
|
|
45
45
|
var _this = this;
|
|
46
46
|
return this.wrapper.get(this.keys.buildSegmentTillKey(name)).then(function (value) {
|
|
47
47
|
var i = parseInt(value, 10);
|
|
48
|
-
return (0, lang_1.isNaNNumber)(i) ?
|
|
48
|
+
return (0, lang_1.isNaNNumber)(i) ? undefined : i;
|
|
49
49
|
}).catch(function (e) {
|
|
50
50
|
_this.log.error(constants_1.LOG_PREFIX + 'Could not retrieve changeNumber from segments storage. Error: ' + e);
|
|
51
|
-
return
|
|
51
|
+
return undefined;
|
|
52
52
|
});
|
|
53
53
|
};
|
|
54
54
|
/**
|
|
@@ -21,9 +21,9 @@ function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segments, read
|
|
|
21
21
|
var sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
|
|
22
22
|
return sincePromise.then(function (since) {
|
|
23
23
|
// if fetchOnlyNew flag, avoid processing already fetched segments
|
|
24
|
-
return fetchOnlyNew && since !==
|
|
24
|
+
return fetchOnlyNew && since !== undefined ?
|
|
25
25
|
false :
|
|
26
|
-
segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
|
|
26
|
+
segmentChangesFetcher(since || -1, segmentName, noCache, till).then(function (changes) {
|
|
27
27
|
return Promise.all(changes.map(function (x) {
|
|
28
28
|
log.debug(constants_2.LOG_PREFIX_SYNC_SEGMENTS + "Processing " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
|
|
29
29
|
return segments.update(segmentName, x.added, x.removed, x.till);
|
|
@@ -14,7 +14,7 @@ function checkAllSegmentsExist(segments) {
|
|
|
14
14
|
var registeredSegments = Promise.resolve(segments.getRegisteredSegments());
|
|
15
15
|
return registeredSegments.then(function (segmentNames) {
|
|
16
16
|
return Promise.all(segmentNames.map(function (segmentName) { return segments.getChangeNumber(segmentName); }))
|
|
17
|
-
.then(function (changeNumbers) { return changeNumbers.every(function (changeNumber) { return changeNumber !==
|
|
17
|
+
.then(function (changeNumbers) { return changeNumbers.every(function (changeNumber) { return changeNumber !== undefined; }); });
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
@@ -16,7 +16,7 @@ function SegmentsUpdateWorker(log, segmentsSyncTask, segmentsCache) {
|
|
|
16
16
|
var backoff = new Backoff_1.Backoff(__handleSegmentUpdateCall, constants_1.FETCH_BACKOFF_BASE, constants_1.FETCH_BACKOFF_MAX_WAIT);
|
|
17
17
|
function __handleSegmentUpdateCall() {
|
|
18
18
|
isHandlingEvent = true;
|
|
19
|
-
if (maxChangeNumber > segmentsCache.getChangeNumber(segment)) {
|
|
19
|
+
if (maxChangeNumber > (segmentsCache.getChangeNumber(segment) || -1)) {
|
|
20
20
|
handleNewEvent = false;
|
|
21
21
|
// fetch segments revalidating data if cached
|
|
22
22
|
segmentsSyncTask.execute(false, segment, true, cdnBypass ? maxChangeNumber : undefined).then(function () {
|
|
@@ -27,7 +27,7 @@ function SegmentsUpdateWorker(log, segmentsSyncTask, segmentsCache) {
|
|
|
27
27
|
}
|
|
28
28
|
else {
|
|
29
29
|
var attempts = backoff.attempts + 1;
|
|
30
|
-
if (maxChangeNumber <= segmentsCache.getChangeNumber(segment)) {
|
|
30
|
+
if (maxChangeNumber <= (segmentsCache.getChangeNumber(segment) || -1)) {
|
|
31
31
|
log.debug("Refresh completed" + (cdnBypass ? ' bypassing the CDN' : '') + " in " + attempts + " attempts.");
|
|
32
32
|
isHandlingEvent = false;
|
|
33
33
|
return;
|
|
@@ -54,7 +54,7 @@ function SegmentsUpdateWorker(log, segmentsSyncTask, segmentsCache) {
|
|
|
54
54
|
}
|
|
55
55
|
return {
|
|
56
56
|
put: function (changeNumber) {
|
|
57
|
-
var currentChangeNumber = segmentsCache.getChangeNumber(segment);
|
|
57
|
+
var currentChangeNumber = segmentsCache.getChangeNumber(segment) || -1;
|
|
58
58
|
if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber)
|
|
59
59
|
return;
|
|
60
60
|
maxChangeNumber = changeNumber;
|
|
@@ -25,7 +25,7 @@ function segmentsEventEmitterFactory(EventEmitter) {
|
|
|
25
25
|
/**
|
|
26
26
|
* Factory of readiness manager, which handles the ready / update event propagation.
|
|
27
27
|
*/
|
|
28
|
-
export function readinessManagerFactory(EventEmitter, settings, splits
|
|
28
|
+
export function readinessManagerFactory(EventEmitter, settings, splits) {
|
|
29
29
|
if (splits === void 0) { splits = splitsEventEmitterFactory(EventEmitter); }
|
|
30
30
|
var readyTimeout = settings.startup.readyTimeout;
|
|
31
31
|
var segments = segmentsEventEmitterFactory(EventEmitter);
|
|
@@ -51,24 +51,22 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
|
|
|
51
51
|
syncLastUpdate();
|
|
52
52
|
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
|
|
53
53
|
}
|
|
54
|
+
var readyTimeoutId;
|
|
55
|
+
if (readyTimeout > 0) {
|
|
56
|
+
if (splits.hasInit)
|
|
57
|
+
readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
58
|
+
else
|
|
59
|
+
splits.initCallbacks.push(function () { readyTimeoutId = setTimeout(timeout, readyTimeout); });
|
|
60
|
+
}
|
|
54
61
|
// emit SDK_READY and SDK_UPDATE
|
|
55
62
|
var isReady = false;
|
|
56
63
|
splits.on(SDK_SPLITS_ARRIVED, checkIsReadyOrUpdate);
|
|
57
64
|
segments.on(SDK_SEGMENTS_ARRIVED, checkIsReadyOrUpdate);
|
|
58
65
|
var isDestroyed = false;
|
|
59
|
-
var readyTimeoutId;
|
|
60
|
-
function __init() {
|
|
61
|
-
isDestroyed = false;
|
|
62
|
-
if (readyTimeout > 0 && !isReady)
|
|
63
|
-
readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
64
|
-
}
|
|
65
|
-
splits.initCallbacks.push(__init);
|
|
66
|
-
if (splits.hasInit)
|
|
67
|
-
__init();
|
|
68
66
|
function checkIsReadyFromCache() {
|
|
69
67
|
isReadyFromCache = true;
|
|
70
68
|
// Don't emit SDK_READY_FROM_CACHE if SDK_READY has been emitted
|
|
71
|
-
if (!isReady
|
|
69
|
+
if (!isReady) {
|
|
72
70
|
try {
|
|
73
71
|
syncLastUpdate();
|
|
74
72
|
gate.emit(SDK_READY_FROM_CACHE);
|
|
@@ -80,8 +78,6 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
|
|
|
80
78
|
}
|
|
81
79
|
}
|
|
82
80
|
function checkIsReadyOrUpdate(diff) {
|
|
83
|
-
if (isDestroyed)
|
|
84
|
-
return;
|
|
85
81
|
if (isReady) {
|
|
86
82
|
try {
|
|
87
83
|
syncLastUpdate();
|
|
@@ -107,12 +103,14 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
|
|
|
107
103
|
}
|
|
108
104
|
}
|
|
109
105
|
}
|
|
106
|
+
var refCount = 1;
|
|
110
107
|
return {
|
|
111
108
|
splits: splits,
|
|
112
109
|
segments: segments,
|
|
113
110
|
gate: gate,
|
|
114
111
|
shared: function () {
|
|
115
|
-
|
|
112
|
+
refCount++;
|
|
113
|
+
return readinessManagerFactory(EventEmitter, settings, splits);
|
|
116
114
|
},
|
|
117
115
|
// @TODO review/remove next methods when non-recoverable errors are reworked
|
|
118
116
|
// Called on consumer mode, when storage fails to connect
|
|
@@ -129,9 +127,13 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
|
|
|
129
127
|
destroy: function () {
|
|
130
128
|
isDestroyed = true;
|
|
131
129
|
syncLastUpdate();
|
|
130
|
+
segments.removeAllListeners();
|
|
131
|
+
gate.removeAllListeners();
|
|
132
132
|
clearTimeout(readyTimeoutId);
|
|
133
|
-
if (
|
|
134
|
-
|
|
133
|
+
if (refCount > 0)
|
|
134
|
+
refCount--;
|
|
135
|
+
if (refCount === 0)
|
|
136
|
+
splits.removeAllListeners();
|
|
135
137
|
},
|
|
136
138
|
isReady: function () { return isReady; },
|
|
137
139
|
isReadyFromCache: function () { return isReadyFromCache; },
|
package/esm/sdkFactory/index.js
CHANGED
|
@@ -99,7 +99,6 @@ export function sdkFactory(params) {
|
|
|
99
99
|
Logger: createLoggerAPI(log),
|
|
100
100
|
settings: settings,
|
|
101
101
|
destroy: function () {
|
|
102
|
-
hasInit = false;
|
|
103
102
|
return Promise.all(Object.keys(clients).map(function (key) { return clients[key].destroy(); })).then(function () { });
|
|
104
103
|
}
|
|
105
104
|
}, extraProps && extraProps(ctx), lazyInit ? { init: init } : init());
|
|
@@ -6,6 +6,8 @@ import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
|
6
6
|
import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
|
|
7
7
|
import { SplitsCacheInLocal } from './SplitsCacheInLocal';
|
|
8
8
|
import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
|
|
9
|
+
import { MySegmentsCacheInMemory } from '../inMemory/MySegmentsCacheInMemory';
|
|
10
|
+
import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
|
|
9
11
|
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
|
|
10
12
|
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
|
|
11
13
|
import { LOG_PREFIX } from './constants';
|
|
@@ -25,7 +27,7 @@ export function InLocalStorage(options) {
|
|
|
25
27
|
params.settings.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
|
|
26
28
|
return InMemoryStorageCSFactory(params);
|
|
27
29
|
}
|
|
28
|
-
var settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize,
|
|
30
|
+
var settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, _c = _a.sync, impressionsMode = _c.impressionsMode, __splitFiltersValidation = _c.__splitFiltersValidation;
|
|
29
31
|
var matchingKey = getMatching(settings.core.key);
|
|
30
32
|
var keys = new KeyBuilderCS(prefix, matchingKey);
|
|
31
33
|
var expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
@@ -41,7 +43,16 @@ export function InLocalStorage(options) {
|
|
|
41
43
|
events: new EventsCacheInMemory(eventsQueueSize),
|
|
42
44
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
43
45
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
|
|
44
|
-
destroy: function () {
|
|
46
|
+
destroy: function () {
|
|
47
|
+
var _a;
|
|
48
|
+
this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
49
|
+
this.segments = new MySegmentsCacheInMemory();
|
|
50
|
+
this.largeSegments = new MySegmentsCacheInMemory();
|
|
51
|
+
this.impressions.clear();
|
|
52
|
+
this.impressionCounts && this.impressionCounts.clear();
|
|
53
|
+
this.events.clear();
|
|
54
|
+
(_a = this.uniqueKeys) === null || _a === void 0 ? void 0 : _a.clear();
|
|
55
|
+
},
|
|
45
56
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
46
57
|
shared: function (matchingKey) {
|
|
47
58
|
return {
|
|
@@ -52,7 +63,11 @@ export function InLocalStorage(options) {
|
|
|
52
63
|
impressionCounts: this.impressionCounts,
|
|
53
64
|
events: this.events,
|
|
54
65
|
telemetry: this.telemetry,
|
|
55
|
-
destroy: function () {
|
|
66
|
+
destroy: function () {
|
|
67
|
+
this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
68
|
+
this.segments = new MySegmentsCacheInMemory();
|
|
69
|
+
this.largeSegments = new MySegmentsCacheInMemory();
|
|
70
|
+
}
|
|
56
71
|
};
|
|
57
72
|
},
|
|
58
73
|
};
|
|
@@ -23,7 +23,15 @@ export function InMemoryStorageFactory(params) {
|
|
|
23
23
|
events: new EventsCacheInMemory(eventsQueueSize),
|
|
24
24
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
25
25
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,
|
|
26
|
-
|
|
26
|
+
// When using MEMORY we should clean all the caches to leave them empty
|
|
27
|
+
destroy: function () {
|
|
28
|
+
this.splits.clear();
|
|
29
|
+
this.segments.clear();
|
|
30
|
+
this.impressions.clear();
|
|
31
|
+
this.impressionCounts && this.impressionCounts.clear();
|
|
32
|
+
this.events.clear();
|
|
33
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
34
|
+
}
|
|
27
35
|
};
|
|
28
36
|
// @TODO revisit storage logic in localhost mode
|
|
29
37
|
// No tracking data in localhost mode to avoid memory leaks
|
|
@@ -25,7 +25,16 @@ export function InMemoryStorageCSFactory(params) {
|
|
|
25
25
|
events: new EventsCacheInMemory(eventsQueueSize),
|
|
26
26
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
27
27
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
|
|
28
|
-
|
|
28
|
+
// When using MEMORY we should clean all the caches to leave them empty
|
|
29
|
+
destroy: function () {
|
|
30
|
+
this.splits.clear();
|
|
31
|
+
this.segments.clear();
|
|
32
|
+
this.largeSegments.clear();
|
|
33
|
+
this.impressions.clear();
|
|
34
|
+
this.impressionCounts && this.impressionCounts.clear();
|
|
35
|
+
this.events.clear();
|
|
36
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
37
|
+
},
|
|
29
38
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
|
|
30
39
|
shared: function () {
|
|
31
40
|
return {
|
|
@@ -36,7 +45,12 @@ export function InMemoryStorageCSFactory(params) {
|
|
|
36
45
|
impressionCounts: this.impressionCounts,
|
|
37
46
|
events: this.events,
|
|
38
47
|
telemetry: this.telemetry,
|
|
39
|
-
|
|
48
|
+
// Set a new splits cache to clean it for the client without affecting other clients
|
|
49
|
+
destroy: function () {
|
|
50
|
+
this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
51
|
+
this.segments.clear();
|
|
52
|
+
this.largeSegments.clear();
|
|
53
|
+
}
|
|
40
54
|
};
|
|
41
55
|
},
|
|
42
56
|
};
|
|
@@ -49,7 +49,7 @@ var SegmentsCacheInMemory = /** @class */ (function () {
|
|
|
49
49
|
};
|
|
50
50
|
SegmentsCacheInMemory.prototype.getChangeNumber = function (name) {
|
|
51
51
|
var value = this.segmentChangeNumber[name];
|
|
52
|
-
return isIntegerNumber(value) ? value :
|
|
52
|
+
return isIntegerNumber(value) ? value : undefined;
|
|
53
53
|
};
|
|
54
54
|
// No-op. Not used in server-side
|
|
55
55
|
SegmentsCacheInMemory.prototype.resetSegments = function () { return false; };
|
|
@@ -28,10 +28,10 @@ var SegmentsCacheInRedis = /** @class */ (function () {
|
|
|
28
28
|
var _this = this;
|
|
29
29
|
return this.redis.get(this.keys.buildSegmentTillKey(name)).then(function (value) {
|
|
30
30
|
var i = parseInt(value, 10);
|
|
31
|
-
return isNaNNumber(i) ?
|
|
31
|
+
return isNaNNumber(i) ? undefined : i;
|
|
32
32
|
}).catch(function (e) {
|
|
33
33
|
_this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from segments storage. Error: ' + e);
|
|
34
|
-
return
|
|
34
|
+
return undefined;
|
|
35
35
|
});
|
|
36
36
|
};
|
|
37
37
|
SegmentsCacheInRedis.prototype.registerSegments = function (segments) {
|
|
@@ -42,10 +42,10 @@ var SegmentsCachePluggable = /** @class */ (function () {
|
|
|
42
42
|
var _this = this;
|
|
43
43
|
return this.wrapper.get(this.keys.buildSegmentTillKey(name)).then(function (value) {
|
|
44
44
|
var i = parseInt(value, 10);
|
|
45
|
-
return isNaNNumber(i) ?
|
|
45
|
+
return isNaNNumber(i) ? undefined : i;
|
|
46
46
|
}).catch(function (e) {
|
|
47
47
|
_this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from segments storage. Error: ' + e);
|
|
48
|
-
return
|
|
48
|
+
return undefined;
|
|
49
49
|
});
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
@@ -18,9 +18,9 @@ export function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segment
|
|
|
18
18
|
var sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
|
|
19
19
|
return sincePromise.then(function (since) {
|
|
20
20
|
// if fetchOnlyNew flag, avoid processing already fetched segments
|
|
21
|
-
return fetchOnlyNew && since !==
|
|
21
|
+
return fetchOnlyNew && since !== undefined ?
|
|
22
22
|
false :
|
|
23
|
-
segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
|
|
23
|
+
segmentChangesFetcher(since || -1, segmentName, noCache, till).then(function (changes) {
|
|
24
24
|
return Promise.all(changes.map(function (x) {
|
|
25
25
|
log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processing " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
|
|
26
26
|
return segments.update(segmentName, x.added, x.removed, x.till);
|
|
@@ -11,7 +11,7 @@ function checkAllSegmentsExist(segments) {
|
|
|
11
11
|
var registeredSegments = Promise.resolve(segments.getRegisteredSegments());
|
|
12
12
|
return registeredSegments.then(function (segmentNames) {
|
|
13
13
|
return Promise.all(segmentNames.map(function (segmentName) { return segments.getChangeNumber(segmentName); }))
|
|
14
|
-
.then(function (changeNumbers) { return changeNumbers.every(function (changeNumber) { return changeNumber !==
|
|
14
|
+
.then(function (changeNumbers) { return changeNumbers.every(function (changeNumber) { return changeNumber !== undefined; }); });
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
@@ -13,7 +13,7 @@ export function SegmentsUpdateWorker(log, segmentsSyncTask, segmentsCache) {
|
|
|
13
13
|
var backoff = new Backoff(__handleSegmentUpdateCall, FETCH_BACKOFF_BASE, FETCH_BACKOFF_MAX_WAIT);
|
|
14
14
|
function __handleSegmentUpdateCall() {
|
|
15
15
|
isHandlingEvent = true;
|
|
16
|
-
if (maxChangeNumber > segmentsCache.getChangeNumber(segment)) {
|
|
16
|
+
if (maxChangeNumber > (segmentsCache.getChangeNumber(segment) || -1)) {
|
|
17
17
|
handleNewEvent = false;
|
|
18
18
|
// fetch segments revalidating data if cached
|
|
19
19
|
segmentsSyncTask.execute(false, segment, true, cdnBypass ? maxChangeNumber : undefined).then(function () {
|
|
@@ -24,7 +24,7 @@ export function SegmentsUpdateWorker(log, segmentsSyncTask, segmentsCache) {
|
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
26
26
|
var attempts = backoff.attempts + 1;
|
|
27
|
-
if (maxChangeNumber <= segmentsCache.getChangeNumber(segment)) {
|
|
27
|
+
if (maxChangeNumber <= (segmentsCache.getChangeNumber(segment) || -1)) {
|
|
28
28
|
log.debug("Refresh completed" + (cdnBypass ? ' bypassing the CDN' : '') + " in " + attempts + " attempts.");
|
|
29
29
|
isHandlingEvent = false;
|
|
30
30
|
return;
|
|
@@ -51,7 +51,7 @@ export function SegmentsUpdateWorker(log, segmentsSyncTask, segmentsCache) {
|
|
|
51
51
|
}
|
|
52
52
|
return {
|
|
53
53
|
put: function (changeNumber) {
|
|
54
|
-
var currentChangeNumber = segmentsCache.getChangeNumber(segment);
|
|
54
|
+
var currentChangeNumber = segmentsCache.getChangeNumber(segment) || -1;
|
|
55
55
|
if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber)
|
|
56
56
|
return;
|
|
57
57
|
maxChangeNumber = changeNumber;
|
package/package.json
CHANGED
|
@@ -37,9 +37,7 @@ function segmentsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitt
|
|
|
37
37
|
export function readinessManagerFactory(
|
|
38
38
|
EventEmitter: new () => SplitIO.IEventEmitter,
|
|
39
39
|
settings: ISettings,
|
|
40
|
-
splits: ISplitsEventEmitter = splitsEventEmitterFactory(EventEmitter)
|
|
41
|
-
isShared?: boolean
|
|
42
|
-
): IReadinessManager {
|
|
40
|
+
splits: ISplitsEventEmitter = splitsEventEmitterFactory(EventEmitter)): IReadinessManager {
|
|
43
41
|
|
|
44
42
|
const readyTimeout = settings.startup.readyTimeout;
|
|
45
43
|
|
|
@@ -68,6 +66,11 @@ export function readinessManagerFactory(
|
|
|
68
66
|
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
|
|
69
67
|
}
|
|
70
68
|
|
|
69
|
+
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
70
|
+
if (readyTimeout > 0) {
|
|
71
|
+
if (splits.hasInit) readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
72
|
+
else splits.initCallbacks.push(() => { readyTimeoutId = setTimeout(timeout, readyTimeout); });
|
|
73
|
+
}
|
|
71
74
|
|
|
72
75
|
// emit SDK_READY and SDK_UPDATE
|
|
73
76
|
let isReady = false;
|
|
@@ -75,19 +78,11 @@ export function readinessManagerFactory(
|
|
|
75
78
|
segments.on(SDK_SEGMENTS_ARRIVED, checkIsReadyOrUpdate);
|
|
76
79
|
|
|
77
80
|
let isDestroyed = false;
|
|
78
|
-
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
79
|
-
function __init() {
|
|
80
|
-
isDestroyed = false;
|
|
81
|
-
if (readyTimeout > 0 && !isReady) readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
splits.initCallbacks.push(__init);
|
|
85
|
-
if (splits.hasInit) __init();
|
|
86
81
|
|
|
87
82
|
function checkIsReadyFromCache() {
|
|
88
83
|
isReadyFromCache = true;
|
|
89
84
|
// Don't emit SDK_READY_FROM_CACHE if SDK_READY has been emitted
|
|
90
|
-
if (!isReady
|
|
85
|
+
if (!isReady) {
|
|
91
86
|
try {
|
|
92
87
|
syncLastUpdate();
|
|
93
88
|
gate.emit(SDK_READY_FROM_CACHE);
|
|
@@ -99,7 +94,6 @@ export function readinessManagerFactory(
|
|
|
99
94
|
}
|
|
100
95
|
|
|
101
96
|
function checkIsReadyOrUpdate(diff: any) {
|
|
102
|
-
if (isDestroyed) return;
|
|
103
97
|
if (isReady) {
|
|
104
98
|
try {
|
|
105
99
|
syncLastUpdate();
|
|
@@ -123,13 +117,16 @@ export function readinessManagerFactory(
|
|
|
123
117
|
}
|
|
124
118
|
}
|
|
125
119
|
|
|
120
|
+
let refCount = 1;
|
|
121
|
+
|
|
126
122
|
return {
|
|
127
123
|
splits,
|
|
128
124
|
segments,
|
|
129
125
|
gate,
|
|
130
126
|
|
|
131
127
|
shared() {
|
|
132
|
-
|
|
128
|
+
refCount++;
|
|
129
|
+
return readinessManagerFactory(EventEmitter, settings, splits);
|
|
133
130
|
},
|
|
134
131
|
|
|
135
132
|
// @TODO review/remove next methods when non-recoverable errors are reworked
|
|
@@ -148,9 +145,13 @@ export function readinessManagerFactory(
|
|
|
148
145
|
destroy() {
|
|
149
146
|
isDestroyed = true;
|
|
150
147
|
syncLastUpdate();
|
|
148
|
+
|
|
149
|
+
segments.removeAllListeners();
|
|
150
|
+
gate.removeAllListeners();
|
|
151
151
|
clearTimeout(readyTimeoutId);
|
|
152
152
|
|
|
153
|
-
if (
|
|
153
|
+
if (refCount > 0) refCount--;
|
|
154
|
+
if (refCount === 0) splits.removeAllListeners();
|
|
154
155
|
},
|
|
155
156
|
|
|
156
157
|
isReady() { return isReady; },
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -126,7 +126,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
126
126
|
settings,
|
|
127
127
|
|
|
128
128
|
destroy() {
|
|
129
|
-
hasInit = false;
|
|
130
129
|
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
|
|
131
130
|
}
|
|
132
131
|
}, extraProps && extraProps(ctx), lazyInit ? { init } : init());
|
|
@@ -42,7 +42,7 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
|
|
|
42
42
|
// @TODO for client-side it should be the number of clients, but it requires a refactor of MySegments caches to simplify the code.
|
|
43
43
|
abstract getKeysCount(): number
|
|
44
44
|
|
|
45
|
-
abstract getChangeNumber(
|
|
45
|
+
abstract getChangeNumber(): number
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* For server-side synchronizer: the method is not used.
|
|
@@ -7,6 +7,8 @@ import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
|
7
7
|
import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
|
|
8
8
|
import { SplitsCacheInLocal } from './SplitsCacheInLocal';
|
|
9
9
|
import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
|
|
10
|
+
import { MySegmentsCacheInMemory } from '../inMemory/MySegmentsCacheInMemory';
|
|
11
|
+
import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
|
|
10
12
|
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
|
|
11
13
|
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
|
|
12
14
|
import { LOG_PREFIX } from './constants';
|
|
@@ -34,7 +36,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
34
36
|
return InMemoryStorageCSFactory(params);
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
|
|
39
|
+
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
|
|
38
40
|
const matchingKey = getMatching(settings.core.key);
|
|
39
41
|
const keys = new KeyBuilderCS(prefix, matchingKey);
|
|
40
42
|
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
@@ -53,7 +55,15 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
53
55
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
54
56
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
|
|
55
57
|
|
|
56
|
-
destroy() {
|
|
58
|
+
destroy() {
|
|
59
|
+
this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
60
|
+
this.segments = new MySegmentsCacheInMemory();
|
|
61
|
+
this.largeSegments = new MySegmentsCacheInMemory();
|
|
62
|
+
this.impressions.clear();
|
|
63
|
+
this.impressionCounts && this.impressionCounts.clear();
|
|
64
|
+
this.events.clear();
|
|
65
|
+
this.uniqueKeys?.clear();
|
|
66
|
+
},
|
|
57
67
|
|
|
58
68
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
59
69
|
shared(matchingKey: string) {
|
|
@@ -67,7 +77,11 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
67
77
|
events: this.events,
|
|
68
78
|
telemetry: this.telemetry,
|
|
69
79
|
|
|
70
|
-
destroy() {
|
|
80
|
+
destroy() {
|
|
81
|
+
this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
82
|
+
this.segments = new MySegmentsCacheInMemory();
|
|
83
|
+
this.largeSegments = new MySegmentsCacheInMemory();
|
|
84
|
+
}
|
|
71
85
|
};
|
|
72
86
|
},
|
|
73
87
|
};
|
|
@@ -28,7 +28,15 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
|
|
|
28
28
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
29
29
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// When using MEMORY we should clean all the caches to leave them empty
|
|
32
|
+
destroy() {
|
|
33
|
+
this.splits.clear();
|
|
34
|
+
this.segments.clear();
|
|
35
|
+
this.impressions.clear();
|
|
36
|
+
this.impressionCounts && this.impressionCounts.clear();
|
|
37
|
+
this.events.clear();
|
|
38
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
39
|
+
}
|
|
32
40
|
};
|
|
33
41
|
|
|
34
42
|
// @TODO revisit storage logic in localhost mode
|
|
@@ -30,7 +30,16 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
30
30
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
31
31
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// When using MEMORY we should clean all the caches to leave them empty
|
|
34
|
+
destroy() {
|
|
35
|
+
this.splits.clear();
|
|
36
|
+
this.segments.clear();
|
|
37
|
+
this.largeSegments.clear();
|
|
38
|
+
this.impressions.clear();
|
|
39
|
+
this.impressionCounts && this.impressionCounts.clear();
|
|
40
|
+
this.events.clear();
|
|
41
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
42
|
+
},
|
|
34
43
|
|
|
35
44
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
|
|
36
45
|
shared() {
|
|
@@ -43,7 +52,12 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
43
52
|
events: this.events,
|
|
44
53
|
telemetry: this.telemetry,
|
|
45
54
|
|
|
46
|
-
|
|
55
|
+
// Set a new splits cache to clean it for the client without affecting other clients
|
|
56
|
+
destroy() {
|
|
57
|
+
this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
58
|
+
this.segments.clear();
|
|
59
|
+
this.largeSegments.clear();
|
|
60
|
+
}
|
|
47
61
|
};
|
|
48
62
|
},
|
|
49
63
|
};
|
|
@@ -65,7 +65,7 @@ export class SegmentsCacheInMemory implements ISegmentsCacheSync {
|
|
|
65
65
|
getChangeNumber(name: string) {
|
|
66
66
|
const value = this.segmentChangeNumber[name];
|
|
67
67
|
|
|
68
|
-
return isIntegerNumber(value) ? value :
|
|
68
|
+
return isIntegerNumber(value) ? value : undefined;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// No-op. Not used in server-side
|
|
@@ -44,10 +44,10 @@ export class SegmentsCacheInRedis implements ISegmentsCacheAsync {
|
|
|
44
44
|
return this.redis.get(this.keys.buildSegmentTillKey(name)).then((value: string | null) => {
|
|
45
45
|
const i = parseInt(value as string, 10);
|
|
46
46
|
|
|
47
|
-
return isNaNNumber(i) ?
|
|
47
|
+
return isNaNNumber(i) ? undefined : i;
|
|
48
48
|
}).catch((e) => {
|
|
49
49
|
this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from segments storage. Error: ' + e);
|
|
50
|
-
return
|
|
50
|
+
return undefined;
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -55,10 +55,10 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync {
|
|
|
55
55
|
return this.wrapper.get(this.keys.buildSegmentTillKey(name)).then((value: string | null) => {
|
|
56
56
|
const i = parseInt(value as string, 10);
|
|
57
57
|
|
|
58
|
-
return isNaNNumber(i) ?
|
|
58
|
+
return isNaNNumber(i) ? undefined : i;
|
|
59
59
|
}).catch((e) => {
|
|
60
60
|
this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from segments storage. Error: ' + e);
|
|
61
|
-
return
|
|
61
|
+
return undefined;
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
|
package/src/storages/types.ts
CHANGED
|
@@ -237,7 +237,7 @@ export interface ISegmentsCacheBase {
|
|
|
237
237
|
isInSegment(name: string, key?: string): MaybeThenable<boolean> // different signature on Server and Client-Side
|
|
238
238
|
registerSegments(names: string[]): MaybeThenable<boolean | void> // only for Server-Side
|
|
239
239
|
getRegisteredSegments(): MaybeThenable<string[]> // only for Server-Side
|
|
240
|
-
getChangeNumber(name: string): MaybeThenable<number> // only for Server-Side
|
|
240
|
+
getChangeNumber(name: string): MaybeThenable<number | undefined> // only for Server-Side
|
|
241
241
|
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): MaybeThenable<boolean> // only for Server-Side
|
|
242
242
|
clear(): MaybeThenable<boolean | void>
|
|
243
243
|
}
|
|
@@ -248,7 +248,7 @@ export interface ISegmentsCacheSync extends ISegmentsCacheBase {
|
|
|
248
248
|
registerSegments(names: string[]): boolean
|
|
249
249
|
getRegisteredSegments(): string[]
|
|
250
250
|
getKeysCount(): number // only used for telemetry
|
|
251
|
-
getChangeNumber(name?: string): number
|
|
251
|
+
getChangeNumber(name?: string): number | undefined
|
|
252
252
|
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): boolean // only for Server-Side
|
|
253
253
|
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean // only for Sync Client-Side
|
|
254
254
|
clear(): void
|
|
@@ -258,7 +258,7 @@ export interface ISegmentsCacheAsync extends ISegmentsCacheBase {
|
|
|
258
258
|
isInSegment(name: string, key: string): Promise<boolean>
|
|
259
259
|
registerSegments(names: string[]): Promise<boolean | void>
|
|
260
260
|
getRegisteredSegments(): Promise<string[]>
|
|
261
|
-
getChangeNumber(name: string): Promise<number>
|
|
261
|
+
getChangeNumber(name: string): Promise<number | undefined>
|
|
262
262
|
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): Promise<boolean>
|
|
263
263
|
clear(): Promise<boolean | void>
|
|
264
264
|
}
|
|
@@ -33,9 +33,9 @@ export function segmentChangesUpdaterFactory(
|
|
|
33
33
|
|
|
34
34
|
return sincePromise.then(since => {
|
|
35
35
|
// if fetchOnlyNew flag, avoid processing already fetched segments
|
|
36
|
-
return fetchOnlyNew && since !==
|
|
36
|
+
return fetchOnlyNew && since !== undefined ?
|
|
37
37
|
false :
|
|
38
|
-
segmentChangesFetcher(since, segmentName, noCache, till).then((changes) => {
|
|
38
|
+
segmentChangesFetcher(since || -1, segmentName, noCache, till).then((changes) => {
|
|
39
39
|
return Promise.all(changes.map(x => {
|
|
40
40
|
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
|
|
41
41
|
return segments.update(segmentName, x.added, x.removed, x.till);
|
|
@@ -19,7 +19,7 @@ function checkAllSegmentsExist(segments: ISegmentsCacheBase): Promise<boolean> {
|
|
|
19
19
|
let registeredSegments = Promise.resolve(segments.getRegisteredSegments());
|
|
20
20
|
return registeredSegments.then(segmentNames => {
|
|
21
21
|
return Promise.all(segmentNames.map(segmentName => segments.getChangeNumber(segmentName)))
|
|
22
|
-
.then(changeNumbers => changeNumbers.every(changeNumber => changeNumber !==
|
|
22
|
+
.then(changeNumbers => changeNumbers.every(changeNumber => changeNumber !== undefined));
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -3,10 +3,11 @@ import { Backoff } from '../../../utils/Backoff';
|
|
|
3
3
|
import { IUpdateWorker } from './types';
|
|
4
4
|
import { ITelemetryTracker } from '../../../trackers/types';
|
|
5
5
|
import { MEMBERSHIPS } from '../../../utils/constants';
|
|
6
|
-
import {
|
|
6
|
+
import { IStorageSync } from '../../../storages/types';
|
|
7
7
|
import { ILogger } from '../../../logger/types';
|
|
8
8
|
import { FETCH_BACKOFF_MAX_RETRIES } from './constants';
|
|
9
9
|
import { MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE } from '../constants';
|
|
10
|
+
import { AbstractMySegmentsCacheSync } from '../../../storages/AbstractMySegmentsCacheSync';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* MySegmentsUpdateWorker factory
|
|
@@ -16,7 +17,7 @@ export function MySegmentsUpdateWorker(log: ILogger, storage: Pick<IStorageSync,
|
|
|
16
17
|
let _delay: undefined | number;
|
|
17
18
|
let _delayTimeoutID: any;
|
|
18
19
|
|
|
19
|
-
function createUpdateWorker(mySegmentsCache:
|
|
20
|
+
function createUpdateWorker(mySegmentsCache: AbstractMySegmentsCacheSync) {
|
|
20
21
|
|
|
21
22
|
let maxChangeNumber = 0; // keeps the maximum changeNumber among queued events
|
|
22
23
|
let currentChangeNumber = -1;
|
|
@@ -117,8 +118,8 @@ export function MySegmentsUpdateWorker(log: ILogger, storage: Pick<IStorageSync,
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
const updateWorkers = {
|
|
120
|
-
[MEMBERSHIPS_MS_UPDATE]: createUpdateWorker(storage.segments),
|
|
121
|
-
[MEMBERSHIPS_LS_UPDATE]: createUpdateWorker(storage.largeSegments
|
|
121
|
+
[MEMBERSHIPS_MS_UPDATE]: createUpdateWorker(storage.segments as AbstractMySegmentsCacheSync),
|
|
122
|
+
[MEMBERSHIPS_LS_UPDATE]: createUpdateWorker(storage.largeSegments as AbstractMySegmentsCacheSync),
|
|
122
123
|
};
|
|
123
124
|
|
|
124
125
|
return {
|
|
@@ -21,7 +21,7 @@ export function SegmentsUpdateWorker(log: ILogger, segmentsSyncTask: ISegmentsSy
|
|
|
21
21
|
|
|
22
22
|
function __handleSegmentUpdateCall() {
|
|
23
23
|
isHandlingEvent = true;
|
|
24
|
-
if (maxChangeNumber > segmentsCache.getChangeNumber(segment)) {
|
|
24
|
+
if (maxChangeNumber > (segmentsCache.getChangeNumber(segment) || -1)) {
|
|
25
25
|
handleNewEvent = false;
|
|
26
26
|
|
|
27
27
|
// fetch segments revalidating data if cached
|
|
@@ -32,7 +32,7 @@ export function SegmentsUpdateWorker(log: ILogger, segmentsSyncTask: ISegmentsSy
|
|
|
32
32
|
} else {
|
|
33
33
|
const attempts = backoff.attempts + 1;
|
|
34
34
|
|
|
35
|
-
if (maxChangeNumber <= segmentsCache.getChangeNumber(segment)) {
|
|
35
|
+
if (maxChangeNumber <= (segmentsCache.getChangeNumber(segment) || -1)) {
|
|
36
36
|
log.debug(`Refresh completed${cdnBypass ? ' bypassing the CDN' : ''} in ${attempts} attempts.`);
|
|
37
37
|
isHandlingEvent = false;
|
|
38
38
|
return;
|
|
@@ -60,7 +60,7 @@ export function SegmentsUpdateWorker(log: ILogger, segmentsSyncTask: ISegmentsSy
|
|
|
60
60
|
|
|
61
61
|
return {
|
|
62
62
|
put(changeNumber: number) {
|
|
63
|
-
const currentChangeNumber = segmentsCache.getChangeNumber(segment);
|
|
63
|
+
const currentChangeNumber = segmentsCache.getChangeNumber(segment) || -1;
|
|
64
64
|
|
|
65
65
|
if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber) return;
|
|
66
66
|
|