Skip to content

Commit 95ca89b

Browse files
committed
ipc: improve serialization perf, add array support
fixes microsoft#62950
1 parent f9a9e4e commit 95ca89b

2 files changed

Lines changed: 133 additions & 40 deletions

File tree

src/vs/base/parts/ipc/node/ipc.ts

Lines changed: 117 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -99,53 +99,112 @@ export interface IRoutingChannelClient {
9999
getChannel<T extends IChannel>(channelName: string, router: IClientRouter): T;
100100
}
101101

102-
enum BodyType {
103-
Undefined,
104-
String,
105-
Buffer,
106-
Object
102+
interface IReader {
103+
read(bytes: number): Buffer;
107104
}
108105

109-
const empty = Buffer.allocUnsafe(0);
106+
interface IWriter {
107+
write(buffer: Buffer): void;
108+
}
110109

111-
function serializeBody(body: any): { buffer: Buffer, type: BodyType } {
112-
if (typeof body === 'undefined') {
113-
return { buffer: empty, type: BodyType.Undefined };
114-
} else if (typeof body === 'string') {
115-
return { buffer: Buffer.from(body), type: BodyType.String };
116-
} else if (Buffer.isBuffer(body)) {
117-
return { buffer: body, type: BodyType.Buffer };
118-
} else {
119-
return { buffer: Buffer.from(JSON.stringify(body)), type: BodyType.Object };
110+
class BufferReader implements IReader {
111+
112+
private pos = 0;
113+
114+
constructor(private buffer: Buffer) { }
115+
116+
read(bytes: number): Buffer {
117+
const result = this.buffer.slice(this.pos, this.pos + bytes);
118+
this.pos += result.length;
119+
return result;
120+
}
121+
}
122+
123+
class BufferWriter implements IWriter {
124+
125+
private buffers: Buffer[] = [];
126+
127+
get buffer(): Buffer {
128+
return Buffer.concat(this.buffers);
129+
}
130+
131+
write(buffer: Buffer): void {
132+
this.buffers.push(buffer);
120133
}
121134
}
122135

123-
function serialize(header: any, body: any = undefined): Buffer {
124-
const headerSizeBuffer = Buffer.allocUnsafe(4);
125-
const { buffer: bodyBuffer, type: bodyType } = serializeBody(body);
126-
const headerBuffer = Buffer.from(JSON.stringify([header, bodyType]));
127-
headerSizeBuffer.writeUInt32BE(headerBuffer.byteLength, 0);
136+
enum DataType {
137+
Undefined = 0,
138+
String = 1,
139+
Buffer = 2,
140+
Array = 3,
141+
Object = 4
142+
}
128143

129-
return Buffer.concat([headerSizeBuffer, headerBuffer, bodyBuffer]);
144+
function createSizeBuffer(size: number): Buffer {
145+
const result = Buffer.allocUnsafe(4);
146+
result.writeUInt32BE(size, 0);
147+
return result;
130148
}
131149

132-
function deserializeBody(bodyBuffer: Buffer, bodyType: BodyType): any {
133-
switch (bodyType) {
134-
case BodyType.Undefined: return undefined;
135-
case BodyType.String: return bodyBuffer.toString();
136-
case BodyType.Buffer: return bodyBuffer;
137-
case BodyType.Object: return JSON.parse(bodyBuffer.toString());
150+
function readSizeBuffer(reader: IReader): number {
151+
return reader.read(4).readUInt32BE(0);
152+
}
153+
154+
const BufferPresets = {
155+
Undefined: Buffer.alloc(1, DataType.Undefined),
156+
String: Buffer.alloc(1, DataType.String),
157+
Buffer: Buffer.alloc(1, DataType.Buffer),
158+
Array: Buffer.alloc(1, DataType.Array),
159+
Object: Buffer.alloc(1, DataType.Object)
160+
};
161+
162+
function serialize(writer: IWriter, data: any): void {
163+
if (typeof data === 'undefined') {
164+
writer.write(BufferPresets.Undefined);
165+
} else if (typeof data === 'string') {
166+
const buffer = Buffer.from(data);
167+
writer.write(BufferPresets.String);
168+
writer.write(createSizeBuffer(buffer.length));
169+
writer.write(buffer);
170+
} else if (Buffer.isBuffer(data)) {
171+
writer.write(BufferPresets.Buffer);
172+
writer.write(createSizeBuffer(data.length));
173+
writer.write(data);
174+
} else if (Array.isArray(data)) {
175+
writer.write(BufferPresets.Array);
176+
writer.write(createSizeBuffer(data.length));
177+
178+
for (const el of data) {
179+
serialize(writer, el);
180+
}
181+
} else {
182+
const buffer = Buffer.from(JSON.stringify(data));
183+
writer.write(BufferPresets.Object);
184+
writer.write(createSizeBuffer(buffer.length));
185+
writer.write(buffer);
138186
}
139187
}
140188

141-
function deserialize(buffer: Buffer): { header: any, body: any } {
142-
const headerSize = buffer.readUInt32BE(0);
143-
const headerBuffer = buffer.slice(4, 4 + headerSize);
144-
const bodyBuffer = buffer.slice(4 + headerSize);
145-
const [header, bodyType] = JSON.parse(headerBuffer.toString());
146-
const body = deserializeBody(bodyBuffer, bodyType);
189+
function deserialize(reader: IReader): any {
190+
const type = reader.read(1).readUInt8(0);
191+
192+
switch (type) {
193+
case DataType.Undefined: return undefined;
194+
case DataType.String: return reader.read(readSizeBuffer(reader)).toString();
195+
case DataType.Buffer: return reader.read(readSizeBuffer(reader));
196+
case DataType.Array: {
197+
const length = readSizeBuffer(reader);
198+
const result: any[] = [];
199+
200+
for (let i = 0; i < length; i++) {
201+
result.push(deserialize(reader));
202+
}
147203

148-
return { header, body };
204+
return result;
205+
}
206+
case DataType.Object: return JSON.parse(reader.read(readSizeBuffer(reader)).toString());
207+
}
149208
}
150209

151210
export class ChannelServer implements IChannelServer, IDisposable {
@@ -166,16 +225,23 @@ export class ChannelServer implements IChannelServer, IDisposable {
166225
private sendResponse(response: IRawResponse): void {
167226
switch (response.type) {
168227
case ResponseType.Initialize:
169-
return this.sendBuffer(serialize([response.type]));
228+
return this.send([response.type]);
170229

171230
case ResponseType.PromiseSuccess:
172231
case ResponseType.PromiseError:
173232
case ResponseType.EventFire:
174233
case ResponseType.PromiseErrorObj:
175-
return this.sendBuffer(serialize([response.type, response.id], response.data));
234+
return this.send([response.type, response.id], response.data);
176235
}
177236
}
178237

238+
private send(header: any, body: any = undefined): void {
239+
const writer = new BufferWriter();
240+
serialize(writer, header);
241+
serialize(writer, body);
242+
this.sendBuffer(writer.buffer);
243+
}
244+
179245
private sendBuffer(message: Buffer): void {
180246
try {
181247
this.protocol.send(message);
@@ -185,7 +251,9 @@ export class ChannelServer implements IChannelServer, IDisposable {
185251
}
186252

187253
private onRawMessage(message: Buffer): void {
188-
const { header, body } = deserialize(message);
254+
const reader = new BufferReader(message);
255+
const header = deserialize(reader);
256+
const body = deserialize(reader);
189257
const type = header[0] as RequestType;
190258

191259
switch (type) {
@@ -397,14 +465,21 @@ export class ChannelClient implements IChannelClient, IDisposable {
397465
switch (request.type) {
398466
case RequestType.Promise:
399467
case RequestType.EventListen:
400-
return this.sendBuffer(serialize([request.type, request.id, request.channelName, request.name], request.arg));
468+
return this.send([request.type, request.id, request.channelName, request.name], request.arg);
401469

402470
case RequestType.PromiseCancel:
403471
case RequestType.EventDispose:
404-
return this.sendBuffer(serialize([request.type, request.id]));
472+
return this.send([request.type, request.id]);
405473
}
406474
}
407475

476+
private send(header: any, body: any = undefined): void {
477+
const writer = new BufferWriter();
478+
serialize(writer, header);
479+
serialize(writer, body);
480+
this.sendBuffer(writer.buffer);
481+
}
482+
408483
private sendBuffer(message: Buffer): void {
409484
try {
410485
this.protocol.send(message);
@@ -414,7 +489,9 @@ export class ChannelClient implements IChannelClient, IDisposable {
414489
}
415490

416491
private onBuffer(message: Buffer): void {
417-
const { header, body } = deserialize(message);
492+
const reader = new BufferReader(message);
493+
const header = deserialize(reader);
494+
const body = deserialize(reader);
418495
const type: ResponseType = header[0];
419496

420497
switch (type) {

src/vs/base/parts/ipc/test/node/ipc.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ interface ITestService {
9999
error(message: string): Thenable<void>;
100100
neverComplete(): Thenable<void>;
101101
neverCompleteCT(cancellationToken: CancellationToken): Thenable<void>;
102+
buffersLength(buffers: Buffer[]): Thenable<number>;
102103

103104
pong: Event<string>;
104105
}
@@ -128,6 +129,10 @@ class TestService implements ITestService {
128129
return new Promise((_, e) => cancellationToken.onCancellationRequested(() => e(canceled())));
129130
}
130131

132+
buffersLength(buffers: Buffer[]): Thenable<number> {
133+
return Promise.resolve(buffers.reduce((r, b) => r + b.length, 0));
134+
}
135+
131136
ping(msg: string): void {
132137
this._pong.fire(msg);
133138
}
@@ -138,6 +143,7 @@ interface ITestChannel extends IChannel {
138143
call(command: 'error'): Thenable<void>;
139144
call(command: 'neverComplete'): Thenable<void>;
140145
call(command: 'neverCompleteCT', arg: undefined, cancellationToken: CancellationToken): Thenable<void>;
146+
call(command: 'buffersLength', arg: [Buffer, Buffer]): Thenable<void>;
141147
call<T>(command: string, arg?: any, cancellationToken?: CancellationToken): Thenable<T>;
142148

143149
listen(event: 'pong'): Event<string>;
@@ -154,6 +160,7 @@ class TestChannel implements ITestChannel {
154160
case 'error': return this.service.error(arg);
155161
case 'neverComplete': return this.service.neverComplete();
156162
case 'neverCompleteCT': return this.service.neverCompleteCT(cancellationToken);
163+
case 'buffersLength': return this.service.buffersLength(arg);
157164
default: return Promise.reject(new Error('not implemented'));
158165
}
159166
}
@@ -189,6 +196,10 @@ class TestChannelClient implements ITestService {
189196
neverCompleteCT(cancellationToken: CancellationToken): Thenable<void> {
190197
return this.channel.call('neverCompleteCT', undefined, cancellationToken);
191198
}
199+
200+
buffersLength(buffers: Buffer[]): Thenable<number> {
201+
return this.channel.call('buffersLength', buffers);
202+
}
192203
}
193204

194205
suite('Base IPC', function () {
@@ -294,5 +305,10 @@ suite('Base IPC', function () {
294305

295306
assert.deepEqual(messages, ['hello', 'world']);
296307
});
308+
309+
test('buffers in arrays', async function () {
310+
const r = await ipcService.buffersLength([Buffer.allocUnsafe(2), Buffer.allocUnsafe(3)]);
311+
return assert.equal(r, 5);
312+
});
297313
});
298314
});

0 commit comments

Comments
 (0)