Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit e2a126c

Browse files
authored
Merge pull request #2222 from matrix-org/bwindels/phasedrollout
Phased rollout of lazy loading
2 parents 15f3d99 + 63f1c41 commit e2a126c

File tree

3 files changed

+144
-2
lines changed

3 files changed

+144
-2
lines changed

src/MatrixClientPeg.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set';
2626
import createMatrixClient from './utils/createMatrixClient';
2727
import SettingsStore from './settings/SettingsStore';
2828
import MatrixActionCreators from './actions/MatrixActionCreators';
29+
import {phasedRollOutExpiredForUser} from "./PhasedRollOut";
2930

3031
interface MatrixClientCreds {
3132
homeserverUrl: string,
@@ -124,8 +125,12 @@ class MatrixClientPeg {
124125
// the react sdk doesn't work without this, so don't allow
125126
opts.pendingEventOrdering = "detached";
126127

127-
if (SettingsStore.isFeatureEnabled('feature_lazyloading')) {
128-
opts.lazyLoadMembers = true;
128+
const LAZY_LOADING_FEATURE = "feature_lazyloading";
129+
if (SettingsStore.isFeatureEnabled(LAZY_LOADING_FEATURE)) {
130+
const userId = this.matrixClient.credentials.userId;
131+
if (phasedRollOutExpiredForUser(userId, LAZY_LOADING_FEATURE, Date.now())) {
132+
opts.lazyLoadMembers = true;
133+
}
129134
}
130135

131136
// Connect the matrix client to the dispatcher

src/PhasedRollOut.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
Copyright 2018 New Vector Ltd
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import SdkConfig from './SdkConfig';
18+
19+
function hashCode(str) {
20+
let hash = 0;
21+
let i;
22+
let chr;
23+
if (str.length === 0) {
24+
return hash;
25+
}
26+
for (i = 0; i < str.length; i++) {
27+
chr = str.charCodeAt(i);
28+
hash = ((hash << 5) - hash) + chr;
29+
hash |= 0;
30+
}
31+
return Math.abs(hash);
32+
}
33+
34+
export function phasedRollOutExpiredForUser(username, feature, now, rollOutConfig = SdkConfig.get().phasedRollOut) {
35+
if (!rollOutConfig) {
36+
console.log(`no phased rollout configuration, so enabling ${feature}`);
37+
return true;
38+
}
39+
const featureConfig = rollOutConfig[feature];
40+
if (!featureConfig) {
41+
console.log(`${feature} doesn't have phased rollout configured, so enabling`);
42+
return true;
43+
}
44+
if (!Number.isFinite(featureConfig.offset) || !Number.isFinite(featureConfig.period)) {
45+
console.error(`phased rollout of ${feature} is misconfigured, ` +
46+
`offset and/or period are not numbers, so disabling`, featureConfig);
47+
return false;
48+
}
49+
50+
const hash = hashCode(username);
51+
//ms -> min, enable users at minute granularity
52+
const bucketRatio = 1000 * 60;
53+
const bucketCount = featureConfig.period / bucketRatio;
54+
const userBucket = hash % bucketCount;
55+
const userMs = userBucket * bucketRatio;
56+
const enableAt = featureConfig.offset + userMs;
57+
const result = now >= enableAt;
58+
const bucketStr = `(bucket ${userBucket}/${bucketCount})`;
59+
if (result) {
60+
console.log(`${feature} enabled for ${username} ${bucketStr}`);
61+
} else {
62+
console.log(`${feature} will be enabled for ${username} in ${Math.ceil((enableAt - now)/1000)}s ${bucketStr}`);
63+
}
64+
return result;
65+
}

test/PhasedRollOut-test.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
Copyright 2018 New Vector Ltd
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
import expect from 'expect';
15+
import {phasedRollOutExpiredForUser} from '../src/PhasedRollOut';
16+
17+
const OFFSET = 6000000;
18+
// phasedRollOutExpiredForUser enables users in bucks of 1 minute
19+
const MS_IN_MINUTE = 60 * 1000;
20+
21+
describe('PhasedRollOut', function() {
22+
it('should return true if phased rollout is not configured', function() {
23+
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 0, null)).toBeTruthy();
24+
});
25+
26+
it('should return true if phased rollout feature is not configured', function() {
27+
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 0, {
28+
"feature_other": {offset: 0, period: 0},
29+
})).toBeTruthy();
30+
});
31+
32+
it('should return false if phased rollout for feature is misconfigured', function() {
33+
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 0, {
34+
"feature_test": {},
35+
})).toBeFalsy();
36+
});
37+
38+
it("should return false if phased rollout hasn't started yet", function() {
39+
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 5000000, {
40+
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE},
41+
})).toBeFalsy();
42+
});
43+
44+
it("should start to return true in bucket 2/10 for '@user:hs'", function() {
45+
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test",
46+
OFFSET + (MS_IN_MINUTE * 2) - 1, {
47+
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
48+
})).toBeFalsy();
49+
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test",
50+
OFFSET + (MS_IN_MINUTE * 2), {
51+
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
52+
})).toBeTruthy();
53+
});
54+
55+
it("should start to return true in bucket 4/10 for 'alice@other-hs'", function() {
56+
expect(phasedRollOutExpiredForUser("alice@other-hs", "feature_test",
57+
OFFSET + (MS_IN_MINUTE * 4) - 1, {
58+
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
59+
})).toBeFalsy();
60+
expect(phasedRollOutExpiredForUser("alice@other-hs", "feature_test",
61+
OFFSET + (MS_IN_MINUTE * 4), {
62+
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
63+
})).toBeTruthy();
64+
});
65+
66+
it("should return true after complete rollout period'", function() {
67+
expect(phasedRollOutExpiredForUser("user:hs", "feature_test",
68+
OFFSET + (MS_IN_MINUTE * 20), {
69+
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
70+
})).toBeTruthy();
71+
});
72+
});

0 commit comments

Comments
 (0)