Path: blob/main/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts
5250 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 { VSBuffer } from '../../../../base/common/buffer.js';6import { Emitter, Event } from '../../../../base/common/event.js';7import { Disposable } from '../../../../base/common/lifecycle.js';8import { Schemas } from '../../../../base/common/network.js';9import * as platform from '../../../../base/common/platform.js';10import { URI } from '../../../../base/common/uri.js';11import { IMessagePassingProtocol } from '../../../../base/parts/ipc/common/ipc.js';12import { PersistentProtocol } from '../../../../base/parts/ipc/common/ipc.net.js';13import { IExtensionHostDebugService } from '../../../../platform/debug/common/extensionHostDebug.js';14import { ILabelService } from '../../../../platform/label/common/label.js';15import { ILogService, ILoggerService } from '../../../../platform/log/common/log.js';16import { IProductService } from '../../../../platform/product/common/productService.js';17import { IConnectionOptions, IRemoteExtensionHostStartParams, connectRemoteAgentExtensionHost } from '../../../../platform/remote/common/remoteAgentConnection.js';18import { IRemoteAuthorityResolverService, IRemoteConnectionData } from '../../../../platform/remote/common/remoteAuthorityResolver.js';19import { IRemoteSocketFactoryService } from '../../../../platform/remote/common/remoteSocketFactoryService.js';20import { ISignService } from '../../../../platform/sign/common/sign.js';21import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';22import { isLoggingOnly } from '../../../../platform/telemetry/common/telemetryUtils.js';23import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';24import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';25import { IDefaultLogLevelsService } from '../../log/common/defaultLogLevels.js';26import { parseExtensionDevOptions } from './extensionDevOptions.js';27import { IExtensionHostInitData, MessageType, UIKind, createMessageOfType, isMessageOfType } from './extensionHostProtocol.js';28import { RemoteRunningLocation } from './extensionRunningLocation.js';29import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost } from './extensions.js';3031export interface IRemoteExtensionHostInitData {32readonly connectionData: IRemoteConnectionData | null;33readonly pid: number;34readonly appRoot: URI;35readonly extensionHostLogsPath: URI;36readonly globalStorageHome: URI;37readonly workspaceStorageHome: URI;38readonly extensions: ExtensionHostExtensions;39}4041export interface IRemoteExtensionHostDataProvider {42readonly remoteAuthority: string;43getInitData(): Promise<IRemoteExtensionHostInitData>;44}4546export class RemoteExtensionHost extends Disposable implements IExtensionHost {4748public readonly pid = null;49public readonly remoteAuthority: string;50public readonly startup = ExtensionHostStartup.EagerAutoStart;51public extensions: ExtensionHostExtensions | null = null;5253private _onExit: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());54public readonly onExit: Event<[number, string | null]> = this._onExit.event;5556private _protocol: PersistentProtocol | null;57private _hasLostConnection: boolean;58private _terminating: boolean;59private _hasDisconnected = false;60private readonly _isExtensionDevHost: boolean;6162constructor(63public readonly runningLocation: RemoteRunningLocation,64private readonly _initDataProvider: IRemoteExtensionHostDataProvider,65@IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService,66@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,67@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,68@ITelemetryService private readonly _telemetryService: ITelemetryService,69@ILogService private readonly _logService: ILogService,70@ILoggerService protected readonly _loggerService: ILoggerService,71@ILabelService private readonly _labelService: ILabelService,72@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,73@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,74@IProductService private readonly _productService: IProductService,75@ISignService private readonly _signService: ISignService,76@IDefaultLogLevelsService private readonly _defaultLogLevelsService: IDefaultLogLevelsService,77) {78super();79this.remoteAuthority = this._initDataProvider.remoteAuthority;80this._protocol = null;81this._hasLostConnection = false;82this._terminating = false;8384const devOpts = parseExtensionDevOptions(this._environmentService);85this._isExtensionDevHost = devOpts.isExtensionDevHost;86}8788public start(): Promise<IMessagePassingProtocol> {89const options: IConnectionOptions = {90commit: this._productService.commit,91quality: this._productService.quality,92addressProvider: {93getAddress: async () => {94const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);95return { connectTo: authority.connectTo, connectionToken: authority.connectionToken };96}97},98remoteSocketFactoryService: this.remoteSocketFactoryService,99signService: this._signService,100logService: this._logService,101ipcLogger: null102};103return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => {104105const startParams: IRemoteExtensionHostStartParams = {106language: platform.language,107debugId: this._environmentService.debugExtensionHost.debugId,108break: this._environmentService.debugExtensionHost.break,109port: this._environmentService.debugExtensionHost.port,110env: { ...this._environmentService.debugExtensionHost.env, ...resolverResult.options?.extensionHostEnv },111};112113const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;114115let debugOk = true;116if (extDevLocs && extDevLocs.length > 0) {117// TODO@AW: handles only first path in array118if (extDevLocs[0].scheme === Schemas.file) {119debugOk = false;120}121}122123if (!debugOk) {124startParams.break = false;125}126127return connectRemoteAgentExtensionHost(options, startParams).then(result => {128this._register(result);129const { protocol, debugPort, reconnectionToken } = result;130const isExtensionDevelopmentDebug = typeof debugPort === 'number';131if (debugOk && this._environmentService.isExtensionDevelopment && this._environmentService.debugExtensionHost.debugId && debugPort) {132this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority);133}134135protocol.onDidDispose(() => {136this._onExtHostConnectionLost(reconnectionToken);137});138139protocol.onSocketClose(() => {140if (this._isExtensionDevHost) {141this._onExtHostConnectionLost(reconnectionToken);142}143});144145// 1) wait for the incoming `ready` event and send the initialization data.146// 2) wait for the incoming `initialized` event.147return new Promise<IMessagePassingProtocol>((resolve, reject) => {148149const handle = setTimeout(() => {150reject('The remote extension host took longer than 60s to send its ready message.');151}, 60 * 1000);152153const disposable = protocol.onMessage(msg => {154155if (isMessageOfType(msg, MessageType.Ready)) {156// 1) Extension Host is ready to receive messages, initialize it157this._createExtHostInitData(isExtensionDevelopmentDebug).then(data => {158protocol.send(VSBuffer.fromString(JSON.stringify(data)));159});160return;161}162163if (isMessageOfType(msg, MessageType.Initialized)) {164// 2) Extension Host is initialized165166clearTimeout(handle);167168// stop listening for messages here169disposable.dispose();170171// release this promise172this._protocol = protocol;173resolve(protocol);174175return;176}177178console.error(`received unexpected message during handshake phase from the extension host: `, msg);179});180181});182});183});184}185186private _onExtHostConnectionLost(reconnectionToken: string): void {187if (this._hasLostConnection) {188// avoid re-entering this method189return;190}191this._hasLostConnection = true;192193if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId) {194this._extensionHostDebugService.close(this._environmentService.debugExtensionHost.debugId);195}196197if (this._terminating) {198// Expected termination path (we asked the process to terminate)199return;200}201202this._onExit.fire([0, reconnectionToken]);203}204205private async _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise<IExtensionHostInitData> {206const remoteInitData = await this._initDataProvider.getInitData();207this.extensions = remoteInitData.extensions;208const workspace = this._contextService.getWorkspace();209return {210commit: this._productService.commit,211version: this._productService.version,212quality: this._productService.quality,213date: this._productService.date,214parentPid: remoteInitData.pid,215environment: {216isExtensionDevelopmentDebug,217appRoot: remoteInitData.appRoot,218appName: this._productService.nameLong,219appHost: this._productService.embedderIdentifier || 'desktop',220appUriScheme: this._productService.urlProtocol,221isExtensionTelemetryLoggingOnly: isLoggingOnly(this._productService, this._environmentService),222appLanguage: platform.language,223extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,224extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,225globalStorageHome: remoteInitData.globalStorageHome,226workspaceStorageHome: remoteInitData.workspaceStorageHome,227extensionLogLevel: this._defaultLogLevelsService.defaultLogLevels.extensions228},229workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {230configuration: workspace.configuration,231id: workspace.id,232name: this._labelService.getWorkspaceLabel(workspace),233transient: workspace.transient234},235remote: {236isRemote: true,237authority: this._initDataProvider.remoteAuthority,238connectionData: remoteInitData.connectionData239},240consoleForward: {241includeStack: false,242logNative: Boolean(this._environmentService.debugExtensionHost.debugId)243},244extensions: this.extensions.toSnapshot(),245telemetryInfo: {246sessionId: this._telemetryService.sessionId,247machineId: this._telemetryService.machineId,248sqmId: this._telemetryService.sqmId,249devDeviceId: this._telemetryService.devDeviceId ?? this._telemetryService.machineId,250firstSessionDate: this._telemetryService.firstSessionDate,251msftInternal: this._telemetryService.msftInternal252},253remoteExtensionTips: this._productService.remoteExtensionTips,254virtualWorkspaceExtensionTips: this._productService.virtualWorkspaceExtensionTips,255logLevel: this._logService.getLevel(),256loggers: [...this._loggerService.getRegisteredLoggers()],257logsLocation: remoteInitData.extensionHostLogsPath,258autoStart: (this.startup === ExtensionHostStartup.EagerAutoStart),259uiKind: platform.isWeb ? UIKind.Web : UIKind.Desktop260};261}262263getInspectPort(): undefined {264return undefined;265}266267enableInspectPort(): Promise<boolean> {268return Promise.resolve(false);269}270271async disconnect() {272if (this._protocol && !this._hasDisconnected) {273this._protocol.send(createMessageOfType(MessageType.Terminate));274this._protocol.sendDisconnect();275this._hasDisconnected = true;276await this._protocol.drain();277}278}279280override dispose(): void {281super.dispose();282283this._terminating = true;284this.disconnect();285286if (this._protocol) {287// Send the extension host a request to terminate itself288// (graceful termination)289// setTimeout(() => {290// console.log(`SENDING TERMINATE TO REMOTE EXT HOST!`);291this._protocol.getSocket().end();292// this._protocol.drain();293this._protocol = null;294// }, 1000);295}296}297}298299300