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.
This commit is contained in:
Asher
2019-04-02 17:44:28 -05:00
committed by Kyle Carberry
parent f484781693
commit 3a672d725a
31 changed files with 5788 additions and 3277 deletions

View File

@@ -5,8 +5,8 @@ import { Emitter } from "@coder/events";
import { logger, field } from "@coder/logger";
import { ReadWriteConnection, InitData, SharedProcessData } from "../common/connection";
import { Module, ServerProxy } from "../common/proxy";
import { stringify, parse, moduleToProto, protoToModule, protoToOperatingSystem } from "../common/util";
import { Ping, ServerMessage, ClientMessage, MethodMessage, NamedProxyMessage, NumberedProxyMessage, SuccessMessage, FailMessage, EventMessage, CallbackMessage } from "../proto";
import { argumentToProto, protoToArgument, moduleToProto, protoToModule, protoToOperatingSystem } from "../common/util";
import { Argument, Ping, ServerMessage, ClientMessage, Method, Event, Callback } from "../proto";
import { FsModule, ChildProcessModule, NetModule, NodePtyModule, SpdlogModule, TrashModule } from "./modules";
// tslint:disable no-any
@@ -24,8 +24,8 @@ export class Client {
private messageId = 0;
private callbackId = 0;
private readonly proxies = new Map<number | Module, ProxyData>();
private readonly successEmitter = new Emitter<SuccessMessage>();
private readonly failEmitter = new Emitter<FailMessage>();
private readonly successEmitter = new Emitter<Method.Success>();
private readonly failEmitter = new Emitter<Method.Fail>();
private readonly eventEmitter = new Emitter<{ event: string; args: any[]; }>();
private _initData: InitData | undefined;
@@ -129,9 +129,9 @@ export class Client {
field("event listeners", this.eventEmitter.counts),
]);
const message = new FailMessage();
const message = new Method.Fail();
const error = new Error("disconnected");
message.setResponse(stringify(error));
message.setResponse(argumentToProto(error));
this.failEmitter.emit(message);
this.eventEmitter.emit({ event: "disconnected", args: [error] });
@@ -182,20 +182,21 @@ export class Client {
case "kill":
return Promise.resolve();
}
return Promise.reject(
new Error(`Unable to call "${method}" on proxy ${proxyId}: disconnected`),
);
}
const message = new MethodMessage();
const message = new Method();
const id = this.messageId++;
let proxyMessage: NamedProxyMessage | NumberedProxyMessage;
let proxyMessage: Method.Named | Method.Numbered;
if (typeof proxyId === "string") {
proxyMessage = new NamedProxyMessage();
proxyMessage = new Method.Named();
proxyMessage.setModule(moduleToProto(proxyId));
message.setNamedProxy(proxyMessage);
} else {
proxyMessage = new NumberedProxyMessage();
proxyMessage = new Method.Numbered();
proxyMessage.setProxyId(proxyId);
message.setNumberedProxy(proxyMessage);
}
@@ -215,16 +216,14 @@ export class Client {
return callbackId;
};
const stringifiedArgs = args.map((a) => stringify(a, storeCallback));
logger.trace(() => [
"sending",
field("id", id),
field("proxyId", proxyId),
field("method", method),
field("args", stringifiedArgs),
]);
proxyMessage.setArgsList(stringifiedArgs);
proxyMessage.setArgsList(args.map((a) => argumentToProto(a, storeCallback)));
const clientMessage = new ClientMessage();
clientMessage.setMethod(message);
@@ -246,12 +245,12 @@ export class Client {
const d1 = this.successEmitter.event(id, (message) => {
dispose();
resolve(this.parse(message.getResponse(), promise));
resolve(this.protoToArgument(message.getResponse(), promise));
});
const d2 = this.failEmitter.event(id, (message) => {
dispose();
reject(parse(message.getResponse()));
reject(protoToArgument(message.getResponse()));
});
});
@@ -262,42 +261,53 @@ export class Client {
* Handle all messages from the server.
*/
private async handleMessage(message: ServerMessage): Promise<void> {
if (message.hasInit()) {
const init = message.getInit()!;
this._initData = {
dataDirectory: init.getDataDirectory(),
homeDirectory: init.getHomeDirectory(),
tmpDirectory: init.getTmpDirectory(),
workingDirectory: init.getWorkingDirectory(),
os: protoToOperatingSystem(init.getOperatingSystem()),
shell: init.getShell(),
builtInExtensionsDirectory: init.getBuiltinExtensionsDir(),
};
this.initDataEmitter.emit(this._initData);
} else if (message.hasSuccess()) {
this.emitSuccess(message.getSuccess()!);
} else if (message.hasFail()) {
this.emitFail(message.getFail()!);
} else if (message.hasEvent()) {
await this.emitEvent(message.getEvent()!);
} else if (message.hasCallback()) {
await this.runCallback(message.getCallback()!);
} else if (message.hasSharedProcessActive()) {
const sharedProcessActiveMessage = message.getSharedProcessActive()!;
this.sharedProcessActiveEmitter.emit({
socketPath: sharedProcessActiveMessage.getSocketPath(),
logPath: sharedProcessActiveMessage.getLogPath(),
});
} else if (message.hasPong()) {
// Nothing to do since pings are on a timer rather than waiting for the
// next pong in case a message from either the client or server is dropped
// which would break the ping cycle.
} else {
throw new Error("unknown message type");
switch (message.getMsgCase()) {
case ServerMessage.MsgCase.INIT:
const init = message.getInit()!;
this._initData = {
dataDirectory: init.getDataDirectory(),
homeDirectory: init.getHomeDirectory(),
tmpDirectory: init.getTmpDirectory(),
workingDirectory: init.getWorkingDirectory(),
os: protoToOperatingSystem(init.getOperatingSystem()),
shell: init.getShell(),
builtInExtensionsDirectory: init.getBuiltinExtensionsDir(),
};
this.initDataEmitter.emit(this._initData);
break;
case ServerMessage.MsgCase.SUCCESS:
this.emitSuccess(message.getSuccess()!);
break;
case ServerMessage.MsgCase.FAIL:
this.emitFail(message.getFail()!);
break;
case ServerMessage.MsgCase.EVENT:
await this.emitEvent(message.getEvent()!);
break;
case ServerMessage.MsgCase.CALLBACK:
await this.runCallback(message.getCallback()!);
break;
case ServerMessage.MsgCase.SHARED_PROCESS_ACTIVE:
const sharedProcessActiveMessage = message.getSharedProcessActive()!;
this.sharedProcessActiveEmitter.emit({
socketPath: sharedProcessActiveMessage.getSocketPath(),
logPath: sharedProcessActiveMessage.getLogPath(),
});
break;
case ServerMessage.MsgCase.PONG:
// Nothing to do since pings are on a timer rather than waiting for the
// next pong in case a message from either the client or server is dropped
// which would break the ping cycle.
break;
default:
throw new Error("unknown message type");
}
}
private emitSuccess(message: SuccessMessage): void {
/**
* Convert message to a success event.
*/
private emitSuccess(message: Method.Success): void {
logger.trace(() => [
"received resolve",
field("id", message.getId()),
@@ -306,7 +316,10 @@ export class Client {
this.successEmitter.emit(message.getId(), message);
}
private emitFail(message: FailMessage): void {
/**
* Convert message to a fail event.
*/
private emitFail(message: Method.Fail): void {
logger.trace(() => [
"received reject",
field("id", message.getId()),
@@ -322,7 +335,7 @@ export class Client {
* request before it emits. Instead, emit all events from the server so all
* events are always caught on the client.
*/
private async emitEvent(message: EventMessage): Promise<void> {
private async emitEvent(message: Event): Promise<void> {
const eventMessage = message.getNamedEvent()! || message.getNumberedEvent()!;
const proxyId = message.getNamedEvent()
? protoToModule(message.getNamedEvent()!.getModule())
@@ -333,10 +346,9 @@ export class Client {
"received event",
field("proxyId", proxyId),
field("event", event),
field("args", eventMessage.getArgsList()),
]);
const args = eventMessage.getArgsList().map((a) => this.parse(a));
const args = eventMessage.getArgsList().map((a) => this.protoToArgument(a));
this.eventEmitter.emit(proxyId, { event, args });
}
@@ -348,7 +360,7 @@ export class Client {
* also only be used when passed together with the method. If they are sent
* afterward, they may never be called due to timing issues.
*/
private async runCallback(message: CallbackMessage): Promise<void> {
private async runCallback(message: Callback): Promise<void> {
const callbackMessage = message.getNamedCallback()! || message.getNumberedCallback()!;
const proxyId = message.getNamedCallback()
? protoToModule(message.getNamedCallback()!.getModule())
@@ -359,16 +371,15 @@ export class Client {
"running callback",
field("proxyId", proxyId),
field("callbackId", callbackId),
field("args", callbackMessage.getArgsList()),
]);
const args = callbackMessage.getArgsList().map((a) => this.parse(a));
const args = callbackMessage.getArgsList().map((a) => this.protoToArgument(a));
this.getProxy(proxyId).callbacks.get(callbackId)!(...args);
}
/**
* Start the ping loop. Does nothing if already pinging.
*/
private startPinging = (): void => {
private readonly startPinging = (): void => {
if (typeof this.pingTimeout !== "undefined") {
return;
}
@@ -505,10 +516,16 @@ export class Client {
await this.getProxy(proxyId).promise;
}
private parse(value?: string, promise?: Promise<any>): any {
return parse(value, undefined, (id) => this.createProxy(id, promise));
/**
* Same as protoToArgument except provides createProxy.
*/
private protoToArgument(value?: Argument, promise?: Promise<any>): any {
return protoToArgument(value, undefined, (id) => this.createProxy(id, promise));
}
/**
* Get a proxy. Error if it doesn't exist.
*/
private getProxy(proxyId: number | Module): ProxyData {
if (!this.proxies.has(proxyId)) {
throw new Error(`proxy ${proxyId} disposed too early`);

View File

@@ -6,6 +6,8 @@ import { ClientProxy } from "../../common/proxy";
import { ChildProcessModuleProxy, ChildProcessProxy, ChildProcessProxies } from "../../node/modules/child_process";
import { Readable, Writable } from "./stream";
// tslint:disable completed-docs
export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.ChildProcess {
public readonly stdin: stream.Writable;
public readonly stdout: stream.Readable;
@@ -23,10 +25,10 @@ export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.C
this.stderr = new Readable(proxyPromises.then((p) => p.stderr!));
this.stdio = [this.stdin, this.stdout, this.stderr];
this.proxy.getPid().then((pid) => {
this.catch(this.proxy.getPid().then((pid) => {
this._pid = pid;
this._connected = true;
});
}));
this.on("disconnect", () => this._connected = false);
this.on("exit", () => {
this._connected = false;
@@ -48,19 +50,19 @@ export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.C
public kill(): void {
this._killed = true;
this.proxy.kill();
this.catch(this.proxy.kill());
}
public disconnect(): void {
this.proxy.disconnect();
this.catch(this.proxy.disconnect());
}
public ref(): void {
this.proxy.ref();
this.catch(this.proxy.ref());
}
public unref(): void {
this.proxy.unref();
this.catch(this.proxy.unref());
}
public send(
@@ -88,6 +90,9 @@ export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.C
return true; // Always true since we can't get this synchronously.
}
/**
* Exit and close the process when disconnected.
*/
protected handleDisconnect(): void {
this.emit("exit", 1);
this.emit("close");

View File

@@ -6,6 +6,7 @@ import { FsModuleProxy, Stats as IStats, WatcherProxy, WriteStreamProxy } from "
import { Writable } from "./stream";
// tslint:disable no-any
// tslint:disable completed-docs
class StatBatch extends Batch<IStats, { path: fs.PathLike }> {
public constructor(private readonly proxy: FsModuleProxy) {
@@ -39,7 +40,7 @@ class ReaddirBatch extends Batch<Buffer[] | fs.Dirent[] | string[], { path: fs.P
class Watcher extends ClientProxy<WatcherProxy> implements fs.FSWatcher {
public close(): void {
this.proxy.close();
this.catch(this.proxy.close());
}
protected handleDisconnect(): void {
@@ -57,7 +58,7 @@ class WriteStream extends Writable<WriteStreamProxy> implements fs.WriteStream {
}
public close(): void {
this.proxy.close();
this.catch(this.proxy.close());
}
}

View File

@@ -4,6 +4,8 @@ import { ClientProxy } from "../../common/proxy";
import { NetModuleProxy, NetServerProxy, NetSocketProxy } from "../../node/modules/net";
import { Duplex } from "./stream";
// tslint:disable completed-docs
export class Socket extends Duplex<NetSocketProxy> implements net.Socket {
private _connecting: boolean = false;
private _destroyed: boolean = false;
@@ -29,9 +31,8 @@ export class Socket extends Duplex<NetSocketProxy> implements net.Socket {
if (callback) {
this.on("connect", callback as () => void);
}
this.proxy.connect(options, host);
return this;
return this.catch(this.proxy.connect(options, host));
}
// tslint:disable-next-line no-any
@@ -117,11 +118,11 @@ export class Socket extends Duplex<NetSocketProxy> implements net.Socket {
}
public unref(): void {
this.proxy.unref();
this.catch(this.proxy.unref());
}
public ref(): void {
this.proxy.ref();
this.catch(this.proxy.ref());
}
}
@@ -133,14 +134,14 @@ export class Server extends ClientProxy<NetServerProxy> implements net.Server {
public constructor(proxyPromise: Promise<NetServerProxy> | NetServerProxy) {
super(proxyPromise);
this.proxy.onConnection((socketProxy) => {
this.catch(this.proxy.onConnection((socketProxy) => {
const socket = new Socket(socketProxy);
const socketId = this.socketId++;
this.sockets.set(socketId, socket);
socket.on("error", () => this.sockets.delete(socketId))
socket.on("close", () => this.sockets.delete(socketId))
socket.on("error", () => this.sockets.delete(socketId));
socket.on("close", () => this.sockets.delete(socketId));
this.emit("connection", socket);
});
}));
this.on("listening", () => this._listening = true);
this.on("error", () => this._listening = false);
@@ -160,9 +161,7 @@ export class Server extends ClientProxy<NetServerProxy> implements net.Server {
this.on("listening", callback as () => void);
}
this.proxy.listen(handle, hostname, backlog);
return this;
return this.catch(this.proxy.listen(handle, hostname, backlog));
}
public get connections(): number {
@@ -186,21 +185,16 @@ export class Server extends ClientProxy<NetServerProxy> implements net.Server {
if (callback) {
this.on("close", callback);
}
this.proxy.close();
return this;
return this.catch(this.proxy.close());
}
public ref(): this {
this.proxy.ref();
return this;
return this.catch(this.proxy.ref());
}
public unref(): this {
this.proxy.unref();
return this;
return this.catch(this.proxy.unref());
}
public getConnections(cb: (error: Error | null, count: number) => void): void {

View File

@@ -2,6 +2,8 @@ import * as pty from "node-pty";
import { ClientProxy } from "../../common/proxy";
import { NodePtyModuleProxy, NodePtyProcessProxy } from "../../node/modules/node-pty";
// tslint:disable completed-docs
export class NodePtyProcess extends ClientProxy<NodePtyProcessProxy> implements pty.IPty {
private _pid = -1;
private _process = "";
@@ -16,10 +18,10 @@ export class NodePtyProcess extends ClientProxy<NodePtyProcessProxy> implements
this.on("process", (process) => this._process = process);
}
protected initialize(proxyPromise: Promise<NodePtyProcessProxy>) {
protected initialize(proxyPromise: Promise<NodePtyProcessProxy>): void {
super.initialize(proxyPromise);
this.proxy.getPid().then((pid) => this._pid = pid);
this.proxy.getProcess().then((process) => this._process = process);
this.catch(this.proxy.getPid().then((p) => this._pid = p));
this.catch(this.proxy.getProcess().then((p) => this._process = p));
}
public get pid(): number {
@@ -31,15 +33,15 @@ export class NodePtyProcess extends ClientProxy<NodePtyProcessProxy> implements
}
public resize(columns: number, rows: number): void {
this.proxy.resize(columns, rows);
this.catch(this.proxy.resize(columns, rows));
}
public write(data: string): void {
this.proxy.write(data);
this.catch(this.proxy.write(data));
}
public kill(signal?: string): void {
this.proxy.kill(signal);
this.catch(this.proxy.kill(signal));
}
protected handleDisconnect(): void {

View File

@@ -2,6 +2,8 @@ import * as spdlog from "spdlog";
import { ClientProxy } from "../../common/proxy";
import { RotatingLoggerProxy, SpdlogModuleProxy } from "../../node/modules/spdlog";
// tslint:disable completed-docs
class RotatingLogger extends ClientProxy<RotatingLoggerProxy> implements spdlog.RotatingLogger {
public constructor(
private readonly moduleProxy: SpdlogModuleProxy,
@@ -13,16 +15,16 @@ class RotatingLogger extends ClientProxy<RotatingLoggerProxy> implements spdlog.
super(moduleProxy.createLogger(name, filename, filesize, filecount));
}
public async trace (message: string): Promise<void> { this.proxy.trace(message); }
public async debug (message: string): Promise<void> { this.proxy.debug(message); }
public async info (message: string): Promise<void> { this.proxy.info(message); }
public async warn (message: string): Promise<void> { this.proxy.warn(message); }
public async error (message: string): Promise<void> { this.proxy.error(message); }
public async critical (message: string): Promise<void> { this.proxy.critical(message); }
public async setLevel (level: number): Promise<void> { this.proxy.setLevel(level); }
public async clearFormatters (): Promise<void> { this.proxy.clearFormatters(); }
public async flush (): Promise<void> { this.proxy.flush(); }
public async drop (): Promise<void> { this.proxy.drop(); }
public trace (message: string): void { this.catch(this.proxy.trace(message)); }
public debug (message: string): void { this.catch(this.proxy.debug(message)); }
public info (message: string): void { this.catch(this.proxy.info(message)); }
public warn (message: string): void { this.catch(this.proxy.warn(message)); }
public error (message: string): void { this.catch(this.proxy.error(message)); }
public critical (message: string): void { this.catch(this.proxy.critical(message)); }
public setLevel (level: number): void { this.catch(this.proxy.setLevel(level)); }
public clearFormatters (): void { this.catch(this.proxy.clearFormatters()); }
public flush (): void { this.catch(this.proxy.flush()); }
public drop (): void { this.catch(this.proxy.drop()); }
protected handleDisconnect(): void {
this.initialize(this.moduleProxy.createLogger(this.name, this.filename, this.filesize, this.filecount));
@@ -40,7 +42,7 @@ export class SpdlogModule {
};
}
public setAsyncMode = (bufferSize: number, flushInterval: number): void => {
this.proxy.setAsyncMode(bufferSize, flushInterval);
public setAsyncMode = (bufferSize: number, flushInterval: number): Promise<void> => {
return this.proxy.setAsyncMode(bufferSize, flushInterval);
}
}

View File

@@ -3,6 +3,8 @@ import { callbackify } from "util";
import { ClientProxy } from "../../common/proxy";
import { DuplexProxy, IReadableProxy, WritableProxy } from "../../node/modules/stream";
// tslint:disable completed-docs
export class Writable<T extends WritableProxy = WritableProxy> extends ClientProxy<T> implements stream.Writable {
public get writable(): boolean {
throw new Error("not implemented");
@@ -41,13 +43,11 @@ export class Writable<T extends WritableProxy = WritableProxy> extends ClientPro
}
public destroy(): void {
this.proxy.destroy();
this.catch(this.proxy.destroy());
}
public setDefaultEncoding(encoding: string): this {
this.proxy.setDefaultEncoding(encoding);
return this;
return this.catch(this.proxy.setDefaultEncoding(encoding));
}
// tslint:disable-next-line no-any
@@ -151,13 +151,11 @@ export class Readable<T extends IReadableProxy = IReadableProxy> extends ClientP
}
public destroy(): void {
this.proxy.destroy();
this.catch(this.proxy.destroy());
}
public setEncoding(encoding: string): this {
this.proxy.setEncoding(encoding);
return this;
return this.catch(this.proxy.setEncoding(encoding));
}
protected handleDisconnect(): void {
@@ -236,9 +234,7 @@ export class Duplex<T extends DuplexProxy = DuplexProxy> extends Writable<T> imp
}
public setEncoding(encoding: string): this {
this.proxy.setEncoding(encoding);
return this;
return this.catch(this.proxy.setEncoding(encoding));
}
protected handleDisconnect(): void {

View File

@@ -1,6 +1,8 @@
import * as trash from "trash";
import { TrashModuleProxy } from "../../node/modules/trash";
// tslint:disable completed-docs
export class TrashModule {
public constructor(private readonly proxy: TrashModuleProxy) {}