Refactor evaluations (#285)

* Replace evaluations with proxies and messages

* Return proxies synchronously

Otherwise events can be lost.

* Ensure events cannot be missed

* Refactor remaining fills

* Use more up-to-date version of util

For callbackify.

* Wait for dispose to come back before removing

This prevents issues with the "done" event not always being the last
event fired. For example a socket might close and then end, but only
if the caller called end.

* Remove old node-pty tests

* Fix emitting events twice on duplex streams

* Preserve environment when spawning processes

* Throw a better error if the proxy doesn't exist

* Remove rimraf dependency from ide

* Update net.Server.listening

* Use exit event instead of killed

Doesn't look like killed is even a thing.

* Add response timeout to server

* Fix trash

* Require node-pty & spdlog after they get unpackaged

This fixes an error when running in the binary.

* Fix errors in down emitter preventing reconnecting

* Fix disposing proxies when nothing listens to "error" event

* Refactor event tests to use jest.fn()

* Reject proxy call when disconnected

Otherwise it'll wait for the timeout which is a waste of time since we
already know the connection is dead.

* Use nbin for binary packaging

* Remove additional module requires

* Attempt to remove require for local bootstrap-fork

* Externalize fsevents
This commit is contained in:
Asher
2019-03-26 13:01:25 -05:00
committed by Kyle Carberry
parent d16c6aeb30
commit dc2253e718
75 changed files with 5866 additions and 6181 deletions

View File

@@ -1,94 +1,4 @@
import { Module } from "@coder/protocol";
import { client } from "@coder/ide/src/fill/client";
import { EventEmitter } from "events";
import * as nodePty from "node-pty";
import { ActiveEvalHelper } from "@coder/protocol";
import { logger } from "@coder/logger";
/**
* Implementation of nodePty for the browser.
*/
class Pty implements nodePty.IPty {
private readonly emitter = new EventEmitter();
private readonly ae: ActiveEvalHelper;
private _pid = -1;
private _process = "";
public constructor(file: string, args: string[] | string, options: nodePty.IPtyForkOptions) {
this.ae = client.run((ae, file, args, options) => {
ae.preserveEnv(options);
const ptyProc = ae.modules.pty.spawn(file, args, options);
let process = ptyProc.process;
ae.emit("process", process);
ae.emit("pid", ptyProc.pid);
const timer = setInterval(() => {
if (ptyProc.process !== process) {
process = ptyProc.process;
ae.emit("process", process);
}
}, 200);
ptyProc.on("exit", (code, signal) => {
clearTimeout(timer);
ae.emit("exit", code, signal);
});
ptyProc.on("data", (data) => ae.emit("data", data));
ae.on("resize", (cols: number, rows: number) => ptyProc.resize(cols, rows));
ae.on("write", (data: string) => ptyProc.write(data));
ae.on("kill", (signal: string) => ptyProc.kill(signal));
return {
onDidDispose: (cb): void => ptyProc.on("exit", cb),
dispose: (): void => {
ptyProc.kill();
setTimeout(() => ptyProc.kill("SIGKILL"), 5000); // Double tap.
},
};
}, file, args, options);
this.ae.on("error", (error) => logger.error(error.message));
this.ae.on("pid", (pid) => this._pid = pid);
this.ae.on("process", (process) => this._process = process);
this.ae.on("exit", (code, signal) => this.emitter.emit("exit", code, signal));
this.ae.on("data", (data) => this.emitter.emit("data", data));
}
public get pid(): number {
return this._pid;
}
public get process(): string {
return this._process;
}
// tslint:disable-next-line no-any
public on(event: string, listener: (...args: any[]) => void): void {
this.emitter.on(event, listener);
}
public resize(columns: number, rows: number): void {
this.ae.emit("resize", columns, rows);
}
public write(data: string): void {
this.ae.emit("write", data);
}
public kill(signal?: string): void {
this.ae.emit("kill", signal);
}
}
const ptyType: typeof nodePty = {
spawn: (file: string, args: string[] | string, options: nodePty.IPtyForkOptions): nodePty.IPty => {
return new Pty(file, args, options);
},
};
module.exports = ptyType;
export = client.modules[Module.NodePty];

View File

@@ -1,63 +1,4 @@
import { RotatingLogger as NodeRotatingLogger } from "spdlog";
import { logger } from "@coder/logger";
import { Module } from "@coder/protocol";
import { client } from "@coder/ide/src/fill/client";
const ae = client.run((ae) => {
const loggers = new Map<number, NodeRotatingLogger>();
ae.on("new", (id: number, name: string, filePath: string, fileSize: number, fileCount: number) => {
const logger = new ae.modules.spdlog.RotatingLogger(name, filePath, fileSize, fileCount);
loggers.set(id, logger);
});
ae.on("clearFormatters", (id: number) => loggers.get(id)!.clearFormatters());
ae.on("critical", (id: number, message: string) => loggers.get(id)!.critical(message));
ae.on("debug", (id: number, message: string) => loggers.get(id)!.debug(message));
ae.on("drop", (id: number) => loggers.get(id)!.drop());
ae.on("errorLog", (id: number, message: string) => loggers.get(id)!.error(message));
ae.on("flush", (id: number) => loggers.get(id)!.flush());
ae.on("info", (id: number, message: string) => loggers.get(id)!.info(message));
ae.on("setAsyncMode", (bufferSize: number, flushInterval: number) => ae.modules.spdlog.setAsyncMode(bufferSize, flushInterval));
ae.on("setLevel", (id: number, level: number) => loggers.get(id)!.setLevel(level));
ae.on("trace", (id: number, message: string) => loggers.get(id)!.trace(message));
ae.on("warn", (id: number, message: string) => loggers.get(id)!.warn(message));
const disposeCallbacks = <Array<() => void>>[];
return {
onDidDispose: (cb): number => disposeCallbacks.push(cb),
dispose: (): void => {
loggers.forEach((logger) => logger.flush());
loggers.clear();
disposeCallbacks.forEach((cb) => cb());
},
};
});
const spdLogger = logger.named("spdlog");
ae.on("close", () => spdLogger.error("session closed prematurely"));
ae.on("error", (error: Error) => spdLogger.error(error.message));
let id = 0;
export class RotatingLogger implements NodeRotatingLogger {
private readonly id = id++;
public constructor(name: string, filePath: string, fileSize: number, fileCount: number) {
ae.emit("new", this.id, name, filePath, fileSize, fileCount);
}
public trace(message: string): void { ae.emit("trace", this.id, message); }
public debug(message: string): void { ae.emit("debug", this.id, message); }
public info(message: string): void { ae.emit("info", this.id, message); }
public warn(message: string): void { ae.emit("warn", this.id, message); }
public error(message: string): void { ae.emit("errorLog", this.id, message); }
public critical(message: string): void { ae.emit("critical", this.id, message); }
public setLevel(level: number): void { ae.emit("setLevel", this.id, level); }
public clearFormatters(): void { ae.emit("clearFormatters", this.id); }
public flush(): void { ae.emit("flush", this.id); }
public drop(): void { ae.emit("drop", this.id); }
}
export const setAsyncMode = (bufferSize: number, flushInterval: number): void => {
ae.emit("setAsyncMode", bufferSize, flushInterval);
};
export = client.modules[Module.Spdlog];