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