Skip to content

Commit 574d814

Browse files
Merge pull request #896 from splitio/handle-segmentchanges-timeout
Updated SDK’s initial sync to support timeout and retry of segment requests in server-side
2 parents 34fa535 + fc3150c commit 574d814

File tree

10 files changed

+96
-54
lines changed

10 files changed

+96
-54
lines changed

.github/workflows/ci-cd.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
runs-on: ubuntu-latest
2323
steps:
2424
- name: Checkout code
25-
uses: actions/checkout@v4
25+
uses: actions/checkout@v5
2626

2727
- name: Install Redis
2828
run: |
@@ -33,7 +33,7 @@ jobs:
3333
run: redis-cli ping
3434

3535
- name: Setup Node.js
36-
uses: actions/setup-node@v4
36+
uses: actions/setup-node@v6
3737
with:
3838
node-version: 'lts/*'
3939
cache: 'npm'
@@ -58,7 +58,7 @@ jobs:
5858

5959
- name: Store assets
6060
if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/development' || github.ref == 'refs/heads/main') }}
61-
uses: actions/upload-artifact@v4
61+
uses: actions/upload-artifact@v5
6262
with:
6363
name: assets
6464
path: umd/
@@ -80,7 +80,7 @@ jobs:
8080

8181
steps:
8282
- name: Download assets
83-
uses: actions/download-artifact@v4
83+
uses: actions/download-artifact@v6
8484
with:
8585
name: assets
8686
path: umd
@@ -119,7 +119,7 @@ jobs:
119119

120120
steps:
121121
- name: Download assets
122-
uses: actions/download-artifact@v4
122+
uses: actions/download-artifact@v6
123123
with:
124124
name: assets
125125
path: umd

.github/workflows/sonar-scan.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ jobs:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- name: Checkout code
18-
uses: actions/checkout@v4
18+
uses: actions/checkout@v5
1919
with:
2020
fetch-depth: 0
2121

2222
- name: Set up Node.js
23-
uses: actions/setup-node@v4
23+
uses: actions/setup-node@v6
2424
with:
2525
node-version: 'lts/*'
2626
cache: 'npm'
@@ -36,7 +36,7 @@ jobs:
3636

3737
- name: SonarQube Scan (Push)
3838
if: github.event_name == 'push'
39-
uses: SonarSource/sonarcloud-github-action@v1.6
39+
uses: SonarSource/sonarqube-scan-action@v6
4040
env:
4141
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
4242
with:
@@ -47,9 +47,10 @@ jobs:
4747
-Dsonar.projectKey=${{ github.event.repository.name }}
4848
-Dsonar.links.ci="https://github.com/splitio/${{ github.event.repository.name }}/actions"
4949
-Dsonar.links.scm="https://github.com/splitio/${{ github.event.repository.name }}"
50+
5051
- name: SonarQube Scan (Pull Request)
5152
if: github.event_name == 'pull_request'
52-
uses: SonarSource/sonarcloud-github-action@v1.6
53+
uses: SonarSource/sonarqube-scan-action@v6
5354
env:
5455
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
5556
with:

CHANGES.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
11.9.0 (November 26, 2025)
2+
- Updated @splitsoftware/splitio-commons package to version 2.9.0, which:
3+
- updates 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.
4+
- updates the order of storage operations to prevent inconsistent states when using the `LOCALSTORAGE` storage type and the browser’s `localStorage` fails due to quota limits.
5+
16
11.8.0 (October 30, 2025)
27
- Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs.
38
- Added `client.getStatus()` method to retrieve the client readiness status properties (`isReady`, `isReadyFromCache`, etc).

package-lock.json

Lines changed: 37 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio",
3-
"version": "11.8.0",
3+
"version": "11.9.0",
44
"description": "Split SDK",
55
"files": [
66
"README.md",
@@ -38,7 +38,7 @@
3838
"node": ">=14.0.0"
3939
},
4040
"dependencies": {
41-
"@splitsoftware/splitio-commons": "2.8.0",
41+
"@splitsoftware/splitio-commons": "2.9.0",
4242
"bloom-filters": "^3.0.4",
4343
"ioredis": "^4.28.0",
4444
"js-yaml": "^3.13.1",

src/__tests__/browserSuites/ready-from-cache.spec.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,18 +108,34 @@ export const expectedHashWithFilter = '2ce5cc38'; // for SDK key '<fake-token-rf
108108

109109
export default function (fetchMock, assert) {
110110

111-
assert.test(t => { // Testing when we start from scratch
111+
assert.test(t => { // Testing when we start from scratch, with an initial localStorage write operation fail (should retry splitChanges with -1)
112112
const testUrls = {
113113
sdk: 'https://sdk.baseurl/readyFromCacheEmpty',
114114
events: 'https://events.baseurl/readyFromCacheEmpty'
115115
};
116116
localStorage.clear();
117+
118+
// simulate a localStorage failure when saving a FF and a membership
119+
const originalSetItem = localStorage.setItem;
120+
localStorage.setItem = (key, value) => {
121+
if (key.includes('[email protected].')) {
122+
throw new Error('localStorage.setItem failed');
123+
}
124+
if (key.includes('.split.')) {
125+
localStorage.setItem = originalSetItem;
126+
throw new Error('localStorage.setItem failed');
127+
}
128+
return originalSetItem.call(localStorage, key, value);
129+
};
130+
117131
t.plan(4);
118132

119-
fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 });
120-
fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas });
121-
fetchMock.get(testUrls.sdk + '/memberships/nicolas2%40split.io', { status: 200, body: { 'ms': {} } });
122-
fetchMock.get(testUrls.sdk + '/memberships/nicolas3%40split.io', { status: 200, body: { 'ms': {} } });
133+
fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 });
134+
fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 }); // retry
135+
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas });
136+
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas }); // retry
137+
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas2%40split.io', { status: 200, body: { 'ms': {} } });
138+
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas3%40split.io', { status: 200, body: { 'ms': {} } });
123139

