Skip to content

Commit 3a672d7

Browse files
code-asherkylecarbs
authored andcommitted
Convert fully to protobuf (was partially JSON) (#402)
* Convert fully to protobuf (was partially JSON) * Handle all floating promises * Remove stringified proto from trace logging It wasn't proving to be very useful.
1 parent f484781 commit 3a672d7

31 files changed

+4432
-1921
lines changed

packages/protocol/src/browser/client.ts

+75-58
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { Emitter } from "@coder/events";
55
import { logger, field } from "@coder/logger";
66
import { ReadWriteConnection, InitData, SharedProcessData } from "../common/connection";
77
import { Module, ServerProxy } from "../common/proxy";
8-
import { stringify, parse, moduleToProto, protoToModule, protoToOperatingSystem } from "../common/util";
9-
import { Ping, ServerMessage, ClientMessage, MethodMessage, NamedProxyMessage, NumberedProxyMessage, SuccessMessage, FailMessage, EventMessage, CallbackMessage } from "../proto";
8+
import { argumentToProto, protoToArgument, moduleToProto, protoToModule, protoToOperatingSystem } from "../common/util";
9+
import { Argument, Ping, ServerMessage, ClientMessage, Method, Event, Callback } from "../proto";
1010
import { FsModule, ChildProcessModule, NetModule, NodePtyModule, SpdlogModule, TrashModule } from "./modules";
1111

1212
// tslint:disable no-any
@@ -24,8 +24,8 @@ export class Client {
2424
private messageId = 0;
2525
private callbackId = 0;
2626
private readonly proxies = new Map<number | Module, ProxyData>();
27-
private readonly successEmitter = new Emitter<SuccessMessage>();
28-
private readonly failEmitter = new Emitter<FailMessage>();
27+
private readonly successEmitter = new Emitter<Method.Success>();
28+
private readonly failEmitter = new Emitter<Method.Fail>();
2929
private readonly eventEmitter = new Emitter<{ event: string; args: any[]; }>();
3030

3131
private _initData: InitData | undefined;
@@ -129,9 +129,9 @@ export class Client {
129129
field("event listeners", this.eventEmitter.counts),
130130
]);
131131

132-
const message = new FailMessage();
132+
const message = new Method.Fail();
133133
const error = new Error("disconnected");
134-
message.setResponse(stringify(error));
134+
message.setResponse(argumentToProto(error));
135135
this.failEmitter.emit(message);
136136

137137
this.eventEmitter.emit({ event: "disconnected", args: [error] });
@@ -182,20 +182,21 @@ export class Client {
182182
case "kill":
183183
return Promise.resolve();
184184
}
185+
185186
return Promise.reject(
186187
new Error(`Unable to call "${method}" on proxy ${proxyId}: disconnected`),
187188
);
188189
}
189190

190-
const message = new MethodMessage();
191+
const message = new Method();
191192
const id = this.messageId++;
192-
let proxyMessage: NamedProxyMessage | NumberedProxyMessage;
193+
let proxyMessage: Method.Named | Method.Numbered;
193194
if (typeof proxyId === "string") {
194-
proxyMessage = new NamedProxyMessage();
195+
proxyMessage = new Method.Named();
195196
proxyMessage.setModule(moduleToProto(proxyId));
196197
message.setNamedProxy(proxyMessage);
197198
} else {
198-
proxyMessage = new NumberedProxyMessage();
199+
proxyMessage = new Method.Numbered();
199200
proxyMessage.setProxyId(proxyId);
200201
message.setNumberedProxy(proxyMessage);
201202
}
@@ -215,16 +216,14 @@ export class Client {
215216
return callbackId;
216217
};
217218

218-
const stringifiedArgs = args.map((a) => stringify(a, storeCallback));
219219
logger.trace(() => [
220220
"sending",
221221
field("id", id),
222222
field("proxyId", proxyId),
223223
field("method", method),
224-
field("args", stringifiedArgs),
225224
]);
226225

227-
proxyMessage.setArgsList(stringifiedArgs);
226+
proxyMessage.setArgsList(args.map((a) => argumentToProto(a, storeCallback)));
228227

229228
const clientMessage = new ClientMessage();
230229
clientMessage.setMethod(message);
@@ -246,12 +245,12 @@ export class Client {
246245

247246
const d1 = this.successEmitter.event(id, (message) => {
248247
dispose();
249-
resolve(this.parse(message.getResponse(), promise));
248+
resolve(this.protoToArgument(message.getResponse(), promise));
250249
});
251250

252251
const d2 = this.failEmitter.event(id, (message) => {
253252
dispose();
254-
reject(parse(message.getResponse()));
253+
reject(protoToArgument(message.getResponse()));
255254
});
256255
});
257256

