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