diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 2f35bafb7dc..b0edfdd1803 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -8,7 +8,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { isCancellationError, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { generateUuid } from 'vs/base/common/uuid'; import { IIPCLogger } from 'vs/base/parts/ipc/common/ipc'; import { Client, ISocket, PersistentProtocol, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net'; @@ -539,17 +539,21 @@ export abstract class PersistentConnection extends Disposable { private readonly _onDidStateChange = this._register(new Emitter()); public readonly onDidStateChange = this._onDidStateChange.event; - protected readonly _options: IConnectionOptions; - public readonly reconnectionToken: string; - public readonly protocol: PersistentProtocol; + private _permanentFailure: boolean = false; + private get _isPermanentFailure(): boolean { + return this._permanentFailure || PersistentConnection._permanentFailure; + } private _isReconnecting: boolean; - constructor(private readonly _connectionType: ConnectionType, options: IConnectionOptions, reconnectionToken: string, protocol: PersistentProtocol) { + constructor( + private readonly _connectionType: ConnectionType, + protected readonly _options: IConnectionOptions, + public readonly reconnectionToken: string, + public readonly protocol: PersistentProtocol, + private readonly _reconnectionFailureIsFatal: boolean + ) { super(); - this._options = options; - this.reconnectionToken = reconnectionToken; - this.protocol = protocol; this._isReconnecting = false; this._onDidStateChange.fire(new ConnectionGainEvent(this.reconnectionToken, 0, 0)); @@ -578,8 +582,14 @@ export abstract class PersistentConnection extends Disposable { })); PersistentConnection._instances.push(this); + this._register(toDisposable(() => { + const myIndex = PersistentConnection._instances.indexOf(this); + if (myIndex >= 0) { + PersistentConnection._instances.splice(myIndex, 1); + } + })); - if (PersistentConnection._permanentFailure) { + if (this._isPermanentFailure) { this._gotoPermanentFailure(PersistentConnection._permanentFailureMillisSinceLastIncomingData, PersistentConnection._permanentFailureAttempt, PersistentConnection._permanentFailureHandled); } } @@ -598,7 +608,7 @@ export abstract class PersistentConnection extends Disposable { } private async _runReconnectingLoop(): Promise { - if (PersistentConnection._permanentFailure) { + if (this._isPermanentFailure) { // no more attempts! return; } @@ -621,7 +631,7 @@ export abstract class PersistentConnection extends Disposable { } catch { } // User canceled timer } - if (PersistentConnection._permanentFailure) { + if (this._isPermanentFailure) { this._options.logService.error(`${logPrefix} permanent failure occurred while running the reconnecting loop.`); break; } @@ -640,14 +650,14 @@ export abstract class PersistentConnection extends Disposable { if (err.code === 'VSCODE_CONNECTION_ERROR') { this._options.logService.error(`${logPrefix} A permanent error occurred in the reconnecting loop! Will give up now! Error:`); this._options.logService.error(err); - PersistentConnection.triggerPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false); + this._onReconnectionPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false); break; } if (attempt > 360) { // ReconnectionGraceTime is 3hrs, with 30s between attempts that yields a maximum of 360 attempts this._options.logService.error(`${logPrefix} An error occurred while reconnecting, but it will be treated as a permanent error because the reconnection grace time has expired! Will give up now! Error:`); this._options.logService.error(err); - PersistentConnection.triggerPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false); + this._onReconnectionPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false); break; } if (RemoteAuthorityResolverError.isTemporarilyNotAvailable(err)) { @@ -671,15 +681,23 @@ export abstract class PersistentConnection extends Disposable { if (err instanceof RemoteAuthorityResolverError) { this._options.logService.error(`${logPrefix} A RemoteAuthorityResolverError occurred while trying to reconnect. Will give up now! Error:`); this._options.logService.error(err); - PersistentConnection.triggerPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, RemoteAuthorityResolverError.isHandled(err)); + this._onReconnectionPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, RemoteAuthorityResolverError.isHandled(err)); break; } this._options.logService.error(`${logPrefix} An unknown error occurred while trying to reconnect, since this is an unknown case, it will be treated as a permanent error! Will give up now! Error:`); this._options.logService.error(err); - PersistentConnection.triggerPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false); + this._onReconnectionPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false); break; } - } while (!PersistentConnection._permanentFailure); + } while (!this._isPermanentFailure); + } + + private _onReconnectionPermanentFailure(millisSinceLastIncomingData: number, attempt: number, handled: boolean): void { + if (this._reconnectionFailureIsFatal) { + PersistentConnection.triggerPermanentFailure(millisSinceLastIncomingData, attempt, handled); + } else { + this._gotoPermanentFailure(millisSinceLastIncomingData, attempt, handled); + } } private _gotoPermanentFailure(millisSinceLastIncomingData: number, attempt: number, handled: boolean): void { @@ -699,7 +717,7 @@ export class ManagementPersistentConnection extends PersistentConnection { public readonly client: Client; constructor(options: IConnectionOptions, remoteAuthority: string, clientId: string, reconnectionToken: string, protocol: PersistentProtocol) { - super(ConnectionType.Management, options, reconnectionToken, protocol); + super(ConnectionType.Management, options, reconnectionToken, protocol, /*reconnectionFailureIsFatal*/true); this.client = this._register(new Client(protocol, { remoteAuthority: remoteAuthority, clientId: clientId @@ -717,7 +735,7 @@ export class ExtensionHostPersistentConnection extends PersistentConnection { public readonly debugPort: number | undefined; constructor(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams, reconnectionToken: string, protocol: PersistentProtocol, debugPort: number | undefined) { - super(ConnectionType.ExtensionHost, options, reconnectionToken, protocol); + super(ConnectionType.ExtensionHost, options, reconnectionToken, protocol, /*reconnectionFailureIsFatal*/false); this._startArguments = startArguments; this.debugPort = debugPort; } diff --git a/src/vs/server/node/extensionHostConnection.ts b/src/vs/server/node/extensionHostConnection.ts index ab46e83eccb..3656526d871 100644 --- a/src/vs/server/node/extensionHostConnection.ts +++ b/src/vs/server/node/extensionHostConnection.ts @@ -20,6 +20,7 @@ import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentServi import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; import { removeDangerousEnvVariables } from 'vs/base/node/processes'; +import { IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; export async function buildUserEnvironment(startParamsEnv: { [key: string]: string | null } = {}, withUserShellEnvironment: boolean, language: string, isDebug: boolean, environmentService: IServerEnvironmentService, logService: ILogService): Promise { const nlsConfig = await getNLSConfiguration(language, environmentService.userDataPath); @@ -102,12 +103,13 @@ export class ExtensionHostConnection { private _connectionData: ConnectionData | null; constructor( - private readonly _environmentService: IServerEnvironmentService, - private readonly _logService: ILogService, private readonly _reconnectionToken: string, remoteAddress: string, socket: NodeSocket | WebSocketNodeSocket, - initialDataChunk: VSBuffer + initialDataChunk: VSBuffer, + @IServerEnvironmentService private readonly _environmentService: IServerEnvironmentService, + @ILogService private readonly _logService: ILogService, + @IExtensionHostStatusService private readonly _extensionHostStatusService: IExtensionHostStatusService, ) { this._disposed = false; this._remoteAddress = remoteAddress; @@ -233,6 +235,7 @@ export class ExtensionHostConnection { }); this._extensionHostProcess.on('exit', (code: number, signal: string) => { + this._extensionHostStatusService.setExitInfo(this._reconnectionToken, { code, signal }); this._log(`<${pid}> Extension Host Process exited with code: ${code}, signal: ${signal}.`); this._cleanResources(); }); diff --git a/src/vs/server/node/extensionHostStatusService.ts b/src/vs/server/node/extensionHostStatusService.ts new file mode 100644 index 00000000000..d6f7c9e1beb --- /dev/null +++ b/src/vs/server/node/extensionHostStatusService.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtensionHostExitInfo } from 'vs/workbench/services/remote/common/remoteAgentService'; + +export const IExtensionHostStatusService = createDecorator('extensionHostStatusService'); + +export interface IExtensionHostStatusService { + readonly _serviceBrand: undefined; + + setExitInfo(reconnectionToken: string, info: IExtensionHostExitInfo): void; + getExitInfo(reconnectionToken: string): IExtensionHostExitInfo | null; +} + +export class ExtensionHostStatusService implements IExtensionHostStatusService { + _serviceBrand: undefined; + + private readonly _exitInfo = new Map(); + + setExitInfo(reconnectionToken: string, info: IExtensionHostExitInfo): void { + this._exitInfo.set(reconnectionToken, info); + } + + getExitInfo(reconnectionToken: string): IExtensionHostExitInfo | null { + return this._exitInfo.get(reconnectionToken) || null; + } +} diff --git a/src/vs/server/node/remoteAgentEnvironmentImpl.ts b/src/vs/server/node/remoteAgentEnvironmentImpl.ts index 9333c996382..cbaa32ca6c2 100644 --- a/src/vs/server/node/remoteAgentEnvironmentImpl.ts +++ b/src/vs/server/node/remoteAgentEnvironmentImpl.ts @@ -8,7 +8,7 @@ import * as platform from 'vs/base/common/platform'; import * as performance from 'vs/base/common/performance'; import { URI } from 'vs/base/common/uri'; import { createURITransformer } from 'vs/workbench/api/node/uriTransformer'; -import { IRemoteAgentEnvironmentDTO, IGetEnvironmentDataArguments, IScanExtensionsArguments, IScanSingleExtensionArguments } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel'; +import { IRemoteAgentEnvironmentDTO, IGetEnvironmentDataArguments, IScanExtensionsArguments, IScanSingleExtensionArguments, IGetExtensionHostExitInfoArguments } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel'; import * as nls from 'vs/nls'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService'; @@ -30,6 +30,7 @@ import { cwd } from 'vs/base/common/process'; import * as pfs from 'vs/base/node/pfs'; import { IProductService } from 'vs/platform/product/common/productService'; import { ServerConnectionToken, ServerConnectionTokenType } from 'vs/server/node/serverConnectionToken'; +import { IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; let _SystemExtensionsRoot: string | null = null; function getSystemExtensionsRoot(): string { @@ -59,7 +60,8 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { private readonly environmentService: IServerEnvironmentService, extensionManagementCLIService: IExtensionManagementCLIService, private readonly logService: ILogService, - private readonly productService: IProductService + private readonly productService: IProductService, + private readonly extensionHostStatusService: IExtensionHostStatusService, ) { this._logger = new class implements ILog { public error(source: string, message: string): void { @@ -113,6 +115,11 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { return environmentData; } + case 'getExtensionHostExitInfo': { + const args = arg; + return this.extensionHostStatusService.getExitInfo(args.reconnectionToken); + } + case 'whenExtensionsReady': { await this.whenExtensionsReady; return; diff --git a/src/vs/server/node/remoteExtensionHostAgentServer.ts b/src/vs/server/node/remoteExtensionHostAgentServer.ts index eebeab7637f..11f7e7f7c31 100644 --- a/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -481,7 +481,7 @@ export class RemoteExtensionHostAgentServer extends Disposable { protocol.sendControl(VSBuffer.fromString(JSON.stringify(startParams.port ? { debugPort: startParams.port } : {}))); const dataChunk = protocol.readEntireBuffer(); protocol.dispose(); - const con = new ExtensionHostConnection(this._environmentService, this._logService, reconnectionToken, remoteAddress, socket, dataChunk); + const con = this._instantiationService.createInstance(ExtensionHostConnection, reconnectionToken, remoteAddress, socket, dataChunk); this._extHostConnections[reconnectionToken] = con; this._allReconnectionTokens.add(reconnectionToken); con.onClose(() => { diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index 6c6e74409a0..9d6327cd9e3 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -67,6 +67,7 @@ import { ServerEnvironmentService, ServerParsedArgs } from 'vs/server/node/serve import { REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel'; import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/common/remoteFileSystemProviderClient'; +import { ExtensionHostStatusService, IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; const eventPrefix = 'monacoworkbench'; @@ -107,6 +108,9 @@ export async function setupServerServices(connectionToken: ServerConnectionToken const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService); services.set(IConfigurationService, configurationService); + const extensionHostStatusService = new ExtensionHostStatusService(); + services.set(IExtensionHostStatusService, extensionHostStatusService); + // URI Identity services.set(IUriIdentityService, new UriIdentityService(fileService)); @@ -171,7 +175,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(ICredentialsMainService, new SyncDescriptor(CredentialsMainService, [true])); instantiationService.invokeFunction(accessor => { - const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, extensionManagementCLIService, logService, productService); + const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, extensionManagementCLIService, logService, productService, extensionHostStatusService); socketServer.registerChannel('remoteextensionsenvironment', remoteExtensionEnvironmentChannel); const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), appInsightsAppender); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index ff5696b45fa..60b51cb441e 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -51,8 +51,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService, @ILogService logService: ILogService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService, ) { @@ -69,7 +69,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten configurationService, extensionManifestPropertiesService, webExtensionsScannerService, - logService + logService, + remoteAgentService ); this._runningLocation = new Map(); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 8f0f35b84fe..1fc62c93ef4 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -37,6 +37,7 @@ import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensi import { ApiProposalName, allApiProposals } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; import { forEach } from 'vs/base/common/collections'; import { ILogService } from 'vs/platform/log/common/log'; +import { IExtensionHostExitInfo, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -184,6 +185,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx @IExtensionManifestPropertiesService protected readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IWebExtensionsScannerService protected readonly _webExtensionsScannerService: IWebExtensionsScannerService, @ILogService protected readonly _logService: ILogService, + @IRemoteAgentService protected readonly _remoteAgentService: IRemoteAgentService, ) { super(); @@ -645,6 +647,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx if (extensionHost.kind === ExtensionHostKind.LocalProcess) { this.stopExtensionHosts(); } else if (extensionHost.kind === ExtensionHostKind.Remote) { + if (signal) { + this._onRemoteExtensionHostCrashed(signal); + } for (let i = 0; i < this._extensionHostManagers.length; i++) { if (this._extensionHostManagers[i] === extensionHost) { this._extensionHostManagers[i].dispose(); @@ -655,6 +660,38 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } } + private _getExtensionHostExitInfoWithTimeout(reconnectionToken: string): Promise { + return new Promise((resolve, reject) => { + const timeoutHandle = setTimeout(() => { + reject(new Error('getExtensionHostExitInfo timed out')); + }, 2000); + this._remoteAgentService.getExtensionHostExitInfo(reconnectionToken).then( + (r) => { + clearTimeout(timeoutHandle); + resolve(r); + }, + reject + ); + }); + } + + private async _onRemoteExtensionHostCrashed(reconnectionToken: string): Promise { + try { + const info = await this._getExtensionHostExitInfoWithTimeout(reconnectionToken); + // TODO: + console.error(info?.code, info?.signal); + this._notificationService.prompt(Severity.Error, nls.localize('extensionService.crash', "Remote Extension host terminated unexpectedly."), + [{ + label: nls.localize('restart', "Restart Extension Host"), + run: () => console.log(`todo`)//this.startExtensionHosts() + }] + ); + + } catch (err) { + // maybe this wasn't an extension host crash and it was a permanent disconnection + } + } + public async startExtensionHosts(): Promise { this.stopExtensionHosts(); diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 91def9ac7da..f74ca7ec9b7 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -126,19 +126,20 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { } return connectRemoteAgentExtensionHost(options, startParams).then(result => { - let { protocol, debugPort } = result; + this._register(result); + let { protocol, debugPort, reconnectionToken } = result; const isExtensionDevelopmentDebug = typeof debugPort === 'number'; if (debugOk && this._environmentService.isExtensionDevelopment && this._environmentService.debugExtensionHost.debugId && debugPort) { this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority); } protocol.onDidDispose(() => { - this._onExtHostConnectionLost(); + this._onExtHostConnectionLost(reconnectionToken); }); protocol.onSocketClose(() => { if (this._isExtensionDevHost) { - this._onExtHostConnectionLost(); + this._onExtHostConnectionLost(reconnectionToken); } }); @@ -189,7 +190,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { }); } - private _onExtHostConnectionLost(): void { + private _onExtHostConnectionLost(reconnectionToken: string): void { if (this._hasLostConnection) { // avoid re-entering this method return; @@ -205,7 +206,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { return; } - this._onExit.fire([0, null]); + this._onExit.fire([0, reconnectionToken]); } private async _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 783fe97db1d..7642a5d3155 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -71,7 +71,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService, @ILogService logService: ILogService, - @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @INativeHostService private readonly _nativeHostService: INativeHostService, @@ -93,7 +93,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten configurationService, extensionManifestPropertiesService, webExtensionsScannerService, - logService + logService, + remoteAgentService ); [this._enableLocalWebWorker, this._lazyLocalWebWorker] = this._isLocalWebWorkerEnabled(); diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index f079c249735..3ca8af075bd 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -8,7 +8,7 @@ import { IChannel, IServerChannel, getDelayedChannel, IPCLogger } from 'vs/base/ import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { connectRemoteAgentManagement, IConnectionOptions, ISocketFactory, PersistentConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection'; -import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { RemoteExtensionEnvironmentChannelClient } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel'; @@ -69,6 +69,13 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I return this._environment; } + getExtensionHostExitInfo(reconnectionToken: string): Promise { + return this._withChannel( + (channel, connection) => RemoteExtensionEnvironmentChannelClient.getExtensionHostExitInfo(channel, connection.remoteAuthority, reconnectionToken), + null + ); + } + whenExtensionsReady(): Promise { return this._withChannel( channel => RemoteExtensionEnvironmentChannelClient.whenExtensionsReady(channel), diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index c274c717974..6642aceaed2 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -11,11 +11,17 @@ import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensio import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; +import { IExtensionHostExitInfo } from 'vs/workbench/services/remote/common/remoteAgentService'; export interface IGetEnvironmentDataArguments { remoteAuthority: string; } +export interface IGetExtensionHostExitInfoArguments { + remoteAuthority: string; + reconnectionToken: string; +} + export interface IScanExtensionsArguments { language: string; remoteAuthority: string; @@ -74,6 +80,14 @@ export class RemoteExtensionEnvironmentChannelClient { }; } + static async getExtensionHostExitInfo(channel: IChannel, remoteAuthority: string, reconnectionToken: string): Promise { + const args: IGetExtensionHostExitInfoArguments = { + remoteAuthority, + reconnectionToken + }; + return channel.call('getExtensionHostExitInfo', args); + } + static async whenExtensionsReady(channel: IChannel): Promise { await channel.call('whenExtensionsReady'); } diff --git a/src/vs/workbench/services/remote/common/remoteAgentService.ts b/src/vs/workbench/services/remote/common/remoteAgentService.ts index 8e29fc481a6..b7508ffc4a7 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentService.ts @@ -31,6 +31,10 @@ export interface IRemoteAgentService { * Get the remote environment. Can return an error. */ getRawEnvironment(): Promise; + /** + * Get exit information for a remote extension host. + */ + getExtensionHostExitInfo(reconnectionToken: string): Promise; whenExtensionsReady(): Promise; /** @@ -47,6 +51,11 @@ export interface IRemoteAgentService { flushTelemetry(): Promise; } +export interface IExtensionHostExitInfo { + code: number; + signal: string; +} + export interface IRemoteAgentConnection { readonly remoteAuthority: string; diff --git a/src/vs/workbench/services/remote/test/common/testServices.ts b/src/vs/workbench/services/remote/test/common/testServices.ts index b2d6bc77d61..f4323c7bc65 100644 --- a/src/vs/workbench/services/remote/test/common/testServices.ts +++ b/src/vs/workbench/services/remote/test/common/testServices.ts @@ -9,7 +9,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class TestRemoteAgentService implements IRemoteAgentService { _serviceBrand: undefined; @@ -25,6 +25,9 @@ export class TestRemoteAgentService implements IRemoteAgentService { getRawEnvironment(): Promise { throw new Error('Method not implemented.'); } + getExtensionHostExitInfo(reconnectionToken: string): Promise { + throw new Error('Method not implemented.'); + } whenExtensionsReady(): Promise { throw new Error('Method not implemented.'); }