From d51e8f1adbb7b49a8dc5e04f451e62f637b10f88 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 16 Oct 2025 10:54:01 -0300 Subject: [PATCH 1/4] Update segmentChangesUpdater to timeout and retry on startup --- CHANGES.txt | 3 +++ package-lock.json | 4 ++-- package.json | 2 +- src/logger/messages/info.ts | 2 +- src/logger/messages/warn.ts | 2 +- .../polling/syncTasks/segmentsSyncTask.ts | 2 ++ .../polling/updaters/segmentChangesUpdater.ts | 21 +++++++++++++++---- .../polling/updaters/splitChangesUpdater.ts | 9 ++++---- 8 files changed, 31 insertions(+), 14 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2b93b7fc..2060a5a0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +2.8.0 (October XX, 2025) + - Updated the SDK’s initial synchronization in Node.js (server-side) to use `startup.requestTimeoutBeforeReady` and `startup.retriesOnFailureBeforeReady` to control the timeout and retry behavior of segment requests. + 2.7.1 (October 8, 2025) - Bugfix - Update `debug` option to support log levels when `logger` option is used. diff --git a/package-lock.json b/package-lock.json index 356a4e93..9faae9ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.7.1", + "version": "2.7.2-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.7.1", + "version": "2.7.2-rc.0", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", diff --git a/package.json b/package.json index 30ea1784..c43f5994 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.7.1", + "version": "2.7.2-rc.0", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/logger/messages/info.ts b/src/logger/messages/info.ts index fb017250..1e9b5f0d 100644 --- a/src/logger/messages/info.ts +++ b/src/logger/messages/info.ts @@ -22,7 +22,7 @@ export const codesInfo: [number, string][] = codesWarn.concat([ [c.POLLING_SMART_PAUSING, c.LOG_PREFIX_SYNC_POLLING + 'Turning segments data polling %s.'], [c.POLLING_START, c.LOG_PREFIX_SYNC_POLLING + 'Starting polling'], [c.POLLING_STOP, c.LOG_PREFIX_SYNC_POLLING + 'Stopping polling'], - [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying download of feature flags #%s. Reason: %s'], + [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying fetch of feature flags (attempt #%s). Reason: %s'], [c.SUBMITTERS_PUSH_FULL_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full %s queue and resetting timer.'], [c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s.'], [c.SUBMITTERS_PUSH_PAGE_HIDDEN, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing %s because page became hidden.'], diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts index 81cfda1a..97a6a55f 100644 --- a/src/logger/messages/warn.ts +++ b/src/logger/messages/warn.ts @@ -6,7 +6,7 @@ export const codesWarn: [number, string][] = codesError.concat([ [c.ENGINE_VALUE_INVALID, c.LOG_PREFIX_ENGINE_VALUE + 'Value %s doesn\'t match with expected type.'], [c.ENGINE_VALUE_NO_ATTRIBUTES, c.LOG_PREFIX_ENGINE_VALUE + 'Defined attribute `%s`. No attributes received.'], // synchronizer - [c.SYNC_MYSEGMENTS_FETCH_RETRY, c.LOG_PREFIX_SYNC_MYSEGMENTS + 'Retrying download of segments #%s. Reason: %s'], + [c.SYNC_MYSEGMENTS_FETCH_RETRY, c.LOG_PREFIX_SYNC_MYSEGMENTS + 'Retrying fetch of memberships (attempt #%s). Reason: %s'], [c.SYNC_SPLITS_FETCH_FAILS, c.LOG_PREFIX_SYNC_SPLITS + 'Error while doing fetch of feature flags. %s'], [c.STREAMING_PARSING_ERROR_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE error notification: %s'], [c.STREAMING_PARSING_MESSAGE_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE message notification: %s'], diff --git a/src/sync/polling/syncTasks/segmentsSyncTask.ts b/src/sync/polling/syncTasks/segmentsSyncTask.ts index f5a93711..b69d7087 100644 --- a/src/sync/polling/syncTasks/segmentsSyncTask.ts +++ b/src/sync/polling/syncTasks/segmentsSyncTask.ts @@ -23,6 +23,8 @@ export function segmentsSyncTaskFactory( segmentChangesFetcherFactory(fetchSegmentChanges), storage.segments, readiness, + settings.startup.requestTimeoutBeforeReady, + settings.startup.retriesOnFailureBeforeReady, ), settings.scheduler.segmentsRefreshRate, 'segmentChangesUpdater' diff --git a/src/sync/polling/updaters/segmentChangesUpdater.ts b/src/sync/polling/updaters/segmentChangesUpdater.ts index ab951b24..7fe5b7b7 100644 --- a/src/sync/polling/updaters/segmentChangesUpdater.ts +++ b/src/sync/polling/updaters/segmentChangesUpdater.ts @@ -4,6 +4,7 @@ import { IReadinessManager } from '../../../readiness/types'; import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants'; import { ILogger } from '../../../logger/types'; import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants'; +import { timeout } from '../../../utils/promise/timeout'; type ISegmentChangesUpdater = (fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number) => Promise @@ -23,11 +24,18 @@ export function segmentChangesUpdaterFactory( segmentChangesFetcher: ISegmentChangesFetcher, segments: ISegmentsCacheBase, readiness?: IReadinessManager, + requestTimeoutBeforeReady?: number, + retriesOnFailureBeforeReady?: number, ): ISegmentChangesUpdater { let readyOnAlreadyExistentState = true; - function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean): Promise { + function _promiseDecorator(promise: Promise) { + if (readyOnAlreadyExistentState && requestTimeoutBeforeReady) promise = timeout(requestTimeoutBeforeReady, promise); + return promise; + } + + function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean, retries?: number): Promise { log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing segment ${segmentName}`); let sincePromise = Promise.resolve(segments.getChangeNumber(segmentName)); @@ -35,13 +43,19 @@ export function segmentChangesUpdaterFactory( // if fetchOnlyNew flag, avoid processing already fetched segments return fetchOnlyNew && since !== undefined ? false : - segmentChangesFetcher(since || -1, segmentName, noCache, till).then((changes) => { + segmentChangesFetcher(since || -1, segmentName, noCache, till, _promiseDecorator).then((changes) => { return Promise.all(changes.map(x => { log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`); return segments.update(segmentName, x.added, x.removed, x.till); })).then((updates) => { return updates.some(update => update); }); + }).catch(error => { + if (retries) { + log.warn(`${LOG_PREFIX_SYNC_SEGMENTS}Retrying fetch of segment ${segmentName} (attempt #${retries}). Reason: ${error}`); + return updateSegment(segmentName, noCache, till, fetchOnlyNew, retries - 1); + } + throw error; }); }); } @@ -63,8 +77,7 @@ export function segmentChangesUpdaterFactory( let segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments()); return segmentsPromise.then(segmentNames => { - // Async fetchers - const updaters = segmentNames.map(segmentName => updateSegment(segmentName, noCache, till, fetchOnlyNew)); + const updaters = segmentNames.map(segmentName => updateSegment(segmentName, noCache, till, fetchOnlyNew, readyOnAlreadyExistentState ? retriesOnFailureBeforeReady : 0)); return Promise.all(updaters).then(shouldUpdateFlags => { // if at least one segment fetch succeeded, mark segments ready diff --git a/src/sync/polling/updaters/splitChangesUpdater.ts b/src/sync/polling/updaters/splitChangesUpdater.ts index 6c6371e3..cdff16af 100644 --- a/src/sync/polling/updaters/splitChangesUpdater.ts +++ b/src/sync/polling/updaters/splitChangesUpdater.ts @@ -120,8 +120,8 @@ export function splitChangesUpdaterFactory( storage: Pick, splitFiltersValidation: ISplitFiltersValidation, splitsEventEmitter?: ISplitsEventEmitter, - requestTimeoutBeforeReady: number = 0, - retriesOnFailureBeforeReady: number = 0, + requestTimeoutBeforeReady = 0, + retriesOnFailureBeforeReady = 0, isClientSide?: boolean ): SplitChangesUpdater { const { splits, rbSegments, segments } = storage; @@ -201,14 +201,13 @@ export function splitChangesUpdaterFactory( }); }) .catch(error => { - log.warn(SYNC_SPLITS_FETCH_FAILS, [error]); - if (startingUp && retriesOnFailureBeforeReady > retry) { retry += 1; - log.info(SYNC_SPLITS_FETCH_RETRY, [retry, error]); + log.warn(SYNC_SPLITS_FETCH_RETRY, [retry, error]); return _splitChangesUpdater(sinces, retry); } else { startingUp = false; + log.warn(SYNC_SPLITS_FETCH_FAILS, [error]); } return false; }); From 8f2fb26f11dc1119a4a0c0dff4c3b5c563b1f272 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 16 Oct 2025 12:33:18 -0300 Subject: [PATCH 2/4] Polishing --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2060a5a0..755f0f8b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ 2.8.0 (October XX, 2025) - - Updated the SDK’s initial synchronization in Node.js (server-side) to use `startup.requestTimeoutBeforeReady` and `startup.retriesOnFailureBeforeReady` to control the timeout and retry behavior of segment requests. + - Updated the SDK’s initial synchronization in Node.js (server-side) to use the `startup.requestTimeoutBeforeReady` and `startup.retriesOnFailureBeforeReady` options to control the timeout and retry behavior of segment requests. 2.7.1 (October 8, 2025) - Bugfix - Update `debug` option to support log levels when `logger` option is used. From e15e24fdadc8b3ae218b633226551eda9a85161a Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 25 Nov 2025 18:35:48 -0300 Subject: [PATCH 3/4] rc --- package-lock.json | 43 +++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6093d8d..79326f49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.8.0", + "version": "2.8.1-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.8.0", + "version": "2.8.1-rc.0", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -617,10 +617,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -3381,10 +3382,11 @@ } }, "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -6302,10 +6304,11 @@ "dev": true }, "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -8596,9 +8599,9 @@ } }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -10424,9 +10427,9 @@ "dev": true }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -12814,9 +12817,9 @@ "dev": true }, "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "requires": { "argparse": "^1.0.7", diff --git a/package.json b/package.json index ab0a1d2f..638daadd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.8.0", + "version": "2.8.1-rc.0", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", From 001cdc2b433a0b1c487f69caff89523d9145c401 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 26 Nov 2025 13:54:03 -0300 Subject: [PATCH 4/4] stable --- .github/workflows/ci.yml | 4 ++-- .github/workflows/update-license-year.yml | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2128f2b..dec7b190 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,10 +19,10 @@ jobs: - 6379:6379 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up nodejs - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: # @TODO: rollback to 'lts/*' node-version: '22' diff --git a/.github/workflows/update-license-year.yml b/.github/workflows/update-license-year.yml index 7e0a945f..bfd31318 100644 --- a/.github/workflows/update-license-year.yml +++ b/.github/workflows/update-license-year.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/package-lock.json b/package-lock.json index 79326f49..7bf8ffce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.8.1-rc.0", + "version": "2.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.8.1-rc.0", + "version": "2.9.0", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", diff --git a/package.json b/package.json index 638daadd..d68c8e55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.8.1-rc.0", + "version": "2.9.0", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js",