Skip to content

Commit f2eae66

Browse files
authored
feat: optimize extension host restart logic (#3635)
1 parent 532cd45 commit f2eae66

14 files changed

Lines changed: 310 additions & 135 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { ExtObjectTransfer } from '@opensumi/ide-connection/lib/common/fury-extends/any';
2+
3+
import { AnySerializer } from '../../../lib/common/fury-extends/any';
4+
import { furyFactory } from '../../../lib/common/fury-extends/shared';
5+
6+
describe('any serializer', () => {
7+
it('can serialize and deserialize any type', () => {
8+
const obj = {
9+
a: 1,
10+
aa: 1.23412,
11+
b: '2',
12+
c: true,
13+
d: null,
14+
e: undefined,
15+
};
16+
17+
const factory = furyFactory();
18+
const serializer = new AnySerializer(factory.writer, factory.reader);
19+
20+
factory.writer.reset();
21+
serializer.write(obj);
22+
const buffer = factory.writer.dump();
23+
24+
factory.reader.reset(buffer);
25+
const result = serializer.read();
26+
expect(result).toEqual(obj);
27+
});
28+
29+
it('can serialize and deserialize buf', () => {
30+
const obj = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]);
31+
32+
const factory = furyFactory();
33+
const serializer = new AnySerializer(factory.writer, factory.reader, ExtObjectTransfer);
34+
35+
factory.writer.reset();
36+
serializer.write(obj);
37+
const buffer = factory.writer.dump();
38+
39+
factory.reader.reset(buffer);
40+
const result = Uint8Array.from(serializer.read());
41+
expect(result).toEqual(obj);
42+
43+
const obj2 = {
44+
buf: obj,
45+
};
46+
47+
factory.writer.reset();
48+
serializer.write(obj2);
49+
const buffer2 = factory.writer.dump();
50+
51+
factory.reader.reset(buffer2);
52+
const result2 = Uint8Array.from(serializer.read().buf);
53+
expect(result2).toEqual(obj);
54+
});
55+
});

packages/connection/src/browser/ws-channel-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class WSChannelHandler {
131131
closeEvent: { code, reason },
132132
connectInfo: (navigator as any).connection as ConnectionInfo,
133133
});
134-
this.logger.log(this.LOG_TAG, 'channel close: ', `code: ${code}, reason: ${reason}`);
134+
this.logger.log(this.LOG_TAG, `channel close: code: ${code}, reason: ${reason}`);
135135
});
136136

137137
const deferred = new Deferred<void>();

packages/connection/src/common/fury-extends/any.ts

Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,75 +6,76 @@ export enum ProtocolType {
66
String,
77
Buffer,
88
Number,
9-
Int32,
109
JSONObject,
1110
BigInt,
1211
Array,
13-
Union,
1412
Object,
1513
Undefined,
1614
Null,
1715
Boolean,
1816
}
1917

