Skip to content

Commit c899849

Browse files
authored
refactor: update node ws channel code (#3241)
1 parent 32419c9 commit c899849

110 files changed

Lines changed: 2480 additions & 1598 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

jest.config.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ const { pathsToModuleNameMapper } = require('ts-jest');
33
const tsconfig = require('./configs/ts/tsconfig.resolve.json');
44

55
const tsModuleNameMapper = pathsToModuleNameMapper(tsconfig.compilerOptions.paths, { prefix: '<rootDir>/configs/' });
6+
7+
/**
8+
* @type {import('@jest/types').Config.InitialOptions}
9+
*/
610
const baseConfig = {
711
preset: 'ts-jest',
812
testRunner: 'jest-jasmine2',
913
resolver: '<rootDir>/tools/dev-tool/src/jest-resolver.js',
10-
coverageProvider: process.env.JEST_COVERAGE_PROVIDER || 'babel',
1114
// https://dev.to/vantanev/make-your-jest-tests-up-to-20-faster-by-changing-a-single-setting-i36
1215
maxWorkers: 2,
1316
collectCoverageFrom: [
@@ -58,6 +61,11 @@ const baseConfig = {
5861
},
5962
},
6063
};
64+
65+
if (process.env.JEST_COVERAGE_PROVIDER) {
66+
baseConfig.coverageProvider = process.env.JEST_COVERAGE_PROVIDER;
67+
}
68+
6169
/**
6270
* @type {import('@jest/types').Config.InitialOptions}
6371
*/

jest.setup.base.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const { TextEncoder, TextDecoder } = require('util');
2+
13
// Do not log message on GitHub Actions.
24
// Because these logs will affect the detection of real problems.
35
const _console = global.console;
@@ -13,6 +15,9 @@ global.console = process.env.CI
1315
}
1416
: _console;
1517

