Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts
5250 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { VSBuffer } from '../../../../base/common/buffer.js';
7
import { Emitter, Event } from '../../../../base/common/event.js';
8
import { Disposable } from '../../../../base/common/lifecycle.js';
9
import { Schemas } from '../../../../base/common/network.js';
10
import * as platform from '../../../../base/common/platform.js';
11
import { URI } from '../../../../base/common/uri.js';
12
import { IMessagePassingProtocol } from '../../../../base/parts/ipc/common/ipc.js';
13
import { PersistentProtocol } from '../../../../base/parts/ipc/common/ipc.net.js';
14
import { IExtensionHostDebugService } from '../../../../platform/debug/common/extensionHostDebug.js';
15
import { ILabelService } from '../../../../platform/label/common/label.js';
16
import { ILogService, ILoggerService } from '../../../../platform/log/common/log.js';
17
import { IProductService } from '../../../../platform/product/common/productService.js';
18
import { IConnectionOptions, IRemoteExtensionHostStartParams, connectRemoteAgentExtensionHost } from '../../../../platform/remote/common/remoteAgentConnection.js';
19
import { IRemoteAuthorityResolverService, IRemoteConnectionData } from '../../../../platform/remote/common/remoteAuthorityResolver.js';
20
import { IRemoteSocketFactoryService } from '../../../../platform/remote/common/remoteSocketFactoryService.js';
21
import { ISignService } from '../../../../platform/sign/common/sign.js';
22
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
23
import { isLoggingOnly } from '../../../../platform/telemetry/common/telemetryUtils.js';
24
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
25
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
26
import { IDefaultLogLevelsService } from '../../log/common/defaultLogLevels.js';
27
import { parseExtensionDevOptions } from './extensionDevOptions.js';
28
import { IExtensionHostInitData, MessageType, UIKind, createMessageOfType, isMessageOfType } from './extensionHostProtocol.js';
29
import { RemoteRunningLocation } from './extensionRunningLocation.js';
30
import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost } from './extensions.js';
31
32
export interface IRemoteExtensionHostInitData {
33
readonly connectionData: IRemoteConnectionData | null;
34
readonly pid: number;
35
readonly appRoot: URI;
36
readonly extensionHostLogsPath: URI;
37
readonly globalStorageHome: URI;
38
readonly workspaceStorageHome: URI;
39
readonly extensions: ExtensionHostExtensions;
40
}
41
42
export interface IRemoteExtensionHostDataProvider {
43
readonly remoteAuthority: string;
44
getInitData(): Promise<IRemoteExtensionHostInitData>;
45
}
46
47
export class RemoteExtensionHost extends Disposable implements IExtensionHost {
48
49
public readonly pid = null;
50
public readonly remoteAuthority: string;
51
public readonly startup = ExtensionHostStartup.EagerAutoStart;
52
public extensions: ExtensionHostExtensions | null = null;
53
54
private _onExit: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());
55
public readonly onExit: Event<[number, string | null]> = this._onExit.event;
56
57
private _protocol: PersistentProtocol | null;
58
private _hasLostConnection: boolean;
59
private _terminating: boolean;
60
private _hasDisconnected = false;
61
private readonly _isExtensionDevHost: boolean;
62
63
constructor(
64
public readonly runningLocation: RemoteRunningLocation,
65
private readonly _initDataProvider: IRemoteExtensionHostDataProvider,
66
@IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
67
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
68
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
69
@ITelemetryService private readonly _telemetryService: ITelemetryService,
70
@ILogService private readonly _logService: ILogService,
71
@ILoggerService protected readonly _loggerService: ILoggerService,
72
@ILabelService private readonly _labelService: ILabelService,
73
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
74
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,
75
@IProductService private readonly _productService: IProductService,
76
@ISignService private readonly _signService: ISignService,
77
@IDefaultLogLevelsService private readonly _defaultLogLevelsService: IDefaultLogLevelsService,
78
) {
79
super();
80
this.remoteAuthority = this._initDataProvider.remoteAuthority;
81
this._protocol = null;
82
this._hasLostConnection = false;
83
this._terminating = false;
84
85
const devOpts = parseExtensionDevOptions(this._environmentService);
86
this._isExtensionDevHost = devOpts.isExtensionDevHost;
87
}
88
89
public start(): Promise<IMessagePassingProtocol> {
90
const options: IConnectionOptions = {
91
commit: this._productService.commit,
92
quality: this._productService.quality,
93
addressProvider: {
94
getAddress: async () => {
95
const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
96
return { connectTo: authority.connectTo, connectionToken: authority.connectionToken };
97
}
98
},
99
remoteSocketFactoryService: this.remoteSocketFactoryService,
100
signService: this._signService,
101
logService: this._logService,
102
ipcLogger: null
103
};
104
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => {
105
106
const startParams: IRemoteExtensionHostStartParams = {
107
language: platform.language,
108
debugId: this._environmentService.debugExtensionHost.debugId,
109
break: this._environmentService.debugExtensionHost.break,
110
port: this._environmentService.debugExtensionHost.port,
111
env: { ...this._environmentService.debugExtensionHost.env, ...resolverResult.options?.extensionHostEnv },
112
};
113
114
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
115
116
let debugOk = true;
117
if (extDevLocs && extDevLocs.length > 0) {
118
// TODO@AW: handles only first path in array
119
if (extDevLocs[0].scheme === Schemas.file) {
120
debugOk = false;
121
}
122
}
123
124
if (!debugOk) {
125
startParams.break = false;
126
}
127
128
return connectRemoteAgentExtensionHost(options, startParams).then(result => {
129
this._register(result);
130
const { protocol, debugPort, reconnectionToken } = result;
131
const isExtensionDevelopmentDebug = typeof debugPort === 'number';
132
if (debugOk && this._environmentService.isExtensionDevelopment && this._environmentService.debugExtensionHost.debugId && debugPort) {
133
this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority);
134
}
135
136
protocol.onDidDispose(() => {
137
this._onExtHostConnectionLost(reconnectionToken);
138
});
139
140
protocol.onSocketClose(() => {
141
if (this._isExtensionDevHost) {
142
this._onExtHostConnectionLost(reconnectionToken);
143
}
144
});
145
146
// 1) wait for the incoming `ready` event and send the initialization data.
147
// 2) wait for the incoming `initialized` event.
148
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
149
150
const handle = setTimeout(() => {
151
reject('The remote extension host took longer than 60s to send its ready message.');
152
}, 60 * 1000);
153
154
const disposable = protocol.onMessage(msg => {
155
156
if (isMessageOfType(msg, MessageType.Ready)) {
157
// 1) Extension Host is ready to receive messages, initialize it
158
this._createExtHostInitData(isExtensionDevelopmentDebug).then(data => {
159
protocol.send(VSBuffer.fromString(JSON.stringify(data)));
160
});
161
return;
162
}
163
164
if (isMessageOfType(msg, MessageType.Initialized)) {
165
// 2) Extension Host is initialized
166
167
clearTimeout(handle);
168
169
// stop listening for messages here
170
disposable.dispose();
171
172
// release this promise
173
this._protocol = protocol;
174
resolve(protocol);
175
176
return;
177
}
178
179
console.error(`received unexpected message during handshake phase from the extension host: `, msg);
180
});
181
182
});
183
});
184
});
185
}
186
187
private _onExtHostConnectionLost(reconnectionToken: string): void {
188
if (this._hasLostConnection) {
189
// avoid re-entering this method
190
return;
191
}
192
this._hasLostConnection = true;
193
194
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId) {
195
this._extensionHostDebugService.close(this._environmentService.debugExtensionHost.debugId);
196
}
197
198
if (this._terminating) {
199
// Expected termination path (we asked the process to terminate)
200
return;
201
}
202
203
this._onExit.fire([0, reconnectionToken]);
204
}
205
206
private async _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise<IExtensionHostInitData> {
207
const remoteInitData = await this._initDataProvider.getInitData();
208
this.extensions = remoteInitData.extensions;
209
const workspace = this._contextService.getWorkspace();
210
return {
211
commit: this._productService.commit,
212
version: this._productService.version,
213
quality: this._productService.quality,
214
date: this._productService.date,
215
parentPid: remoteInitData.pid,
216
environment: {
217
isExtensionDevelopmentDebug,
218
appRoot: remoteInitData.appRoot,
219
appName: this._productService.nameLong,
220
appHost: this._productService.embedderIdentifier || 'desktop',
221
appUriScheme: this._productService.urlProtocol,
222
isExtensionTelemetryLoggingOnly: isLoggingOnly(this._productService, this._environmentService),
223
appLanguage: platform.language,
224
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
225
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
226
globalStorageHome: remoteInitData.globalStorageHome,
227
workspaceStorageHome: remoteInitData.workspaceStorageHome,
228
extensionLogLevel: this._defaultLogLevelsService.defaultLogLevels.extensions
229
},
230
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
231
configuration: workspace.configuration,
232
id: workspace.id,
233
name: this._labelService.getWorkspaceLabel(workspace),
234
transient: workspace.transient
235
},
236
remote: {
237
isRemote: true,
238
authority: this._initDataProvider.remoteAuthority,
239
connectionData: remoteInitData.connectionData
240
},
241
consoleForward: {
242
includeStack: false,
243
logNative: Boolean(this._environmentService.debugExtensionHost.debugId)
244
},
245
extensions: this.extensions.toSnapshot(),
246
telemetryInfo: {
247
sessionId: this._telemetryService.sessionId,
248
machineId: this._telemetryService.machineId,
249
sqmId: this._telemetryService.sqmId,
250
devDeviceId: this._telemetryService.devDeviceId ?? this._telemetryService.machineId,
251
firstSessionDate: this._telemetryService.firstSessionDate,
252
msftInternal: this._telemetryService.msftInternal
253
},
254
remoteExtensionTips: this._productService.remoteExtensionTips,
255
virtualWorkspaceExtensionTips: this._productService.virtualWorkspaceExtensionTips,
256
logLevel: this._logService.getLevel(),
257
loggers: [...this._loggerService.getRegisteredLoggers()],
258
logsLocation: remoteInitData.extensionHostLogsPath,
259
autoStart: (this.startup === ExtensionHostStartup.EagerAutoStart),
260
uiKind: platform.isWeb ? UIKind.Web : UIKind.Desktop
261
};
262
}
263
264
getInspectPort(): undefined {
265
return undefined;
266
}
267
268
enableInspectPort(): Promise<boolean> {
269
return Promise.resolve(false);
270
}
271
272
async disconnect() {
273
if (this._protocol && !this._hasDisconnected) {
274
this._protocol.send(createMessageOfType(MessageType.Terminate));
275
this._protocol.sendDisconnect();
276
this._hasDisconnected = true;
277
await this._protocol.drain();
278
}
279
}
280
281
override dispose(): void {
282
super.dispose();
283
284
this._terminating = true;
285
this.disconnect();
286
287
if (this._protocol) {
288
// Send the extension host a request to terminate itself
289
// (graceful termination)
290
// setTimeout(() => {
291
// console.log(`SENDING TERMINATE TO REMOTE EXT HOST!`);
292
this._protocol.getSocket().end();
293
// this._protocol.drain();
294
this._protocol = null;
295
// }, 1000);
296
}
297
}
298
}
299
300