Path: blob/main/src/vs/platform/extensions/electron-main/extensionHostStarter.ts
5284 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<unknown> {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,122windowLifecycleBound: true,123windowLifecycleGraceTime: 6000,124correlationId: id125});126const pid = await Event.toPromise(extHost.onSpawn);127return { pid };128}129130async enableInspectPort(id: string): Promise<boolean> {131if (this._shutdown) {132throw canceled();133}134const extHostProcess = this._extHosts.get(id);135if (!extHostProcess) {136return false;137}138return extHostProcess.enableInspectPort();139}140141async kill(id: string): Promise<void> {142if (this._shutdown) {143throw canceled();144}145const extHostProcess = this._extHosts.get(id);146if (!extHostProcess) {147// already gone!148return;149}150extHostProcess.kill();151}152153async _killAllNow(): Promise<void> {154for (const [, extHost] of this._extHosts) {155extHost.kill();156}157}158159async _waitForAllExit(maxWaitTimeMs: number): Promise<void> {160const exitPromises: Promise<void>[] = [];161for (const [, extHost] of this._extHosts) {162exitPromises.push(extHost.waitForExit(maxWaitTimeMs));163}164return Promises.settled(exitPromises).then(() => { });165}166}167168169