Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts
5252 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 { timeout } from '../../../../base/common/async.js';
7
import { encodeBase64, VSBuffer } from '../../../../base/common/buffer.js';
8
import { CancellationError } from '../../../../base/common/errors.js';
9
import { Emitter, Event } from '../../../../base/common/event.js';
10
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
11
import * as objects from '../../../../base/common/objects.js';
12
import * as platform from '../../../../base/common/platform.js';
13
import { removeDangerousEnvVariables } from '../../../../base/common/processes.js';
14
import { StopWatch } from '../../../../base/common/stopwatch.js';
15
import { URI } from '../../../../base/common/uri.js';
16
import { generateUuid } from '../../../../base/common/uuid.js';
17
import { IMessagePassingProtocol } from '../../../../base/parts/ipc/common/ipc.js';
18
import { BufferedEmitter } from '../../../../base/parts/ipc/common/ipc.net.js';
19
import { acquirePort } from '../../../../base/parts/ipc/electron-browser/ipc.mp.js';
20
import * as nls from '../../../../nls.js';
21
import { IExtensionHostDebugService } from '../../../../platform/debug/common/extensionHostDebug.js';
22
import { IExtensionHostProcessOptions, IExtensionHostStarter } from '../../../../platform/extensions/common/extensionHostStarter.js';
23
import { ILabelService } from '../../../../platform/label/common/label.js';
24
import { ILogService, ILoggerService } from '../../../../platform/log/common/log.js';
25
import { INativeHostService } from '../../../../platform/native/common/native.js';
26
import { INotificationService, NotificationPriority, Severity } from '../../../../platform/notification/common/notification.js';
27
import { IProductService } from '../../../../platform/product/common/productService.js';
28
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
29
import { isLoggingOnly } from '../../../../platform/telemetry/common/telemetryUtils.js';
30
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
31
import { IWorkspaceContextService, WorkbenchState, isUntitledWorkspace } from '../../../../platform/workspace/common/workspace.js';
32
import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js';
33
import { IShellEnvironmentService } from '../../environment/electron-browser/shellEnvironmentService.js';
34
import { MessagePortExtHostConnection, writeExtHostConnection } from '../common/extensionHostEnv.js';
35
import { IExtensionHostInitData, MessageType, NativeLogMarkers, UIKind, isMessageOfType } from '../common/extensionHostProtocol.js';
36
import { LocalProcessRunningLocation } from '../common/extensionRunningLocation.js';
37
import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost, IExtensionInspectInfo } from '../common/extensions.js';
38
import { IHostService } from '../../host/browser/host.js';
39
import { ILifecycleService, WillShutdownEvent } from '../../lifecycle/common/lifecycle.js';
40
import { parseExtensionDevOptions } from '../common/extensionDevOptions.js';
41
import { IDefaultLogLevelsService } from '../../log/common/defaultLogLevels.js';
42
43
export interface ILocalProcessExtensionHostInitData {
44
readonly extensions: ExtensionHostExtensions;
45
}
46
47
export interface ILocalProcessExtensionHostDataProvider {
48
getInitData(): Promise<ILocalProcessExtensionHostInitData>;
49
}
50
51
export class ExtensionHostProcess {
52
53
private readonly _id: string;
54
55
public get onStdout(): Event<string> {
56
return this._extensionHostStarter.onDynamicStdout(this._id);
57
}
58
59
public get onStderr(): Event<string> {
60
return this._extensionHostStarter.onDynamicStderr(this._id);
61
}
62
63
public get onMessage(): Event<unknown> {
64
return this._extensionHostStarter.onDynamicMessage(this._id);
65
}
66
67
public get onExit(): Event<{ code: number; signal: string }> {
68
return this._extensionHostStarter.onDynamicExit(this._id);
69
}
70
71
constructor(
72
id: string,
73
private readonly _extensionHostStarter: IExtensionHostStarter,
74
) {
75
this._id = id;
76
}
77
78
public start(opts: IExtensionHostProcessOptions): Promise<{ pid: number | undefined }> {
79
return this._extensionHostStarter.start(this._id, opts);
80
}
81
82
public enableInspectPort(): Promise<boolean> {
83
return this._extensionHostStarter.enableInspectPort(this._id);
84
}
85
86
public kill(): Promise<void> {
87
return this._extensionHostStarter.kill(this._id);
88
}
89
}
90
91
export class NativeLocalProcessExtensionHost extends Disposable implements IExtensionHost {
92
93
public pid: number | null = null;
94
public readonly remoteAuthority = null;
95
public extensions: ExtensionHostExtensions | null = null;
96
97
private readonly _onExit: Emitter<[number, string]> = this._register(new Emitter<[number, string]>());
98
public readonly onExit: Event<[number, string]> = this._onExit.event;
99
100
private readonly _onDidSetInspectPort = this._register(new Emitter<void>());
101
102
103
private readonly _isExtensionDevHost: boolean;
104
private readonly _isExtensionDevDebug: boolean;
105
private readonly _isExtensionDevDebugBrk: boolean;
106
private readonly _isExtensionDevTestFromCli: boolean;
107
108
// State
109
private _terminating: boolean;
110
111
// Resources, in order they get acquired/created when .start() is called:
112
private _inspectListener: IExtensionInspectInfo | null;
113
private _extensionHostProcess: ExtensionHostProcess | null;
114
private _messageProtocol: Promise<IMessagePassingProtocol> | null;
115
116
constructor(
117
public readonly runningLocation: LocalProcessRunningLocation,
118
public readonly startup: ExtensionHostStartup.EagerAutoStart | ExtensionHostStartup.EagerManualStart,
119
private readonly _initDataProvider: ILocalProcessExtensionHostDataProvider,
120
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
121
@INotificationService private readonly _notificationService: INotificationService,
122
@INativeHostService private readonly _nativeHostService: INativeHostService,
123
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
124
@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService,
125
@IUserDataProfilesService private readonly _userDataProfilesService: IUserDataProfilesService,
126
@ITelemetryService private readonly _telemetryService: ITelemetryService,
127
@ILogService private readonly _logService: ILogService,
128
@ILoggerService private readonly _loggerService: ILoggerService,
129
@ILabelService private readonly _labelService: ILabelService,
130
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,
131
@IHostService private readonly _hostService: IHostService,
132
@IProductService private readonly _productService: IProductService,
133
@IShellEnvironmentService private readonly _shellEnvironmentService: IShellEnvironmentService,
134
@IExtensionHostStarter private readonly _extensionHostStarter: IExtensionHostStarter,
135
@IDefaultLogLevelsService private readonly _defaultLogLevelsService: IDefaultLogLevelsService,
136
) {
137
super();
138
const devOpts = parseExtensionDevOptions(this._environmentService);
139
this._isExtensionDevHost = devOpts.isExtensionDevHost;
140
this._isExtensionDevDebug = devOpts.isExtensionDevDebug;
141
this._isExtensionDevDebugBrk = devOpts.isExtensionDevDebugBrk;
142
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
143
144
this._terminating = false;
145
146
this._inspectListener = null;
147
this._extensionHostProcess = null;
148
this._messageProtocol = null;
149
150
this._register(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e)));
151
this._register(this._extensionHostDebugService.onClose(event => {
152
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) {
153
this._nativeHostService.closeWindow();
154
}
155
}));
156
this._register(this._extensionHostDebugService.onReload(event => {
157
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) {
158
this._hostService.reload();
159
}
160
}));
161
}
162
163
public override dispose(): void {
164
if (this._terminating) {
165
return;
166
}
167
this._terminating = true;
168
super.dispose();
169
this._messageProtocol = null;
170
}
171
172
public start(): Promise<IMessagePassingProtocol> {
173
if (this._terminating) {
174
// .terminate() was called
175
throw new CancellationError();
176
}
177
178
if (!this._messageProtocol) {
179
this._messageProtocol = this._start();
180
}
181
182
return this._messageProtocol;
183
}
184
185
private async _start(): Promise<IMessagePassingProtocol> {
186
const [extensionHostCreationResult, portNumber, processEnv] = await Promise.all([
187
this._extensionHostStarter.createExtensionHost(),
188
this._tryFindDebugPort(),
189
this._shellEnvironmentService.getShellEnv(),
190
]);
191
192
this._extensionHostProcess = new ExtensionHostProcess(extensionHostCreationResult.id, this._extensionHostStarter);
193
194
const env = objects.mixin(processEnv, {
195
VSCODE_ESM_ENTRYPOINT: 'vs/workbench/api/node/extensionHostProcess',
196
VSCODE_HANDLES_UNCAUGHT_ERRORS: true
197
});
198
199
if (this._environmentService.debugExtensionHost.env) {
200
objects.mixin(env, this._environmentService.debugExtensionHost.env);
201
}
202
203
removeDangerousEnvVariables(env);
204
205
if (this._isExtensionDevHost) {
206
// Unset `VSCODE_CODE_CACHE_PATH` when developing extensions because it might
207
// be that dependencies, that otherwise would be cached, get modified.
208
delete env['VSCODE_CODE_CACHE_PATH'];
209
}
210
211
const opts: IExtensionHostProcessOptions = {
212
responseWindowId: this._nativeHostService.windowId,
213
responseChannel: 'vscode:startExtensionHostMessagePortResult',
214
responseNonce: generateUuid(),
215
env,
216
// We only detach the extension host on windows. Linux and Mac orphan by default
217
// and detach under Linux and Mac create another process group.
218
// We detach because we have noticed that when the renderer exits, its child processes
219
// (i.e. extension host) are taken down in a brutal fashion by the OS
220
detached: !!platform.isWindows,
221
execArgv: undefined as string[] | undefined,
222
silent: true
223
};
224
225
const inspectHost = '127.0.0.1';
226
if (portNumber !== 0) {
227
opts.execArgv = [
228
'--nolazy',
229
(this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + `${inspectHost}:${portNumber}`
230
];
231
} else {
232
opts.execArgv = ['--inspect-port=0'];
233
}
234
235
if (this._environmentService.extensionTestsLocationURI) {
236
opts.execArgv.unshift('--expose-gc');
237
}
238
239
if (this._environmentService.args['prof-v8-extensions']) {
240
opts.execArgv.unshift('--prof');
241
}
242
243
// Refs https://github.com/microsoft/vscode/issues/189805
244
//
245
// Enable experimental network inspection
246
// inspector agent is always setup hence add this flag
247
// unconditionally.
248
opts.execArgv.unshift('--dns-result-order=ipv4first', '--experimental-network-inspection');
249
250
// Catch all output coming from the extension host process
251
type Output = { data: string; format: string[] };
252
const onStdout = this._handleProcessOutputStream(this._extensionHostProcess.onStdout);
253
const onStderr = this._handleProcessOutputStream(this._extensionHostProcess.onStderr);
254
const onOutput = Event.any(
255
Event.map(onStdout.event, o => ({ data: `%c${o}`, format: [''] })),
256
Event.map(onStderr.event, o => ({ data: `%c${o}`, format: ['color: red'] }))
257
);
258
259
// Debounce all output, so we can render it in the Chrome console as a group
260
const onDebouncedOutput = Event.debounce<Output>(onOutput, (r, o) => {
261
return r
262
? { data: r.data + o.data, format: [...r.format, ...o.format] }
263
: { data: o.data, format: o.format };
264
}, 100);
265
266
// Print out extension host output
267
this._register(onDebouncedOutput(output => {
268
const inspectorUrlMatch = output.data && output.data.match(/ws:\/\/([^\s]+):(\d+)\/([^\s]+)/);
269
if (inspectorUrlMatch) {
270
const [, host, port, auth] = inspectorUrlMatch;
271
const devtoolsUrl = `devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=${host}:${port}/${auth}`;
272
if (!this._environmentService.isBuilt && !this._isExtensionDevTestFromCli) {
273
console.debug(`%c[Extension Host] %cdebugger inspector at ${devtoolsUrl}`, 'color: blue', 'color:');
274
}
275
if (!this._inspectListener || !this._inspectListener.devtoolsUrl) {
276
this._inspectListener = { host, port: Number(port), devtoolsUrl };
277
this._onDidSetInspectPort.fire();
278
}
279
} else {
280
if (!this._isExtensionDevTestFromCli) {
281
console.group('Extension Host');
282
console.log(output.data, ...output.format);
283
console.groupEnd();
284
}
285
}
286
}));
287
288
// Lifecycle
289
290
this._register(this._extensionHostProcess.onExit(({ code, signal }) => this._onExtHostProcessExit(code, signal)));
291
292
// Notify debugger that we are ready to attach to the process if we run a development extension
293
if (portNumber) {
294
if (this._isExtensionDevHost && this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) {
295
this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, portNumber);
296
}
297
this._inspectListener = { port: portNumber, host: inspectHost };
298
this._onDidSetInspectPort.fire();
299
}
300
301
// Help in case we fail to start it
302
let startupTimeoutHandle: Timeout | undefined;
303
if (!this._environmentService.isBuilt && !this._environmentService.remoteAuthority || this._isExtensionDevHost) {
304
startupTimeoutHandle = setTimeout(() => {
305
this._logService.error(`[LocalProcessExtensionHost]: Extension host did not start in 10 seconds (debugBrk: ${this._isExtensionDevDebugBrk})`);
306
307
const msg = this._isExtensionDevDebugBrk
308
? nls.localize('extensionHost.startupFailDebug', "Extension host did not start in 10 seconds, it might be stopped on the first line and needs a debugger to continue.")
309
: nls.localize('extensionHost.startupFail', "Extension host did not start in 10 seconds, that might be a problem.");
310
311
this._notificationService.prompt(Severity.Warning, msg,
312
[{
313
label: nls.localize('reloadWindow', "Reload Window"),
314
run: () => this._hostService.reload()
315
}],
316
{
317
sticky: true,
318
priority: NotificationPriority.URGENT
319
}
320
);
321
}, 10000);
322
}
323
324
// Initialize extension host process with hand shakes
325
const protocol = await this._establishProtocol(this._extensionHostProcess, opts);
326
await this._performHandshake(protocol);
327
clearTimeout(startupTimeoutHandle);
328
return protocol;
329
}
330
331
/**
332
* Find a free port if extension host debugging is enabled.
333
*/
334
private async _tryFindDebugPort(): Promise<number> {
335
336
if (typeof this._environmentService.debugExtensionHost.port !== 'number') {
337
return 0;
338
}
339
340
const expected = this._environmentService.debugExtensionHost.port;
341
const port = await this._nativeHostService.findFreePort(expected, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */, 2048 /* skip 2048 ports between attempts */);
342
343
if (!this._isExtensionDevTestFromCli) {
344
if (!port) {
345
console.warn('%c[Extension Host] %cCould not find a free port for debugging', 'color: blue', 'color:');
346
} else {
347
if (port !== expected) {
348
console.warn(`%c[Extension Host] %cProvided debugging port ${expected} is not free, using ${port} instead.`, 'color: blue', 'color:');
349
}
350
if (this._isExtensionDevDebugBrk) {
351
console.warn(`%c[Extension Host] %cSTOPPED on first line for debugging on port ${port}`, 'color: blue', 'color:');
352
} else {
353
console.debug(`%c[Extension Host] %cdebugger listening on port ${port}`, 'color: blue', 'color:');
354
}
355
}
356
}
357
358
return port || 0;
359
}
360
361
private _establishProtocol(extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise<IMessagePassingProtocol> {
362
363
writeExtHostConnection(new MessagePortExtHostConnection(), opts.env);
364
365
// Get ready to acquire the message port from the shared process worker
366
const portPromise = acquirePort(undefined /* we trigger the request via service call! */, opts.responseChannel, opts.responseNonce);
367
368
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
369
370
const handle = setTimeout(() => {
371
reject('The local extension host took longer than 60s to connect.');
372
}, 60 * 1000);
373
374
portPromise.then((port) => {
375
this._register(toDisposable(() => {
376
// Close the message port when the extension host is disposed
377
port.close();
378
port.onmessage = null;
379
}));
380
clearTimeout(handle);
381
382
const onMessage = new BufferedEmitter<VSBuffer>();
383
port.onmessage = ((e) => {
384
if (e.data) {
385
onMessage.fire(VSBuffer.wrap(e.data));
386
}
387
});
388
port.start();
389
390
resolve({
391
onMessage: onMessage.event,
392
send: message => port.postMessage(message.buffer),
393
});
394
});
395
396
// Now that the message port listener is installed, start the ext host process
397
const sw = StopWatch.create(false);
398
extensionHostProcess.start(opts).then(({ pid }) => {
399
if (pid) {
400
this.pid = pid;
401
}
402
this._logService.info(`Started local extension host with pid ${pid}.`);
403
const duration = sw.elapsed();
404
if (platform.isCI) {
405
this._logService.info(`IExtensionHostStarter.start() took ${duration} ms.`);
406
}
407
}, (err) => {
408
// Starting the ext host process resulted in an error
409
reject(err);
410
});
411
});
412
}
413
414
private _performHandshake(protocol: IMessagePassingProtocol): Promise<void> {
415
// 1) wait for the incoming `ready` event and send the initialization data.
416
// 2) wait for the incoming `initialized` event.
417
return new Promise<void>((resolve, reject) => {
418
419
let timeoutHandle: Timeout;
420
const installTimeoutCheck = () => {
421
timeoutHandle = setTimeout(() => {
422
reject('The local extension host took longer than 60s to send its ready message.');
423
}, 60 * 1000);
424
};
425
const uninstallTimeoutCheck = () => {
426
clearTimeout(timeoutHandle);
427
};
428
429
// Wait 60s for the ready message
430
installTimeoutCheck();
431
432
const disposable = protocol.onMessage(msg => {
433
434
if (isMessageOfType(msg, MessageType.Ready)) {
435
436
// 1) Extension Host is ready to receive messages, initialize it
437
uninstallTimeoutCheck();
438
439
this._createExtHostInitData().then(data => {
440
441
// Wait 60s for the initialized message
442
installTimeoutCheck();
443
444
protocol.send(VSBuffer.fromString(JSON.stringify(data)));
445
});
446
return;
447
}
448
449
if (isMessageOfType(msg, MessageType.Initialized)) {
450
451
// 2) Extension Host is initialized
452
uninstallTimeoutCheck();
453
454
// stop listening for messages here
455
disposable.dispose();
456
457
// release this promise
458
resolve();
459
return;
460
}
461
462
console.error(`received unexpected message during handshake phase from the extension host: `, msg);
463
});
464
465
});
466
}
467
468
private async _createExtHostInitData(): Promise<IExtensionHostInitData> {
469
const initData = await this._initDataProvider.getInitData();
470
this.extensions = initData.extensions;
471
const workspace = this._contextService.getWorkspace();
472
return {
473
commit: this._productService.commit,
474
version: this._productService.version,
475
quality: this._productService.quality,
476
date: this._productService.date,
477
parentPid: 0,
478
environment: {
479
isExtensionDevelopmentDebug: this._isExtensionDevDebug,
480
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined,
481
appName: this._productService.nameLong,
482
appHost: this._productService.embedderIdentifier || 'desktop',
483
appUriScheme: this._productService.urlProtocol,
484
isExtensionTelemetryLoggingOnly: isLoggingOnly(this._productService, this._environmentService),
485
isPortable: this._environmentService.isPortable,
486
appLanguage: platform.language,
487
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
488
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
489
globalStorageHome: this._userDataProfilesService.defaultProfile.globalStorageHome,
490
workspaceStorageHome: this._environmentService.workspaceStorageHome,
491
extensionLogLevel: this._defaultLogLevelsService.defaultLogLevels.extensions
492
},
493
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : {
494
configuration: workspace.configuration ?? undefined,
495
id: workspace.id,
496
name: this._labelService.getWorkspaceLabel(workspace),
497
isUntitled: workspace.configuration ? isUntitledWorkspace(workspace.configuration, this._environmentService) : false,
498
transient: workspace.transient,
499
isAgentSessionsWorkspace: workspace.isAgentSessionsWorkspace
500
},
501
remote: {
502
authority: this._environmentService.remoteAuthority,
503
connectionData: null,
504
isRemote: false
505
},
506
consoleForward: {
507
includeStack: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || this._productService.quality !== 'stable' || this._environmentService.verbose),
508
logNative: !this._isExtensionDevTestFromCli && this._isExtensionDevHost
509
},
510
extensions: this.extensions.toSnapshot(),
511
telemetryInfo: {
512
sessionId: this._telemetryService.sessionId,
513
machineId: this._telemetryService.machineId,
514
sqmId: this._telemetryService.sqmId,
515
devDeviceId: this._telemetryService.devDeviceId ?? this._telemetryService.machineId,
516
firstSessionDate: this._telemetryService.firstSessionDate,
517
msftInternal: this._telemetryService.msftInternal
518
},
519
remoteExtensionTips: this._productService.remoteExtensionTips,
520
virtualWorkspaceExtensionTips: this._productService.virtualWorkspaceExtensionTips,
521
logLevel: this._logService.getLevel(),
522
loggers: [...this._loggerService.getRegisteredLoggers()],
523
logsLocation: this._environmentService.extHostLogsPath,
524
autoStart: (this.startup === ExtensionHostStartup.EagerAutoStart),
525
uiKind: UIKind.Desktop,
526
handle: this._environmentService.window.handle ? encodeBase64(this._environmentService.window.handle) : undefined
527
};
528
}
529
530
private _onExtHostProcessExit(code: number, signal: string): void {
531
if (this._terminating) {
532
// Expected termination path (we asked the process to terminate)
533
return;
534
}
535
536
this._onExit.fire([code, signal]);
537
}
538
539
private _handleProcessOutputStream(stream: Event<string>) {
540
let last = '';
541
let isOmitting = false;
542
const event = new Emitter<string>();
543
stream((chunk) => {
544
// not a fancy approach, but this is the same approach used by the split2
545
// module which is well-optimized (https://github.com/mcollina/split2)
546
last += chunk;
547
const lines = last.split(/\r?\n/g);
548
last = lines.pop()!;
549
550
// protected against an extension spamming and leaking memory if no new line is written.
551
if (last.length > 10_000) {
552
lines.push(last);
553
last = '';
554
}
555
556
for (const line of lines) {
557
if (isOmitting) {
558
if (line === NativeLogMarkers.End) {
559
isOmitting = false;
560
}
561
} else if (line === NativeLogMarkers.Start) {
562
isOmitting = true;
563
} else if (line.length) {
564
event.fire(line + '\n');
565
}
566
}
567
}, undefined, this._store);
568
569
return event;
570
}
571
572
public async enableInspectPort(): Promise<boolean> {
573
if (!!this._inspectListener) {
574
return true;
575
}
576
577
if (!this._extensionHostProcess) {
578
return false;
579
}
580
581
const result = await this._extensionHostProcess.enableInspectPort();
582
if (!result) {
583
return false;
584
}
585
586
await Promise.race([Event.toPromise(this._onDidSetInspectPort.event), timeout(1000)]);
587
return !!this._inspectListener;
588
}
589
590
public getInspectPort(): IExtensionInspectInfo | undefined {
591
return this._inspectListener ?? undefined;
592
}
593
594
private _onWillShutdown(event: WillShutdownEvent): void {
595
// If the extension development host was started without debugger attached we need
596
// to communicate this back to the main side to terminate the debug session
597
if (this._isExtensionDevHost && !this._isExtensionDevTestFromCli && !this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) {
598
this._extensionHostDebugService.terminateSession(this._environmentService.debugExtensionHost.debugId);
599
event.join(timeout(100 /* wait a bit for IPC to get delivered */), { id: 'join.extensionDevelopment', label: nls.localize('join.extensionDevelopment', "Terminating extension debug session") });
600
}
601
}
602
}
603
604