Path: blob/main/src/vs/platform/extensions/electron-main/extensionHostStarter.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Promises } from '../../../base/common/async.js';6import { canceled } from '../../../base/common/errors.js';7import { Event } from '../../../base/common/event.js';8import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';9import { IExtensionHostProcessOptions, IExtensionHostStarter } from '../common/extensionHostStarter.js';10import { ILifecycleMainService } from '../../lifecycle/electron-main/lifecycleMainService.js';11import { ILogService } from '../../log/common/log.js';12import { ITelemetryService } from '../../telemetry/common/telemetry.js';13import { WindowUtilityProcess } from '../../utilityProcess/electron-main/utilityProcess.js';14import { IWindowsMainService } from '../../windows/electron-main/windows.js';15import { IConfigurationService } from '../../configuration/common/configuration.js';1617export class ExtensionHostStarter extends Disposable implements IDisposable, IExtensionHostStarter {1819readonly _serviceBrand: undefined;2021private static _lastId: number = 0;2223private readonly _extHosts = new Map<string, WindowUtilityProcess>();24private _shutdown = false;2526constructor(27@ILogService private readonly _logService: ILogService,28@ILifecycleMainService private readonly _lifecycleMainService: ILifecycleMainService,29@IWindowsMainService private readonly _windowsMainService: IWindowsMainService,30@ITelemetryService private readonly _telemetryService: ITelemetryService,31@IConfigurationService private readonly _configurationService: IConfigurationService,32) {33super();3435// On shutdown: gracefully await extension host shutdowns36this._register(this._lifecycleMainService.onWillShutdown(e => {37this._shutdown = true;38e.join('extHostStarter', this._waitForAllExit(6000));39}));40}4142override dispose(): void {43// Intentionally not killing the extension host processes44super.dispose();45}4647private _getExtHost(id: string): WindowUtilityProcess {48const extHostProcess = this._extHosts.get(id);49if (!extHostProcess) {50throw new Error(`Unknown extension host!`);51}52return extHostProcess;53}5455onDynamicStdout(id: string): Event<string> {56return this._getExtHost(id).onStdout;57}5859onDynamicStderr(id: string): Event<string> {60return this._getExtHost(id).onStderr;61}6263onDynamicMessage(id: string): Event<any> {64return this._getExtHost(id).onMessage;65}6667onDynamicExit(id: string): Event<{ code: number; signal: string }> {68return this._getExtHost(id).onExit;69}7071async createExtensionHost(): Promise<{ id: string }> {72if (this._shutdown) {73throw canceled();74}75const id = String(++ExtensionHostStarter._lastId);76const extHost = new WindowUtilityProcess(this._logService, this._windowsMainService, this._telemetryService, this._lifecycleMainService);77this._extHosts.set(id, extHost);78const disposable = extHost.onExit(({ pid, code, signal }) => {79disposable.dispose();80this._logService.info(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`);81setTimeout(() => {82extHost.dispose();83this._extHosts.delete(id);84});8586// See https://github.com/microsoft/vscode/issues/19447787// We have observed that sometimes the process sends an exit88// event, but does not really exit and is stuck in an endless89// loop. In these cases we kill the process forcefully after90// a certain timeout.91setTimeout(() => {92try {93process.kill(pid, 0); // will throw if the process doesn't exist anymore.94this._logService.error(`Extension host with pid ${pid} still exists, forcefully killing it...`);95process.kill(pid);96} catch (er) {97// ignore, as the process is already gone98}99}, 1000);100});101return { id };102}103104async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number | undefined }> {105if (this._shutdown) {106throw canceled();107}108const extHost = this._getExtHost(id);109const args = ['--skipWorkspaceStorageLock'];110if (this._configurationService.getValue<boolean>('extensions.supportNodeGlobalNavigator')) {111args.push('--supportGlobalNavigator');112}113extHost.start({114...opts,115type: 'extensionHost',116name: 'extension-host',117entryPoint: 'vs/workbench/api/node/extensionHostProcess',118args,119execArgv: opts.execArgv,120allowLoadingUnsignedLibraries: true,121respondToAuthRequestsFromMainProcess: true,122correlationId: id123});124const pid = await Event.toPromise(extHost.onSpawn);125return { pid };126}127128async enableInspectPort(id: string): Promise<boolean> {129if (this._shutdown) {130throw canceled();131}132const extHostProcess = this._extHosts.get(id);133if (!extHostProcess) {134return false;135}136return extHostProcess.enableInspectPort();137}138139async kill(id: string): Promise<void> {140if (this._shutdown) {141throw canceled();142}143const extHostProcess = this._extHosts.get(id);144if (!extHostProcess) {145// already gone!146return;147}148extHostProcess.kill();149}150151async _killAllNow(): Promise<void> {152for (const [, extHost] of this._extHosts) {153extHost.kill();154}155}156157async _waitForAllExit(maxWaitTimeMs: number): Promise<void> {158const exitPromises: Promise<void>[] = [];159for (const [, extHost] of this._extHosts) {160exitPromises.push(extHost.waitForExit(maxWaitTimeMs));161}162return Promises.settled(exitPromises).then(() => { });163}164}165166167