124140
const splitio = SplitFactory({
125141
...baseConfig,

src/__tests__/consumer/node_redis.spec.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,14 @@ tape('Node.js Redis', function (t) {
550550
t.test('Connection error', assert => {
551551
initializeRedisServer()
552552
.then((server) => {
553-
const sdk = SplitFactory(config);
553+
const sdk = SplitFactory({
554+
...config,
555+
fallbackTreatments: {
556+
byFlag: {
557+
'always-on': 'control_always_on'
558+
}
559+
}
560+
});
554561
const client = sdk.client();
555562

556563
client.once(client.Event.SDK_READY_TIMED_OUT, assert.fail);
@@ -585,7 +592,7 @@ tape('Node.js Redis', function (t) {
585592
assert.equal(await client.getTreatment('UT_Segment_member', 'UT_NOT_SET_MATCHER', {
586593
permissions: ['not_matching']
587594
}), 'control', 'In the event of a Redis error like a disconnection, getTreatments should not hang but resolve to "control".');
588-
assert.equal(await client.getTreatment('UT_Segment_member', 'always-on'), 'control', 'In the event of a Redis error like a disconnection, getTreatments should not hang but resolve to "control".');
595+
assert.equal(await client.getTreatment('UT_Segment_member', 'always-on'), 'control_always_on', 'In the event of a Redis error like a disconnection, getTreatments should not hang but resolve to fallback treatment or "control".');
589596

590597
assert.false(await client.track('[email protected]', 'user', 'test.redis.event', 18), 'In the event of a Redis error like a disconnection, track should resolve to false.');
591598

src/__tests__/nodeSuites/ready-promise.spec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ export default function readyPromiseAssertions(key, fetchMock, assert) {
105105
// /splitChanges takes longer than 'requestTimeoutBeforeReady' only for the first attempt
106106
fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', splitChangesMock1, { delay: fromSecondsToMillis(config.startup.requestTimeoutBeforeReady) + 20 });
107107
fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', splitChangesMock1, { delay: fromSecondsToMillis(config.startup.requestTimeoutBeforeReady) - 20 });
108-
mockSegmentChanges(fetchMock, new RegExp(config.urls.sdk + '/segmentChanges/*'), ['some_key']);
108+
mockSegmentChanges(fetchMock, new RegExp(config.urls.sdk + '/segmentChanges/(splitters|developers|segment_excluded_by_rbs)'), ['some_key']);
109+
fetchMock.getOnce(config.urls.sdk + '/segmentChanges/employees?since=-1', { status: 500, body: 'server error' });
110+
fetchMock.getOnce(config.urls.sdk + '/segmentChanges/employees?since=-1', { since: -1, till: 10, name: 'employees', added: ['some_key'], removed: [] });
111+
fetchMock.getOnce(config.urls.sdk + '/segmentChanges/employees?since=10', { since: 10, till: 10, name: 'employees', added: [], removed: [] });
112+
109113
fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', 200);
110114
fetchMock.postOnce(config.urls.events + '/testImpressions/count', 200);
111115

src/settings/defaults/version.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const packageVersion = '11.8.0';
1+
export const packageVersion = '11.9.0';

ts-tests/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ promise = client.destroy();
263263
promise = SDK.destroy();
264264
// @TODO not public yet
265265
// promise = client.flush();
266-
const promiseWhenReadyFromCache: Promise<boolean> = client.whenReadyFromCache();
266+
let promiseWhenReadyFromCache: Promise<boolean> = client.whenReadyFromCache();
267267

268268
// Get readiness status
269269
let status: SplitIO.ReadinessStatus = client.getStatus();
@@ -378,10 +378,15 @@ nodeEventEmitter = asyncClient;
378378

379379
// Ready, destroy and flush (same as for sync client, just for interface checking)
380380
promise = asyncClient.ready();
381+
promise = asyncClient.whenReady();
381382
promise = asyncClient.destroy();
382383
promise = AsyncSDK.destroy();
383384
// @TODO not public yet
384385
// promise = asyncClient.flush();
386+
promiseWhenReadyFromCache = asyncClient.whenReadyFromCache();
387+
388+
// Get readiness status
389+
status = asyncClient.getStatus();
385390

386391
// We can call getTreatment but always with a key.
387392
asyncTreatment = asyncClient.getTreatment(splitKey, 'mySplit');

0 commit comments

Comments
 (0)