2019-01-16 01:36:09 +07:00
|
|
|
import * as os from "os";
|
2019-01-26 07:18:21 +07:00
|
|
|
import { logger, field } from "@coder/logger";
|
2019-03-05 10:26:17 +07:00
|
|
|
import { Pong, ClientMessage, WorkingInitMessage, ServerMessage } from "../proto";
|
2019-01-30 07:48:02 +07:00
|
|
|
import { evaluate, ActiveEvaluation } from "./evaluate";
|
2019-02-23 04:56:29 +07:00
|
|
|
import { ForkProvider } from "../common/helpers";
|
2019-01-12 02:33:44 +07:00
|
|
|
import { ReadWriteConnection } from "../common/connection";
|
2019-03-12 23:12:50 +07:00
|
|
|
import { mkdirP } from "../common/util";
|
2019-01-12 02:33:44 +07:00
|
|
|
|
2019-01-16 01:36:09 +07:00
|
|
|
export interface ServerOptions {
|
|
|
|
readonly workingDirectory: string;
|
|
|
|
readonly dataDirectory: string;
|
2019-03-12 23:12:50 +07:00
|
|
|
readonly cacheDirectory: string;
|
2019-02-06 00:15:20 +07:00
|
|
|
readonly builtInExtensionsDirectory: string;
|
2019-02-23 04:56:29 +07:00
|
|
|
readonly fork?: ForkProvider;
|
2019-01-16 01:36:09 +07:00
|
|
|
}
|
|
|
|
|
2019-01-12 02:33:44 +07:00
|
|
|
export class Server {
|
2019-02-07 00:53:23 +07:00
|
|
|
private readonly evals = new Map<number, ActiveEvaluation>();
|
2019-01-24 07:00:38 +07:00
|
|
|
|
2019-01-12 02:33:44 +07:00
|
|
|
public constructor(
|
|
|
|
private readonly connection: ReadWriteConnection,
|
2019-01-19 06:08:44 +07:00
|
|
|
private readonly options?: ServerOptions,
|
2019-01-12 02:33:44 +07:00
|
|
|
) {
|
|
|
|
connection.onMessage((data) => {
|
|
|
|
try {
|
|
|
|
this.handleMessage(ClientMessage.deserializeBinary(data));
|
|
|
|
} catch (ex) {
|
2019-02-06 00:15:20 +07:00
|
|
|
logger.error("Failed to handle client message", field("length", data.byteLength), field("exception", {
|
|
|
|
message: ex.message,
|
|
|
|
stack: ex.stack,
|
|
|
|
}));
|
2019-01-12 02:33:44 +07:00
|
|
|
}
|
|
|
|
});
|
2019-01-30 07:23:30 +07:00
|
|
|
connection.onClose(() => {
|
2019-02-19 23:17:03 +07:00
|
|
|
this.evals.forEach((e) => e.dispose());
|
2019-01-30 07:23:30 +07:00
|
|
|
});
|
2019-01-16 01:36:09 +07:00
|
|
|
|
2019-02-19 23:17:03 +07:00
|
|
|
if (!this.options) {
|
2019-01-16 01:36:09 +07:00
|
|
|
logger.warn("No server options provided. InitMessage will not be sent.");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-12 23:12:50 +07:00
|
|
|
Promise.all([
|
|
|
|
mkdirP(this.options.cacheDirectory),
|
|
|
|
mkdirP(this.options.dataDirectory),
|
|
|
|
mkdirP(this.options.workingDirectory),
|
|
|
|
]).catch((error) => {
|
2019-01-19 04:46:40 +07:00
|
|
|
logger.error(error.message, field("error", error));
|
|
|
|
});
|
|
|
|
|
|
|
|
const initMsg = new WorkingInitMessage();
|
2019-02-19 23:17:03 +07:00
|
|
|
initMsg.setDataDirectory(this.options.dataDirectory);
|
|
|
|
initMsg.setWorkingDirectory(this.options.workingDirectory);
|
|
|
|
initMsg.setBuiltinExtensionsDir(this.options.builtInExtensionsDirectory);
|
2019-01-16 01:36:09 +07:00
|
|
|
initMsg.setHomeDirectory(os.homedir());
|
|
|
|
initMsg.setTmpDirectory(os.tmpdir());
|
|
|
|
const platform = os.platform();
|
2019-01-19 04:46:40 +07:00
|
|
|
let operatingSystem: WorkingInitMessage.OperatingSystem;
|
2019-01-16 01:36:09 +07:00
|
|
|
switch (platform) {
|
|
|
|
case "win32":
|
2019-01-19 04:46:40 +07:00
|
|
|
operatingSystem = WorkingInitMessage.OperatingSystem.WINDOWS;
|
2019-01-16 01:36:09 +07:00
|
|
|
break;
|
|
|
|
case "linux":
|
2019-01-19 04:46:40 +07:00
|
|
|
operatingSystem = WorkingInitMessage.OperatingSystem.LINUX;
|
2019-01-16 01:36:09 +07:00
|
|
|
break;
|
|
|
|
case "darwin":
|
2019-01-19 04:46:40 +07:00
|
|
|
operatingSystem = WorkingInitMessage.OperatingSystem.MAC;
|
2019-01-16 01:36:09 +07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error(`unrecognized platform "${platform}"`);
|
|
|
|
}
|
|
|
|
initMsg.setOperatingSystem(operatingSystem);
|
2019-02-28 01:43:00 +07:00
|
|
|
initMsg.setShell(os.userInfo().shell || global.process.env.SHELL);
|
2019-01-16 01:36:09 +07:00
|
|
|
const srvMsg = new ServerMessage();
|
|
|
|
srvMsg.setInit(initMsg);
|
|
|
|
connection.send(srvMsg.serializeBinary());
|
2019-01-12 02:33:44 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
private handleMessage(message: ClientMessage): void {
|
|
|
|
if (message.hasNewEval()) {
|
2019-01-26 07:18:21 +07:00
|
|
|
const evalMessage = message.getNewEval()!;
|
2019-02-19 23:17:03 +07:00
|
|
|
logger.trace(() => [
|
2019-01-30 07:23:30 +07:00
|
|
|
"EvalMessage",
|
|
|
|
field("id", evalMessage.getId()),
|
|
|
|
field("args", evalMessage.getArgsList()),
|
|
|
|
field("function", evalMessage.getFunction()),
|
|
|
|
]);
|
2019-01-30 07:48:02 +07:00
|
|
|
const resp = evaluate(this.connection, evalMessage, () => {
|
|
|
|
this.evals.delete(evalMessage.getId());
|
2019-02-19 23:17:03 +07:00
|
|
|
logger.trace(() => [
|
|
|
|
`dispose ${evalMessage.getId()}, ${this.evals.size} left`,
|
|
|
|
]);
|
2019-02-23 04:56:29 +07:00
|
|
|
}, this.options ? this.options.fork : undefined);
|
2019-01-30 07:48:02 +07:00
|
|
|
if (resp) {
|
|
|
|
this.evals.set(evalMessage.getId(), resp);
|
|
|
|
}
|
|
|
|
} else if (message.hasEvalEvent()) {
|
2019-01-31 04:40:01 +07:00
|
|
|
const evalEventMessage = message.getEvalEvent()!;
|
|
|
|
const e = this.evals.get(evalEventMessage.getId());
|
2019-01-30 07:48:02 +07:00
|
|
|
if (!e) {
|
|
|
|
return;
|
|
|
|
}
|
2019-01-31 04:40:01 +07:00
|
|
|
e.onEvent(evalEventMessage);
|
2019-03-05 10:26:17 +07:00
|
|
|
} else if (message.hasPing()) {
|
|
|
|
logger.trace("ping");
|
|
|
|
const srvMsg = new ServerMessage();
|
|
|
|
srvMsg.setPong(new Pong());
|
|
|
|
this.connection.send(srvMsg.serializeBinary());
|
2019-01-26 07:18:21 +07:00
|
|
|
} else {
|
2019-02-19 23:17:03 +07:00
|
|
|
throw new Error("unknown message type");
|
2019-01-12 02:33:44 +07:00
|
|
|
}
|
|
|
|
}
|
2019-01-13 02:44:29 +07:00
|
|
|
}
|