18+
global.TextEncoder = TextEncoder;
19+
global.TextDecoder = TextDecoder;
20+
1621
process.on('unhandledRejection', (error) => {
1722
_console.error('unhandledRejection', error);
1823
if (process.env.EXIT_ON_UNHANDLED_REJECTION) {

jest.setup.node.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ global.document.queryCommandSupported = () => {};
5454
global.document.execCommand = () => {};
5555
global.HTMLElement = jsdom.window.HTMLElement;
5656
global.self = global;
57-
global.TextEncoder = TextEncoder;
58-
global.TextDecoder = TextDecoder;
5957

6058
global.ElectronIpcRenderer = {
6159
send: () => {},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
"@types/react-is": "^16.7.1",
9696
"@types/socket.io-client": "^1.4.32",
9797
"@types/temp": "^0.9.1",
98-
"@types/ws": "^6.0.1",
98+
"@types/ws": "^8.5.10",
9999
"@typescript-eslint/eslint-plugin": "^5.12.1",
100100
"@typescript-eslint/parser": "^5.12.1",
101101
"async-retry": "^1.3.1",

packages/connection/__test__/browser/index.test.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,39 @@
1-
import { WebSocket, Server } from 'mock-socket';
1+
import { ReconnectingWebSocketConnection } from '@opensumi/ide-connection/lib/common/connection/drivers/reconnecting-websocket';
2+
import { WebSocket, Server } from '@opensumi/mock-socket';
23

34
import { WSChannelHandler } from '../../src/browser/ws-channel-handler';
4-
import { stringify, parse } from '../../src/common/utils';
5+
import { stringify, parse } from '../../src/common/ws-channel';
56
(global as any).WebSocket = WebSocket;
67

8+
const randomPortFn = () => Math.floor(Math.random() * 10000) + 10000;
9+
10+
const randomPort = randomPortFn();
11+
712
describe('connection browser', () => {
813
it('init connection', async () => {
914
jest.setTimeout(20000);
1015

11-
const fakeWSURL = 'ws://localhost:8089';
16+
const fakeWSURL = `ws://localhost:${randomPort}`;
1217
const mockServer = new Server(fakeWSURL);
1318

1419
let receivedHeartbeat = false;
1520
mockServer.on('connection', (socket) => {
1621
socket.on('message', (msg) => {
17-
const msgObj = parse(msg as string);
22+
const msgObj = parse(msg as Uint8Array);
1823
if (msgObj.kind === 'open') {
1924
socket.send(
2025
stringify({
2126
id: msgObj.id,
22-
kind: 'ready',
27+
kind: 'server-ready',
2328
}),
2429
);
25-
} else if (msgObj.kind === 'heartbeat') {
30+
} else if (msgObj.kind === 'ping') {
2631
receivedHeartbeat = true;
2732
}
2833
});
2934
});
3035

31-
const wsChannelHandler = new WSChannelHandler(fakeWSURL, console);
36+
const wsChannelHandler = new WSChannelHandler(ReconnectingWebSocketConnection.forURL(fakeWSURL), console);
3237

3338
await wsChannelHandler.initHandler();
3439
await new Promise<void>((resolve) => {
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Buffers } from '../../src/common/connection/buffers';
2+
3+
describe('Buffers', () => {
4+
it('can append and slice', () => {
5+
const list = new Buffers();
6+
list.push(new Uint8Array([1, 2, 3]));
7+
list.push(new Uint8Array([4, 5, 6]));
8+
9+
expect(list.slice(0, 0)).toEqual(new Uint8Array(0));
10+
expect(list.slice(0, 2)).toEqual(new Uint8Array([1, 2]));
11+
expect(list.slice(0, 7)).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6]));
12+
13+
expect(list.slice(1, 1)).toEqual(new Uint8Array(0));
14+
expect(list.slice(1, 5)).toEqual(new Uint8Array([2, 3, 4, 5]));
15+
expect(list.slice(1, 7)).toEqual(new Uint8Array([2, 3, 4, 5, 6]));
16+
17+
expect(list.slice(2, 2)).toEqual(new Uint8Array(0));
18+
expect(list.slice(2, 6)).toEqual(new Uint8Array([3, 4, 5, 6]));
19+
});
20+
21+
it('can splice', () => {
22+
const list = new Buffers();
23+
list.push(new Uint8Array([1, 2, 3]));
24+
list.push(new Uint8Array([4, 5, 6]));
25+
26+
expect(list.splice(0, 0)).toEqual({ buffers: [], size: 0 });
27+
expect(list.byteLength).toEqual(6);
28+
expect(list.splice(0, 1)).toEqual({ buffers: [new Uint8Array([1])], size: 1 });
29+
expect(list.byteLength).toEqual(5);
30+
expect(list.splice(0, 2)).toEqual({ buffers: [new Uint8Array([2, 3])], size: 2 });
31+
expect(list.byteLength).toEqual(3);
32+
33+
expect(list.splice(0, 0, new Uint8Array([1]))).toEqual({ buffers: [], size: 0 });
34+
expect(list.byteLength).toEqual(4);
35+
expect(list.splice(0, 0, new Uint8Array([2, 3]))).toEqual({ buffers: [], size: 0 });
36+
expect(list.byteLength).toEqual(6);
37+
expect(list.splice(0, 0, new Uint8Array([4, 5, 6]))).toEqual({ buffers: [], size: 0 });
38+
expect(list.byteLength).toEqual(9);
39+
40+
expect(list.buffers).toEqual([
41+
new Uint8Array([4, 5, 6]),
42+
new Uint8Array([2, 3]),
43+
new Uint8Array([1]),
44+
new Uint8Array([4, 5, 6]),
45+
]);
46+
});
47+
it('can copy', () => {
48+
const list = new Buffers();
49+
list.push(new Uint8Array([1, 2, 3]));
50+
51+
list.push(new Uint8Array([4, 5, 6]));
52+
53+
const target = new Uint8Array(7);
54+
55+
list.copy(target, 0);
56+
57+
expect(target).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 0]));
58+
});
59+
60+
it('other', () => {
61+
const list = new Buffers();
62+
list.push(new Uint8Array([1, 2, 3]));
63+
list.push(new Uint8Array([4, 5, 6]));
64+
expect(list.byteLength).toEqual(6);
65+
expect(list.pos(0)).toEqual({ buf: 0, offset: 0 });
66+
expect(list.pos(1)).toEqual({ buf: 0, offset: 1 });
67+
expect(list.pos(2)).toEqual({ buf: 0, offset: 2 });
68+
expect(list.get(0)).toEqual(1);
69+
expect(list.get(1)).toEqual(2);
70+
71+
list.set(0, 9);
72+
list.set(1, 10);
73+
expect(list.get(0)).toEqual(9);
74+
expect(list.get(1)).toEqual(10);
75+
});
76+
});
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/* eslint-disable no-console */
2+
3+
import { BinaryReader } from '@furyjs/fury/dist/lib/reader';
4+
5+
import {
6+
StreamPacketDecoder,
7+
createStreamPacket,
8+
kMagicNumber,
9+
} from '../../src/common/connection/drivers/stream-decoder';
10+
11+
const reader = BinaryReader({});
12+
13+
function round(x: number, count: number) {
14+
return Math.round(x * 10 ** count) / 10 ** count;
15+
}
16+
17+
function createPayload(size: number) {
18+
const payload = new Uint8Array(size);
19+
for (let i = 0; i < size; i++) {
20+
payload[i] = i % 256;
21+
}
22+
23+
return payload;
24+
}
25+
26+
console.time('createPayload');
27+
const p1k = createPayload(1024);
28+
const p64k = createPayload(64 * 1024);
29+
const p128k = createPayload(128 * 1024);
30+
const p1m = createPayload(1024 * 1024);
31+
const p5m = createPayload(5 * 1024 * 1024);
32+
const p10m = createPayload(10 * 1024 * 1024);
33+
34+
const h1m = createPayload(1024 + p1m.byteLength);
35+
h1m.set(p1m, 1024);
36+
37+
const h5m = createPayload(1024 + p5m.byteLength + 233);
38+
h5m.set(p5m, 1024);
39+
40+
console.timeEnd('createPayload');
41+
42+
// 1m
43+
const pressure = 1024 * 1024;
44+
45+
const purePackets = [p1k, p64k, p128k, p5m, p10m].map((v) => [createStreamPacket(v), v] as const);
46+
47+
const mixedPackets = [p1m, p5m].map((v) => {
48+
const sumiPacket = createStreamPacket(v);
49+
const newPacket = createPayload(1024 + sumiPacket.byteLength);
50+
newPacket.set(sumiPacket, 1024);
51+
return [newPacket, v] as const;
52+
});
53+
54+
const packets = [...purePackets, ...mixedPackets];
55+
56+
describe('stream-packet', () => {
57+
it('can create sumi stream packet', () => {
58+
const content = new Uint8Array([1, 2, 3]);
59+
const packet = createStreamPacket(content);
60+
61+
reader.reset(packet);
62+
expect(reader.uint32()).toBe(kMagicNumber);
63+
expect(reader.varUInt32()).toBe(content.byteLength);
64+
expect(Uint8Array.from(reader.buffer(content.byteLength))).toEqual(content);
65+
});
66+
67+
packets.forEach(([packet, expected]) => {
68+
it(`can decode stream packet: ${round(packet.byteLength / 1024 / 1024, 2)}m`, (done) => {
69+
const decoder = new StreamPacketDecoder();
70+
71+
decoder.onData((data) => {
72+
expect(data.byteLength).toEqual(expected.byteLength);
73+
for (let i = 0; i < 10; i++) {
74+
// 随机选一些数据(<= 100字节),对比是否正确,对比整个数组的话,超大 buffer 会很耗时
75+
const start = Math.floor(Math.random() * data.byteLength);
76+
const end = Math.floor(Math.random() * 1024);
77+
78+
expect(data.subarray(start, end)).toEqual(expected.subarray(start, end));
79+
}
80+
decoder.dispose();
81+
done();
82+
});
83+
84+
console.log('write chunk', packet.byteLength);
85+
// write chunk by ${pressure} bytes
86+
for (let i = 0; i < packet.byteLength; i += pressure) {
87+
decoder.push(packet.subarray(i, i + pressure));
88+
logMemoryUsage();
89+
}
90+
91+
logMemoryUsage();
92+
});
93+
});
94+
});
95+
96+
function logMemoryUsage() {
97+
const used = process.memoryUsage();
98+
let text = new Date().toLocaleString('zh') + ' Memory usage:\n';
99+
// eslint-disable-next-line guard-for-in
100+
for (const key in used) {
101+
text += `${key} ${Math.round((used[key] / 1024 / 1024) * 100) / 100} MB\n`;
102+
}
103+
104+
console.log(text);
105+
}

0 commit comments

Comments
 (0)