2018
export class AnySerializer {
21-
constructor(protected writer: BinaryWriter, protected reader: BinaryReader) {}
19+
constructor(
20+
protected writer: BinaryWriter,
21+
protected reader: BinaryReader,
22+
protected objectTransfer?: IObjectTransfer,
23+
) {}
2224

2325
write(data: any) {
2426
const { writer } = this;
2527
const type = typeof data;
2628
writer.reserve(1);
2729

28-
switch (type) {
29-
case 'undefined':
30-
writer.uint8(ProtocolType.Undefined);
31-
break;
32-
case 'string':
33-
writer.uint8(ProtocolType.String);
34-
writer.stringOfVarUInt32(data);
35-
break;
36-
case 'boolean':
37-
writer.reserve(1);
38-
writer.uint8(ProtocolType.Boolean);
39-
writer.uint8(data ? 1 : 0);
40-
break;
41-
case 'number':
42-
writer.reserve(8);
43-
if ((data | 0) === data) {
44-
writer.uint8(ProtocolType.Int32);
45-
writer.int32(data);
46-
} else {
47-
writer.uint8(ProtocolType.Number);
48-
writer.double(data);
49-
}
50-
break;
51-
case 'bigint':
52-
writer.reserve(8);
53-
writer.uint8(ProtocolType.BigInt);
54-
writer.int64(data);
55-
break;
56-
case 'object':
57-
if (data === null) {
58-
writer.uint8(ProtocolType.Null);
59-
} else if (Array.isArray(data)) {
60-
writer.reserve(4);
61-
writer.uint8(ProtocolType.Array);
62-
writer.varUInt32(data.length);
63-
for (const element of data) {
64-
this.write(element);
65-
}
66-
} else if (isUint8Array(data)) {
67-
writer.reserve(4);
68-
writer.uint8(ProtocolType.Buffer);
69-
writer.varUInt32(data.byteLength);
70-
writer.buffer(data);
71-
} else {
72-
writer.uint8(ProtocolType.JSONObject);
73-
writer.stringOfVarUInt32(JSON.stringify(data, ObjectTransfer.replacer));
74-
}
75-
break;
30+
switch (data) {
31+
case null:
32+
writer.uint8(ProtocolType.Null);
33+
return;
7634
default:
77-
throw new Error(`Unknown type ${type}`);
35+
switch (type) {
36+
case 'undefined':
37+
writer.uint8(ProtocolType.Undefined);
38+
break;
39+
case 'string':
40+
writer.uint8(ProtocolType.String);
41+
writer.stringOfVarUInt32(data);
42+
break;
43+
case 'boolean':
44+
writer.reserve(1);
45+
writer.uint8(ProtocolType.Boolean);
46+
writer.uint8(data ? 1 : 0);
47+
break;
48+
case 'number':
49+
writer.reserve(8);
50+
writer.uint8(ProtocolType.Number);
51+
writer.double(data);
52+
break;
53+
case 'bigint':
54+
writer.reserve(8);
55+
writer.uint8(ProtocolType.BigInt);
56+
writer.int64(data);
57+
break;
58+
case 'object':
59+
if (Array.isArray(data)) {
60+
writer.reserve(4);
61+
writer.uint8(ProtocolType.Array);
62+
writer.varUInt32(data.length);
63+
for (const element of data) {
64+
this.write(element);
65+
}
66+
} else if (isUint8Array(data)) {
67+
writer.reserve(4);
68+
writer.uint8(ProtocolType.Buffer);
69+
writer.varUInt32(data.byteLength);
70+
writer.buffer(data);
71+
} else {
72+
writer.uint8(ProtocolType.JSONObject);
73+
writer.stringOfVarUInt32(JSON.stringify(data, this.objectTransfer?.replacer));
74+
}
75+
break;
76+
default:
77+
throw new Error(`Unknown type ${type}`);
78+
}
7879
}
7980
}
8081

@@ -93,13 +94,11 @@ export class AnySerializer {
9394
const length = reader.varUInt32();
9495
return reader.buffer(length);
9596
}
96-
case ProtocolType.Int32:
97-
return reader.int32();
9897
case ProtocolType.Number:
9998
return reader.double();
10099
case ProtocolType.JSONObject: {
101100
const json = reader.stringOfVarUInt32();
102-
return JSON.parse(json, ObjectTransfer.reviver);
101+
return JSON.parse(json, this.objectTransfer?.reviver);
103102
}
104103
case ProtocolType.BigInt:
105104
return reader.int64();
@@ -132,10 +131,19 @@ export class AnySerializer {
132131
}
133132

134133
enum EObjectTransferType {
135-
CODE_URI = 'CODE_URI',
134+
CODE_URI = 'CodeURI',
135+
BUFFER = 'Buffer',
136136
}
137137

138-
class ObjectTransfer {
138+
export interface IObjectTransfer {
139+
replacer(key: string | undefined, value: any): any;
140+
reviver(key: string | undefined, value: any): any;
141+
}
142+
143+
/**
144+
* For transfering object between browser and extension
145+
*/
146+
export class ExtObjectTransfer {
139147
static replacer(key: string | undefined, value: any) {
140148
if (value) {
141149
switch (value.$mid) {
@@ -148,6 +156,24 @@ class ObjectTransfer {
148156
};
149157
}
150158
}
159+
160+
if (value instanceof Uint8Array || value instanceof Uint32Array || value instanceof Uint16Array) {
161+
return {
162+
$type: 'Buffer',
163+
data: Array.from(value),
164+
};
165+
} else if (value instanceof ArrayBuffer) {
166+
return {
167+
$type: 'Buffer',
168+
data: Array.from(new Uint8Array(value)),
169+
};
170+
} else if (value.type === 'Buffer') {
171+
// https://nodejs.org/api/buffer.html#buftojson
172+
return {
173+
$type: 'Buffer',
174+
data: value.data,
175+
};
176+
}
151177
}
152178

153179
return value;
@@ -157,6 +183,8 @@ class ObjectTransfer {
157183
switch (value.$type) {
158184
case EObjectTransferType.CODE_URI:
159185
return Uri.parse(value.data);
186+
case EObjectTransferType.BUFFER:
187+
return Uint8Array.from(value.data);
160188
}
161189
}
162190
return value;

packages/connection/src/common/rpc-service/center.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Deferred } from '@opensumi/ide-core-common';
1+
import { Deferred, DisposableStore, IDisposable, randomString } from '@opensumi/ide-core-common';
22
import { addElement } from '@opensumi/ide-utils/lib/arrays';
33

44
import { METHOD_NOT_REGISTERED } from '../constants';
@@ -12,21 +12,21 @@ import { ProtocolRegistry, ServiceRegistry } from './registry';
1212

1313
import type { MessageConnection } from '@opensumi/vscode-jsonrpc';
1414

15-
const safeProcess: { pid: string } = typeof process === 'undefined' ? { pid: 'unknown' } : (process as any);
15+
export class RPCServiceCenter implements IDisposable {
16+
private _disposables = new DisposableStore();
1617

17-
export class RPCServiceCenter {
1818
public uid: string;
1919

2020
private proxies: ProxyBase<any>[] = [];
2121

22-
private serviceRegistry = new ServiceRegistry();
23-
private protocolRegistry = new ProtocolRegistry();
22+
private serviceRegistry = this._disposables.add(new ServiceRegistry());
23+
private protocolRegistry = this._disposables.add(new ProtocolRegistry());
2424

2525
private deferred = new Deferred<void>();
2626
private logger: ILogger;
2727

2828
constructor(private bench?: IBench, logger?: ILogger) {
29-
this.uid = 'RPCServiceCenter:' + safeProcess.pid;
29+
this.uid = randomString(6);
3030
this.logger = logger || console;
3131
}
3232

@@ -125,6 +125,10 @@ export class RPCServiceCenter {
125125
// or just return `undefined`.
126126
return result.length === 1 ? result[0] : result;
127127
}
128+
129+
dispose(): void {
130+
this._disposables.dispose();
131+
}
128132
}
129133

130134
export function getNotificationName(serviceName: string, name: string) {

packages/connection/src/common/rpc-service/registry.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Emitter } from '@opensumi/ide-core-common';
1+
import { DisposableStore, Emitter, IDisposable } from '@opensumi/ide-core-common';
22

33
import { MessageIO, TSumiProtocol, TSumiProtocolMethod } from '../rpc';
44
import { RPCServiceMethod } from '../types';
@@ -31,13 +31,14 @@ export function getServiceMethods(service: any): string[] {
3131
/**
3232
* Store all executable services, and provide a way to invoke them.
3333
*/
34-
export class ServiceRegistry {
35-
protected emitter = new Emitter<string[]>();
36-
37-
private serviceMethodMap = new Map<PropertyKey, RPCServiceMethod>();
34+
export class ServiceRegistry implements IDisposable {
35+
private _disposables = new DisposableStore();
3836

37+
protected emitter = this._disposables.add(new Emitter<string[]>());
3938
onServicesUpdate = this.emitter.event;
4039

40+
private serviceMethodMap = new Map<PropertyKey, RPCServiceMethod>();
41+
4142
register(name: string, methodFn: RPCServiceMethod) {
4243
this.serviceMethodMap.set(name, methodFn);
4344
this.emitter.fire([name]);
@@ -78,15 +79,20 @@ export class ServiceRegistry {
7879
methods() {
7980
return Array.from(this.serviceMethodMap.keys());
8081
}
82+
83+
dispose() {
84+
this._disposables.dispose();
85+
}
8186
}
8287

8388
export class ProtocolRegistry {
84-
protected emitter = new Emitter<string[]>();
85-
86-
private protocolMap = new Map<PropertyKey, TSumiProtocolMethod>();
89+
protected _disposables = new DisposableStore();
8790

91+
protected emitter = this._disposables.add(new Emitter<string[]>());
8892
onProtocolUpdate = this.emitter.event;
8993

94+
private protocolMap = new Map<PropertyKey, TSumiProtocolMethod>();
95+
9096
addProtocol(
9197
protocol: TSumiProtocol,
9298
options?: {
@@ -118,13 +124,19 @@ export class ProtocolRegistry {
118124
io.loadProtocolMethod(protocol);
119125
}
120126

121-
this.onProtocolUpdate((methods) => {
122-
for (const method of methods) {
123-
const protocol = this.protocolMap.get(method);
124-
if (protocol) {
125-
io.loadProtocolMethod(protocol);
127+
this._disposables.add(
128+
this.onProtocolUpdate((methods) => {
129+
for (const method of methods) {
130+
const protocol = this.protocolMap.get(method);
131+
if (protocol) {
132+
io.loadProtocolMethod(protocol);
133+
}
126134
}
127-
}
128-
});
135+
}),
136+
);
137+
}
138+
139+
dispose() {
140+
this._disposables.dispose();
129141
}
130142
}

0 commit comments

Comments
 (0)