Path: blob/main/src/vs/workbench/services/extensions/common/remoteExtensionHost.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 { 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 { parseExtensionDevOptions } from './extensionDevOptions.js';26import { IExtensionHostInitData, MessageType, UIKind, createMessageOfType, isMessageOfType } from './extensionHostProtocol.js';27import { RemoteRunningLocation } from './extensionRunningLocation.js';28import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost } from './extensions.js';2930export interface IRemoteExtensionHostInitData {31readonly connectionData: IRemoteConnectionData | null;32readonly pid: number;33readonly appRoot: URI;34readonly extensionHostLogsPath: URI;35readonly globalStorageHome: URI;36readonly workspaceStorageHome: URI;37readonly extensions: ExtensionHostExtensions;38}3940export interface IRemoteExtensionHostDataProvider {41readonly remoteAuthority: string;42getInitData(): Promise<IRemoteExtensionHostInitData>;43}4445export class RemoteExtensionHost extends Disposable implements IExtensionHost {4647public readonly pid = null;48public readonly remoteAuthority: string;49public readonly startup = ExtensionHostStartup.EagerAutoStart;50public extensions: ExtensionHostExtensions | null = null;5152private _onExit: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());53public readonly onExit: Event<[number, string | null]> = this._onExit.event;5455private _protocol: PersistentProtocol | null;56private _hasLostConnection: boolean;57private _terminating: boolean;58private _hasDisconnected = false;59private readonly _isExtensionDevHost: boolean;6061constructor(62public readonly runningLocation: RemoteRunningLocation,63private readonly _initDataProvider: IRemoteExtensionHostDataProvider,64@IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService,65@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,66@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,67@ITelemetryService private readonly _telemetryService: ITelemetryService,68@ILogService private readonly _logService: ILogService,69@ILoggerService protected readonly _loggerService: ILoggerService,70@ILabelService private readonly _labelService: ILabelService,71@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,72@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,73@IProductService private readonly _productService: IProductService,74@ISignService private readonly _signService: ISignService75) {76super();77this.remoteAuthority = this._initDataProvider.remoteAuthority;78this._protocol = null;79this._hasLostConnection = false;80this._terminating = false;8182const devOpts = parseExtensionDevOptions(this._environmentService);83this._isExtensionDevHost = devOpts.isExtensionDevHost;84}8586public start(): Promise<IMessagePassingProtocol> {87const options: IConnectionOptions = {88commit: this._productService.commit,89quality: this._productService.quality,90addressProvider: {91getAddress: async () => {92const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);93return { connectTo: authority.connectTo, connectionToken: authority.connectionToken };94}95},96remoteSocketFactoryService: this.remoteSocketFactoryService,97signService: this._signService,98logService: this._logService,99ipcLogger: null100};101return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => {102103const startParams: IRemoteExtensionHostStartParams = {104language: platform.language,105debugId: this._environmentService.debugExtensionHost.debugId,106break: this._environmentService.debugExtensionHost.break,107port: this._environmentService.debugExtensionHost.port,108env: { ...this._environmentService.debugExtensionHost.env, ...resolverResult.options?.extensionHostEnv },109};110111const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;112113let debugOk = true;114if (extDevLocs && extDevLocs.length > 0) {115// TODO@AW: handles only first path in array116if (extDevLocs[0].scheme === Schemas.file) {117debugOk = false;118}119}120121if (!debugOk) {122startParams.break = false;123}124125return connectRemoteAgentExtensionHost(options, startParams).then(result => {126this._register(result);127const { protocol, debugPort, reconnectionToken } = result;128const isExtensionDevelopmentDebug = typeof debugPort === 'number';129if (debugOk && this._environmentService.isExtensionDevelopment && this._environmentService.debugExtensionHost.debugId && debugPort) {130this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority);131}132133protocol.onDidDispose(() => {134this._onExtHostConnectionLost(reconnectionToken);135});136137protocol.onSocketClose(() => {138if (this._isExtensionDevHost) {139this._onExtHostConnectionLost(reconnectionToken);140}141});142143// 1) wait for the incoming `ready` event and send the initialization data.144// 2) wait for the incoming `initialized` event.145return new Promise<IMessagePassingProtocol>((resolve, reject) => {146147const handle = setTimeout(() => {148reject('The remote extension host took longer than 60s to send its ready message.');149}, 60 * 1000);150151const disposable = protocol.onMessage(msg => {152153if (isMessageOfType(msg, MessageType.Ready)) {154// 1) Extension Host is ready to receive messages, initialize it155this._createExtHostInitData(isExtensionDevelopmentDebug).then(data => {156protocol.send(VSBuffer.fromString(JSON.stringify(data)));157});158return;159}160161if (isMessageOfType(msg, MessageType.Initialized)) {162// 2) Extension Host is initialized163164clearTimeout(handle);165166// stop listening for messages here167disposable.dispose();168169// release this promise170this._protocol = protocol;171resolve(protocol);172173return;174}175176console.error(`received unexpected message during handshake phase from the extension host: `, msg);177});178179});180});181});182}183184private _onExtHostConnectionLost(reconnectionToken: string): void {185if (this._hasLostConnection) {186// avoid re-entering this method187return;188}189this._hasLostConnection = true;190191if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId) {192this._extensionHostDebugService.close(this._environmentService.debugExtensionHost.debugId);193}194195if (this._terminating) {196// Expected termination path (we asked the process to terminate)197return;198}199200this._onExit.fire([0, reconnectionToken]);201}202203private async _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise<IExtensionHostInitData> {204const remoteInitData = await this._initDataProvider.getInitData();205this.extensions = remoteInitData.extensions;206const workspace = this._contextService.getWorkspace();207return {208commit: this._productService.commit,209version: this._productService.version,210quality: this._productService.quality,211date: this._productService.date,212parentPid: remoteInitData.pid,213environment: {214isExtensionDevelopmentDebug,215appRoot: remoteInitData.appRoot,216appName: this._productService.nameLong,217appHost: this._productService.embedderIdentifier || 'desktop',218appUriScheme: this._productService.urlProtocol,219isExtensionTelemetryLoggingOnly: isLoggingOnly(this._productService, this._environmentService),220appLanguage: platform.language,221extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,222extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,223globalStorageHome: remoteInitData.globalStorageHome,224workspaceStorageHome: remoteInitData.workspaceStorageHome,225extensionLogLevel: this._environmentService.extensionLogLevel226},227workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {228configuration: workspace.configuration,229id: workspace.id,230name: this._labelService.getWorkspaceLabel(workspace),231transient: workspace.transient232},233remote: {234isRemote: true,235authority: this._initDataProvider.remoteAuthority,236connectionData: remoteInitData.connectionData237},238consoleForward: {239includeStack: false,240logNative: Boolean(this._environmentService.debugExtensionHost.debugId)241},242extensions: this.extensions.toSnapshot(),243telemetryInfo: {244sessionId: this._telemetryService.sessionId,245machineId: this._telemetryService.machineId,246sqmId: this._telemetryService.sqmId,247devDeviceId: this._telemetryService.devDeviceId,248firstSessionDate: this._telemetryService.firstSessionDate,249msftInternal: this._telemetryService.msftInternal250},251logLevel: this._logService.getLevel(),252loggers: [...this._loggerService.getRegisteredLoggers()],253logsLocation: remoteInitData.extensionHostLogsPath,254autoStart: (this.startup === ExtensionHostStartup.EagerAutoStart),255uiKind: platform.isWeb ? UIKind.Web : UIKind.Desktop256};257}258259getInspectPort(): undefined {260return undefined;261}262263enableInspectPort(): Promise<boolean> {264return Promise.resolve(false);265}266267async disconnect() {268if (this._protocol && !this._hasDisconnected) {269this._protocol.send(createMessageOfType(MessageType.Terminate));270this._protocol.sendDisconnect();271this._hasDisconnected = true;272await this._protocol.drain();273}274}275276override dispose(): void {277super.dispose();278279this._terminating = true;280this.disconnect();281282if (this._protocol) {283// Send the extension host a request to terminate itself284// (graceful termination)285// setTimeout(() => {286// console.log(`SENDING TERMINATE TO REMOTE EXT HOST!`);287this._protocol.getSocket().end();288// this._protocol.drain();289this._protocol = null;290// }, 1000);291}292}293}294295296