@@ -262,42 +261,53 @@ export class Client {
262261
* Handle all messages from the server.
263262
*/
264263
private async handleMessage(message: ServerMessage): Promise<void> {
265-
if (message.hasInit()) {
266-
const init = message.getInit()!;
267-
this._initData = {
268-
dataDirectory: init.getDataDirectory(),
269-
homeDirectory: init.getHomeDirectory(),
270-
tmpDirectory: init.getTmpDirectory(),
271-
workingDirectory: init.getWorkingDirectory(),
272-
os: protoToOperatingSystem(init.getOperatingSystem()),
273-
shell: init.getShell(),
274-
builtInExtensionsDirectory: init.getBuiltinExtensionsDir(),
275-
};
276-
this.initDataEmitter.emit(this._initData);
277-
} else if (message.hasSuccess()) {
278-
this.emitSuccess(message.getSuccess()!);
279-
} else if (message.hasFail()) {
280-
this.emitFail(message.getFail()!);
281-
} else if (message.hasEvent()) {
282-
await this.emitEvent(message.getEvent()!);
283-
} else if (message.hasCallback()) {
284-
await this.runCallback(message.getCallback()!);
285-
} else if (message.hasSharedProcessActive()) {
286-
const sharedProcessActiveMessage = message.getSharedProcessActive()!;
287-
this.sharedProcessActiveEmitter.emit({
288-
socketPath: sharedProcessActiveMessage.getSocketPath(),
289-
logPath: sharedProcessActiveMessage.getLogPath(),
290-
});
291-
} else if (message.hasPong()) {
292-
// Nothing to do since pings are on a timer rather than waiting for the
293-
// next pong in case a message from either the client or server is dropped
294-
// which would break the ping cycle.
295-
} else {
296-
throw new Error("unknown message type");
264+
switch (message.getMsgCase()) {
265+
case ServerMessage.MsgCase.INIT:
266+
const init = message.getInit()!;
267+
this._initData = {
268+
dataDirectory: init.getDataDirectory(),
269+
homeDirectory: init.getHomeDirectory(),
270+
tmpDirectory: init.getTmpDirectory(),
271+
workingDirectory: init.getWorkingDirectory(),
272+
os: protoToOperatingSystem(init.getOperatingSystem()),
273+
shell: init.getShell(),
274+
builtInExtensionsDirectory: init.getBuiltinExtensionsDir(),
275+
};
276+
this.initDataEmitter.emit(this._initData);
277+
break;
278+
case ServerMessage.MsgCase.SUCCESS:
279+
this.emitSuccess(message.getSuccess()!);
280+
break;
281+
case ServerMessage.MsgCase.FAIL:
282+
this.emitFail(message.getFail()!);
283+
break;
284+
case ServerMessage.MsgCase.EVENT:
285+
await this.emitEvent(message.getEvent()!);
286+
break;
287+
case ServerMessage.MsgCase.CALLBACK:
288+
await this.runCallback(message.getCallback()!);
289+
break;
290+
case ServerMessage.MsgCase.SHARED_PROCESS_ACTIVE:
291+
const sharedProcessActiveMessage = message.getSharedProcessActive()!;
292+
this.sharedProcessActiveEmitter.emit({
293+
socketPath: sharedProcessActiveMessage.getSocketPath(),
294+
logPath: sharedProcessActiveMessage.getLogPath(),
295+
});
296+
break;
297+
case ServerMessage.MsgCase.PONG:
298+
// Nothing to do since pings are on a timer rather than waiting for the
299+
// next pong in case a message from either the client or server is dropped
300+
// which would break the ping cycle.
301+
break;
302+
default:
303+
throw new Error("unknown message type");
297304
}
298305
}
299306

300-
private emitSuccess(message: SuccessMessage): void {
307+
/**
308+
* Convert message to a success event.
309+
*/
310+
private emitSuccess(message: Method.Success): void {
301311
logger.trace(() => [
302312
"received resolve",
303313
field("id", message.getId()),
@@ -306,7 +316,10 @@ export class Client {
306316
this.successEmitter.emit(message.getId(), message);
307317
}
308318

309-
private emitFail(message: FailMessage): void {
319+
/**
320+
* Convert message to a fail event.
321+
*/
322+
private emitFail(message: Method.Fail): void {
310323
logger.trace(() => [
311324
"received reject",
312325
field("id", message.getId()),
@@ -322,7 +335,7 @@ export class Client {
322335
* request before it emits. Instead, emit all events from the server so all
323336
* events are always caught on the client.
324337
*/
325-
private async emitEvent(message: EventMessage): Promise<void> {
338+
private async emitEvent(message: Event): Promise<void> {
326339
const eventMessage = message.getNamedEvent()! || message.getNumberedEvent()!;
327340
const proxyId = message.getNamedEvent()
328341
? protoToModule(message.getNamedEvent()!.getModule())
@@ -333,10 +346,9 @@ export class Client {
333346
"received event",
334347
field("proxyId", proxyId),
335348
field("event", event),
336-
field("args", eventMessage.getArgsList()),
337349
]);
338350

339-
const args = eventMessage.getArgsList().map((a) => this.parse(a));
351+
const args = eventMessage.getArgsList().map((a) => this.protoToArgument(a));
340352
this.eventEmitter.emit(proxyId, { event, args });
341353
}
342354

@@ -348,7 +360,7 @@ export class Client {
348360
* also only be used when passed together with the method. If they are sent
349361
* afterward, they may never be called due to timing issues.
350362
*/
351-
private async runCallback(message: CallbackMessage): Promise<void> {
363+
private async runCallback(message: Callback): Promise<void> {
352364
const callbackMessage = message.getNamedCallback()! || message.getNumberedCallback()!;
353365
const proxyId = message.getNamedCallback()
354366
? protoToModule(message.getNamedCallback()!.getModule())
@@ -359,16 +371,15 @@ export class Client {
359371
"running callback",
360372
field("proxyId", proxyId),
361373
field("callbackId", callbackId),
362-
field("args", callbackMessage.getArgsList()),
363374
]);
364-
const args = callbackMessage.getArgsList().map((a) => this.parse(a));
375+
const args = callbackMessage.getArgsList().map((a) => this.protoToArgument(a));
365376
this.getProxy(proxyId).callbacks.get(callbackId)!(...args);
366377
}
367378

368379
/**
369380
* Start the ping loop. Does nothing if already pinging.
370381
*/
371-
private startPinging = (): void => {
382+
private readonly startPinging = (): void => {
372383
if (typeof this.pingTimeout !== "undefined") {
373384
return;
374385
}
@@ -505,10 +516,16 @@ export class Client {
505516
await this.getProxy(proxyId).promise;
506517
}
507518

508-
private parse(value?: string, promise?: Promise<any>): any {
509-
return parse(value, undefined, (id) => this.createProxy(id, promise));
519+
/**
520+
* Same as protoToArgument except provides createProxy.
521+
*/
522+
private protoToArgument(value?: Argument, promise?: Promise<any>): any {
523+
return protoToArgument(value, undefined, (id) => this.createProxy(id, promise));
510524
}
511525

526+
/**
527+
* Get a proxy. Error if it doesn't exist.
528+
*/
512529
private getProxy(proxyId: number | Module): ProxyData {
513530
if (!this.proxies.has(proxyId)) {
514531
throw new Error(`proxy ${proxyId} disposed too early`);

packages/protocol/src/browser/modules/child_process.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { ClientProxy } from "../../common/proxy";
66
import { ChildProcessModuleProxy, ChildProcessProxy, ChildProcessProxies } from "../../node/modules/child_process";
77
import { Readable, Writable } from "./stream";
88

9+
// tslint:disable completed-docs
10+
911
export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.ChildProcess {
1012
public readonly stdin: stream.Writable;
1113
public readonly stdout: stream.Readable;
@@ -23,10 +25,10 @@ export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.C
2325
this.stderr = new Readable(proxyPromises.then((p) => p.stderr!));
2426
this.stdio = [this.stdin, this.stdout, this.stderr];
2527

26-
this.proxy.getPid().then((pid) => {
28+
this.catch(this.proxy.getPid().then((pid) => {
2729
this._pid = pid;
2830
this._connected = true;
29-
});
31+
}));
3032
this.on("disconnect", () => this._connected = false);
3133
this.on("exit", () => {
3234
this._connected = false;
@@ -48,19 +50,19 @@ export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.C
4850

4951
public kill(): void {
5052
this._killed = true;
51-
this.proxy.kill();
53+
this.catch(this.proxy.kill());
5254
}
5355

5456
public disconnect(): void {
55-
this.proxy.disconnect();
57+
this.catch(this.proxy.disconnect());
5658
}
5759

5860
public ref(): void {
59-
this.proxy.ref();
61+
this.catch(this.proxy.ref());
6062
}
6163

6264
public unref(): void {
63-
this.proxy.unref();
65+
this.catch(this.proxy.unref());
6466
}
6567

6668
public send(
@@ -88,6 +90,9 @@ export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.C
8890
return true; // Always true since we can't get this synchronously.
8991
}
9092

93+
/**
94+
* Exit and close the process when disconnected.
95+
*/
9196
protected handleDisconnect(): void {
9297
this.emit("exit", 1);
9398
this.emit("close");

packages/protocol/src/browser/modules/fs.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { FsModuleProxy, Stats as IStats, WatcherProxy, WriteStreamProxy } from "
66
import { Writable } from "./stream";
77

88
// tslint:disable no-any
9+
// tslint:disable completed-docs
910

1011
class StatBatch extends Batch<IStats, { path: fs.PathLike }> {
1112
public constructor(private readonly proxy: FsModuleProxy) {
@@ -39,7 +40,7 @@ class ReaddirBatch extends Batch<Buffer[] | fs.Dirent[] | string[], { path: fs.P
3940

4041
class Watcher extends ClientProxy<WatcherProxy> implements fs.FSWatcher {
4142
public close(): void {
42-
this.proxy.close();
43+
this.catch(this.proxy.close());
4344
}
4445

4546
protected handleDisconnect(): void {
@@ -57,7 +58,7 @@ class WriteStream extends Writable<WriteStreamProxy> implements fs.WriteStream {
5758
}
5859

5960
public close(): void {
60-
this.proxy.close();
61+
this.catch(this.proxy.close());
6162
}
6263
}
6364

0 commit comments

Comments
 (0)