Archived
1
0
This repository has been archived on 2024-09-09. You can view files and clone it, but cannot push or open issues or pull requests.
code-server/packages/server/src/vscode/sharedProcess.ts

174 lines
4.9 KiB
TypeScript
Raw Normal View History

import { ChildProcess } from "child_process";
import * as fs from "fs";
import * as fse from "fs-extra";
import * as os from "os";
import * as path from "path";
import { forkModule } from "./bootstrapFork";
import { StdioIpcHandler } from "../ipc";
import { ParsedArgs } from "vs/platform/environment/common/environment";
2019-02-07 01:11:31 +01:00
import { Emitter } from "@coder/events/src";
import { retry } from "@coder/ide/src/retry";
import { logger, Level } from "@coder/logger";
export enum SharedProcessState {
Stopped,
Starting,
Ready,
}
export type SharedProcessEvent = {
readonly state: SharedProcessState.Ready | SharedProcessState.Starting;
} | {
readonly state: SharedProcessState.Stopped;
readonly error: string;
};
export class SharedProcess {
public readonly socketPath: string = os.platform() === "win32"
? path.join("\\\\?\\pipe", os.tmpdir(), `.code-server${Math.random().toString()}`)
: path.join(os.tmpdir(), `.code-server${Math.random().toString()}`);
private _state: SharedProcessState = SharedProcessState.Stopped;
private activeProcess: ChildProcess | undefined;
private ipcHandler: StdioIpcHandler | undefined;
private readonly onStateEmitter = new Emitter<SharedProcessEvent>();
public readonly onState = this.onStateEmitter.event;
private readonly logger = logger.named("shared");
private readonly retry = retry.register("Shared process", () => this.connect());
private disposed: boolean = false;
public constructor(
private readonly userDataDir: string,
private readonly extensionsDir: string,
private readonly builtInExtensionsDir: string,
) {
this.retry.run();
}
public get state(): SharedProcessState {
return this._state;
}
/**
* Signal the shared process to terminate.
*/
public dispose(): void {
this.disposed = true;
if (this.ipcHandler) {
this.ipcHandler.send("handshake:goodbye");
}
this.ipcHandler = undefined;
}
/**
* Start and connect to the shared process.
*/
private async connect(): Promise<void> {
this.setState({ state: SharedProcessState.Starting });
const activeProcess = await this.restart();
activeProcess.on("exit", (exitCode) => {
const error = new Error(`Exited with ${exitCode}`);
this.setState({
error: error.message,
state: SharedProcessState.Stopped,
});
if (!this.disposed) {
this.retry.run(error);
}
});
this.setState({ state: SharedProcessState.Ready });
}
/**
* Restart the shared process. Kill existing process if running. Resolve when
* the shared process is ready and reject when it errors or dies before being
* ready.
*/
private async restart(): Promise<ChildProcess> {
if (this.activeProcess && !this.activeProcess.killed) {
this.activeProcess.kill();
}
2019-03-27 15:36:32 +01:00
const backupsDir = path.join(this.userDataDir, "Backups");
await Promise.all([
fse.mkdirp(backupsDir),
]);
2019-03-27 15:36:32 +01:00
const workspacesFile = path.join(backupsDir, "workspaces.json");
if (!fs.existsSync(workspacesFile)) {
fs.appendFileSync(workspacesFile, "");
2019-03-27 15:36:32 +01:00
}
const activeProcess = forkModule("vs/code/electron-browser/sharedProcess/sharedProcessMain", [], {
Make everything use active evals (#30) * Add trace log level * Use active eval to implement spdlog * Split server/client active eval interfaces Since all properties are *not* valid on both sides * +200% fire resistance * Implement exec using active evaluations * Fully implement child process streams * Watch impl, move child_process back to explicitly adding events Automatically forwarding all events might be the right move, but wanna think/discuss it a bit more because it didn't come out very cleanly. * Would you like some args with that callback? * Implement the rest of child_process using active evals * Rampant memory leaks Emit "kill" to active evaluations when client disconnects in order to kill processes. Most likely won't be the final solution. * Resolve some minor issues with output panel * Implement node-pty with active evals * Provide clearTimeout to vm sandbox * Implement socket with active evals * Extract some callback logic Also remove some eval interfaces, need to re-think those. * Implement net.Server and remainder of net.Socket using active evals * Implement dispose for active evaluations * Use trace for express requests * Handle sending buffers through evaluation events * Make event logging a bit more clear * Fix some errors due to us not actually instantiating until connect/listen * is this a commit message? * We can just create the evaluator in the ctor Not sure what I was thinking. * memory leak for you, memory leak for everyone * it's a ternary now * Don't dispose automatically on close or error The code may or may not be disposable at that point. * Handle parsing buffers on the client side as well * Remove unused protobuf * Remove TypedValue * Remove unused forkProvider and test * Improve dispose pattern for active evals * Socket calls close after error; no need to bind both * Improve comment * Comment is no longer wishy washy due to explicit boolean * Simplify check for sendHandle and options * Replace _require with __non_webpack_require__ Webpack will then replace this with `require` which we then provide to the vm sandbox. * Provide path.parse * Prevent original-fs from loading * Start with a pid of -1 vscode immediately checks the PID to see if the debug process launch correctly, but of course we don't get the pid synchronously. * Pass arguments to bootstrap-fork * Fully implement streams Was causing errors because internally the stream would set this.writing to true and it would never become false, so subsequent messages would never send. * Fix serializing errors and streams emitting errors multiple times * Was emitting close to data * Fix missing path for spawned processes * Move evaluation onDispose call Now it's accurate and runs when the active evaluation has actually disposed. * Fix promisifying fs.exists * Fix some active eval callback issues * Patch existsSync in debug adapter
2019-02-19 17:17:03 +01:00
env: {
VSCODE_ALLOW_IO: "true",
VSCODE_LOGS: process.env.VSCODE_LOGS,
},
2019-02-22 02:32:08 +01:00
}, this.userDataDir);
this.activeProcess = activeProcess;
await new Promise((resolve, reject): void => {
const doReject = (error: Error | number | null): void => {
if (error === null) {
error = new Error("Exited unexpectedly");
} else if (typeof error === "number") {
error = new Error(`Exited with ${error}`);
}
activeProcess.removeAllListeners();
this.setState({
error: error.message,
state: SharedProcessState.Stopped,
});
reject(error);
};
activeProcess.on("error", doReject);
activeProcess.on("exit", doReject);
activeProcess.stdout.on("data", (data) => {
logger.trace(data.toString());
});
activeProcess.stderr.on("data", (data) => {
// Warn instead of error to prevent panic. It's unlikely stderr here is
// about anything critical to the functioning of the editor.
logger.warn(data.toString());
});
this.ipcHandler = new StdioIpcHandler(activeProcess);
this.ipcHandler.once("handshake:hello", () => {
const data: {
sharedIPCHandle: string;
args: Partial<ParsedArgs>;
logLevel: Level;
} = {
args: {
"builtin-extensions-dir": this.builtInExtensionsDir,
"user-data-dir": this.userDataDir,
"extensions-dir": this.extensionsDir,
},
logLevel: this.logger.level,
sharedIPCHandle: this.socketPath,
};
this.ipcHandler!.send("handshake:hey there", "", data);
});
this.ipcHandler.once("handshake:im ready", () => {
activeProcess.removeListener("error", doReject);
activeProcess.removeListener("exit", doReject);
resolve();
});
});
return activeProcess;
}
/**
* Set the internal shared process state and emit the state event.
*/
private setState(event: SharedProcessEvent): void {
this._state = event.state;
this.onStateEmitter.emit(event);
}
}