import * as cp from "child_process"; import { getPathFromAmdModule } from "vs/base/common/amd"; import { VSBuffer } from "vs/base/common/buffer"; import { Emitter } from "vs/base/common/event"; import { ISocket } from "vs/base/parts/ipc/common/ipc.net"; import { NodeSocket } from "vs/base/parts/ipc/node/ipc.net"; import { IEnvironmentService } from "vs/platform/environment/common/environment"; import { ILogService } from "vs/platform/log/common/log"; import { getNlsConfiguration } from "vs/server/src/node/nls"; import { Protocol } from "vs/server/src/node/protocol"; import { uriTransformerPath } from "vs/server/src/node/util"; import { IExtHostReadyMessage } from "vs/workbench/services/extensions/common/extensionHostProtocol"; export abstract class Connection { private readonly _onClose = new Emitter(); public readonly onClose = this._onClose.event; private disposed = false; private _offline: number | undefined; public constructor(protected protocol: Protocol, public readonly token: string) {} public get offline(): number | undefined { return this._offline; } public reconnect(socket: ISocket, buffer: VSBuffer): void { this._offline = undefined; this.doReconnect(socket, buffer); } public dispose(): void { if (!this.disposed) { this.disposed = true; this.doDispose(); this._onClose.fire(); } } protected setOffline(): void { if (!this._offline) { this._offline = Date.now(); } } /** * Set up the connection on a new socket. */ protected abstract doReconnect(socket: ISocket, buffer: VSBuffer): void; protected abstract doDispose(): void; } /** * Used for all the IPC channels. */ export class ManagementConnection extends Connection { public constructor(protected protocol: Protocol, token: string) { super(protocol, token); protocol.onClose(() => this.dispose()); // Explicit close. protocol.onSocketClose(() => this.setOffline()); // Might reconnect. } protected doDispose(): void { this.protocol.sendDisconnect(); this.protocol.dispose(); this.protocol.getSocket().end(); } protected doReconnect(socket: ISocket, buffer: VSBuffer): void { this.protocol.beginAcceptReconnection(socket, buffer); this.protocol.endAcceptReconnection(); } } export class ExtensionHostConnection extends Connection { private process?: cp.ChildProcess; public constructor( locale:string, protocol: Protocol, buffer: VSBuffer, token: string, private readonly log: ILogService, private readonly environment: IEnvironmentService, ) { super(protocol, token); this.protocol.dispose(); this.spawn(locale, buffer).then((p) => this.process = p); this.protocol.getUnderlyingSocket().pause(); } protected doDispose(): void { if (this.process) { this.process.kill(); } this.protocol.getSocket().end(); } protected doReconnect(socket: ISocket, buffer: VSBuffer): void { // This is just to set the new socket. this.protocol.beginAcceptReconnection(socket, null); this.protocol.dispose(); this.sendInitMessage(buffer); } private sendInitMessage(buffer: VSBuffer): void { const socket = this.protocol.getUnderlyingSocket(); socket.pause(); this.process!.send({ // Process must be set at this point. type: "VSCODE_EXTHOST_IPC_SOCKET", initialDataChunk: (buffer.buffer as Buffer).toString("base64"), skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket, }, socket); } private async spawn(locale: string, buffer: VSBuffer): Promise { const config = await getNlsConfiguration(locale, this.environment.userDataPath); const proc = cp.fork( getPathFromAmdModule(require, "bootstrap-fork"), [ "--type=extensionHost", `--uriTransformerPath=${uriTransformerPath}` ], { env: { ...process.env, AMD_ENTRYPOINT: "vs/workbench/services/extensions/node/extensionHostProcess", PIPE_LOGGING: "true", VERBOSE_LOGGING: "true", VSCODE_EXTHOST_WILL_SEND_SOCKET: "true", VSCODE_HANDLES_UNCAUGHT_ERRORS: "true", VSCODE_LOG_STACK: "false", VSCODE_LOG_LEVEL: this.environment.verbose ? "trace" : this.environment.log, VSCODE_NLS_CONFIG: JSON.stringify(config), }, silent: true, }, ); proc.on("error", () => this.dispose()); proc.on("exit", () => this.dispose()); proc.stdout.setEncoding("utf8").on("data", (d) => this.log.info("Extension host stdout", d)); proc.stderr.setEncoding("utf8").on("data", (d) => this.log.error("Extension host stderr", d)); proc.on("message", (event) => { if (event && event.type === "__$console") { const severity = (this.log)[event.severity] ? event.severity : "info"; (this.log)[severity]("Extension host", event.arguments); } if (event && event.type === "VSCODE_EXTHOST_DISCONNECTED") { this.setOffline(); } }); const listen = (message: IExtHostReadyMessage) => { if (message.type === "VSCODE_EXTHOST_IPC_READY") { proc.removeListener("message", listen); this.sendInitMessage(buffer); } }; return proc.on("message", listen); } }