extensionHost.ts 20.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import * as nls from 'vs/nls';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import * as objects from 'vs/base/common/objects';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
13
import { isWindows, isLinux } from 'vs/base/common/platform';
14 15
import { findFreePort } from 'vs/base/node/ports';
import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
16
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
17
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
18
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
19 20
import { ChildProcess, fork } from 'child_process';
import { ipcRenderer as ipc } from 'electron';
21
import product from 'vs/platform/node/product';
22 23
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
24
import { generateRandomPipeName, Protocol } from 'vs/base/parts/ipc/node/ipc.net';
A
Alex Dima 已提交
25
import { createServer, Server, Socket } from 'net';
J
Joao Moreno 已提交
26
import Event, { Emitter, debounceEvent, mapEvent, anyEvent, fromNodeEventEmitter } from 'vs/base/common/event';
S
Sandeep Somavarapu 已提交
27
import { IInitData, IWorkspaceData, IConfigurationInitData } from 'vs/workbench/api/node/extHost.protocol';
B
Benjamin Pasero 已提交
28
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
29
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
30
import { ICrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService';
31
import { IBroadcastService, IBroadcast } from 'vs/platform/broadcast/electron-browser/broadcastService';
B
Benjamin Pasero 已提交
32
import { isEqual } from 'vs/base/common/paths';
33
import { EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost';
B
Benjamin Pasero 已提交
34
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
35
import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console';
S
Sandeep Somavarapu 已提交
36
import { getScopes } from 'vs/platform/configuration/common/configurationRegistry';
S
Sandeep Somavarapu 已提交
37
import { ILogService } from 'vs/platform/log/common/log';
38 39
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
40 41

export class ExtensionHostProcessWorker {
42

43 44 45 46 47
	private _onCrashed: Emitter<[number, string]> = new Emitter<[number, string]>();
	public readonly onCrashed: Event<[number, string]> = this._onCrashed.event;

	private readonly _toDispose: IDisposable[];

A
Alex Dima 已提交
48 49 50
	private readonly _isExtensionDevHost: boolean;
	private readonly _isExtensionDevDebug: boolean;
	private readonly _isExtensionDevDebugBrk: boolean;
A
Alex Dima 已提交
51 52 53 54 55
	private readonly _isExtensionDevTestFromCli: boolean;

	// State
	private _lastExtensionHostError: string;
	private _terminating: boolean;
56

57
	// Resources, in order they get acquired/created when .start() is called:
A
Alex Dima 已提交
58
	private _namedPipeServer: Server;
59
	private _inspectPort: number;
A
Alex Dima 已提交
60 61
	private _extensionHostProcess: ChildProcess;
	private _extensionHostConnection: Socket;
62
	private _messageProtocol: TPromise<IMessagePassingProtocol>;
63 64

	constructor(
65
		/* intentionally not injected */private readonly _extensionService: IExtensionService,
A
Alex Dima 已提交
66
		@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
67
		@INotificationService private readonly _notificationService: INotificationService,
A
Alex Dima 已提交
68 69 70 71 72 73 74
		@IWindowsService private readonly _windowsService: IWindowsService,
		@IWindowService private readonly _windowService: IWindowService,
		@IBroadcastService private readonly _broadcastService: IBroadcastService,
		@ILifecycleService private readonly _lifecycleService: ILifecycleService,
		@IEnvironmentService private readonly _environmentService: IEnvironmentService,
		@IWorkspaceConfigurationService private readonly _configurationService: IWorkspaceConfigurationService,
		@ITelemetryService private readonly _telemetryService: ITelemetryService,
S
Sandeep Somavarapu 已提交
75
		@ICrashReporterService private readonly _crashReporterService: ICrashReporterService,
76
		@IChoiceService private readonly _choiceService: IChoiceService,
S
Sandeep Somavarapu 已提交
77
		@ILogService private readonly _logService: ILogService
78 79
	) {
		// handle extension host lifecycle a bit special when we know we are developing an extension that runs inside
A
Alex Dima 已提交
80 81 82 83
		this._isExtensionDevHost = this._environmentService.isExtensionDevelopment;
		this._isExtensionDevDebug = (typeof this._environmentService.debugExtensionHost.port === 'number');
		this._isExtensionDevDebugBrk = !!this._environmentService.debugExtensionHost.break;
		this._isExtensionDevTestFromCli = this._isExtensionDevHost && !!this._environmentService.extensionTestsPath && !this._environmentService.debugExtensionHost.break;
84

A
Alex Dima 已提交
85 86 87 88 89
		this._lastExtensionHostError = null;
		this._terminating = false;

		this._namedPipeServer = null;
		this._extensionHostProcess = null;
90 91
		this._extensionHostConnection = null;
		this._messageProtocol = null;
A
Alex Dima 已提交
92

93 94 95 96 97 98 99 100 101 102 103 104 105 106
		this._toDispose = [];
		this._toDispose.push(this._onCrashed);
		this._toDispose.push(this._lifecycleService.onWillShutdown((e) => this._onWillShutdown(e)));
		this._toDispose.push(this._lifecycleService.onShutdown(reason => this.terminate()));
		this._toDispose.push(this._broadcastService.onBroadcast(b => this._onBroadcast(b)));

		const globalExitListener = () => this.terminate();
		process.once('exit', globalExitListener);
		this._toDispose.push({
			dispose: () => {
				process.removeListener('exit', globalExitListener);
			}
		});
	}
A
Alex Dima 已提交
107

108 109
	public dispose(): void {
		this.terminate();
110 111
	}

A
Alex Dima 已提交
112
	private _onBroadcast(broadcast: IBroadcast): void {
113 114

		// Close Ext Host Window Request
A
Alex Dima 已提交
115
		if (broadcast.channel === EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL && this._isExtensionDevHost) {
116
			const extensionPaths = broadcast.payload as string[];
A
Alex Dima 已提交
117 118
			if (Array.isArray(extensionPaths) && extensionPaths.some(path => isEqual(this._environmentService.extensionDevelopmentPath, path, !isLinux))) {
				this._windowService.closeWindow();
119 120
			}
		}
121

A
Alex Dima 已提交
122
		if (broadcast.channel === EXTENSION_RELOAD_BROADCAST_CHANNEL && this._isExtensionDevHost) {
123
			const extensionPaths = broadcast.payload as string[];
A
Alex Dima 已提交
124 125
			if (Array.isArray(extensionPaths) && extensionPaths.some(path => isEqual(this._environmentService.extensionDevelopmentPath, path, !isLinux))) {
				this._windowService.reloadWindow();
126 127
			}
		}
128 129
	}

130
	public start(): TPromise<IMessagePassingProtocol> {
131 132 133 134
		if (this._terminating) {
			// .terminate() was called
			return null;
		}
J
Joao Moreno 已提交
135

136
		if (!this._messageProtocol) {
J
Johannes Rieken 已提交
137
			this._messageProtocol = TPromise.join([this._tryListenOnPipe(), this._tryFindDebugPort()]).then(data => {
138
				const pipeName = data[0];
J
Johannes Rieken 已提交
139
				const portData = data[1];
140 141

				const opts = {
J
Johannes Rieken 已提交
142
					env: objects.mixin(objects.deepClone(process.env), {
143 144 145 146 147
						AMD_ENTRYPOINT: 'vs/workbench/node/extensionHostProcess',
						PIPE_LOGGING: 'true',
						VERBOSE_LOGGING: true,
						VSCODE_WINDOW_ID: String(this._windowService.getCurrentWindowId()),
						VSCODE_IPC_HOOK_EXTHOST: pipeName,
J
Johannes Rieken 已提交
148
						VSCODE_HANDLES_UNCAUGHT_ERRORS: true,
149
						VSCODE_LOG_STACK: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || product.quality !== 'stable' || this._environmentService.verbose)
150 151 152 153 154 155
					}),
					// We only detach the extension host on windows. Linux and Mac orphan by default
					// and detach under Linux and Mac create another process group.
					// We detach because we have noticed that when the renderer exits, its child processes
					// (i.e. extension host) are taken down in a brutal fashion by the OS
					detached: !!isWindows,
J
Johannes Rieken 已提交
156
					execArgv: <string[]>undefined,
157 158 159
					silent: true
				};

J
Johannes Rieken 已提交
160 161 162 163 164 165 166 167 168 169 170 171
				if (portData.actual) {
					opts.execArgv = [
						'--nolazy',
						(this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + portData.actual
					];
					if (!portData.expected) {
						// No one asked for 'inspect' or 'inspect-brk', only us. We add another
						// option such that the extension host can manipulate the execArgv array
						opts.env.VSCODE_PREVENT_FOREIGN_INSPECT = true;
					}
				}

172 173 174
				const crashReporterOptions = this._crashReporterService.getChildProcessStartOptions('extensionHost');
				if (crashReporterOptions) {
					opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterOptions);
175 176
				}

177 178 179 180 181 182 183
				// Run Extension Host as fork of current process
				this._extensionHostProcess = fork(URI.parse(require.toUrl('bootstrap')).fsPath, ['--type=extensionHost'], opts);

				// Catch all output coming from the extension host process
				type Output = { data: string, format: string[] };
				this._extensionHostProcess.stdout.setEncoding('utf8');
				this._extensionHostProcess.stderr.setEncoding('utf8');
J
Joao Moreno 已提交
184 185
				const onStdout = fromNodeEventEmitter<string>(this._extensionHostProcess.stdout, 'data');
				const onStderr = fromNodeEventEmitter<string>(this._extensionHostProcess.stderr, 'data');
186
				const onOutput = anyEvent(
187 188 189 190 191 192 193 194 195 196 197 198 199
					mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })),
					mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
				);

				// Debounce all output, so we can render it in the Chrome console as a group
				const onDebouncedOutput = debounceEvent<Output>(onOutput, (r, o) => {
					return r
						? { data: r.data + o.data, format: [...r.format, ...o.format] }
						: { data: o.data, format: o.format };
				}, 100);

				// Print out extension host output
				onDebouncedOutput(data => {
200 201 202 203 204 205 206 207
					const inspectorUrlIndex = !this._environmentService.isBuilt && data.data && data.data.indexOf('chrome-devtools://');
					if (inspectorUrlIndex >= 0) {
						console.log(`%c[Extension Host] %cdebugger inspector at ${data.data.substr(inspectorUrlIndex)}`, 'color: blue', 'color: black');
					} else {
						console.group('Extension Host');
						console.log(data.data, ...data.format);
						console.groupEnd();
					}
208
				});
209

210 211
				// Support logging from extension host
				this._extensionHostProcess.on('message', msg => {
212 213
					if (msg && (<IRemoteConsoleLog>msg).type === '__$console') {
						this._logExtensionHostMessage(<IRemoteConsoleLog>msg);
214
					}
B
Benjamin Pasero 已提交
215
				});
216

217 218
				// Lifecycle
				this._extensionHostProcess.on('error', (err) => this._onExtHostProcessError(err));
219
				this._extensionHostProcess.on('exit', (code: number, signal: string) => this._onExtHostProcessExit(code, signal));
220

221
				// Notify debugger that we are ready to attach to the process if we run a development extension
J
Johannes Rieken 已提交
222
				if (this._isExtensionDevHost && portData.actual) {
223 224 225 226
					this._broadcastService.broadcast({
						channel: EXTENSION_ATTACH_BROADCAST_CHANNEL,
						payload: {
							debugId: this._environmentService.debugExtensionHost.debugId,
J
Johannes Rieken 已提交
227
							port: portData.actual
228 229 230
						}
					});
				}
J
Johannes Rieken 已提交
231
				this._inspectPort = portData.actual;
232 233 234 235 236 237 238 239 240

				// Help in case we fail to start it
				let startupTimeoutHandle: number;
				if (!this._environmentService.isBuilt || this._isExtensionDevHost) {
					startupTimeoutHandle = setTimeout(() => {
						const msg = this._isExtensionDevDebugBrk
							? nls.localize('extensionHostProcess.startupFailDebug', "Extension host did not start in 10 seconds, it might be stopped on the first line and needs a debugger to continue.")
							: nls.localize('extensionHostProcess.startupFail', "Extension host did not start in 10 seconds, that might be a problem.");

241 242 243 244 245
						this._choiceService.choose(Severity.Warning, msg, [nls.localize('reloadWindow', "Reload Window")]).then(choice => {
							if (choice === 0) {
								this._windowService.reloadWindow();
							}
						});
246 247
					}, 10000);
				}
248

249 250 251 252 253
				// Initialize extension host process with hand shakes
				return this._tryExtHostHandshake().then((protocol) => {
					clearTimeout(startupTimeoutHandle);
					return protocol;
				});
254
			});
255 256 257
		}

		return this._messageProtocol;
258
	}
259

A
Alex Dima 已提交
260
	/**
261
	 * Start a server (`this._namedPipeServer`) that listens on a named pipe and return the named pipe name.
A
Alex Dima 已提交
262
	 */
263 264 265
	private _tryListenOnPipe(): TPromise<string> {
		return new TPromise<string>((resolve, reject) => {
			const pipeName = generateRandomPipeName();
A
Alex Dima 已提交
266 267 268

			this._namedPipeServer = createServer();
			this._namedPipeServer.on('error', reject);
269
			this._namedPipeServer.listen(pipeName, () => {
A
Alex Dima 已提交
270
				this._namedPipeServer.removeListener('error', reject);
271
				resolve(pipeName);
272
			});
273
		});
274 275
	}

A
Alex Dima 已提交
276 277 278
	/**
	 * Find a free port if extension host debugging is enabled.
	 */
J
Johannes Rieken 已提交
279 280 281 282 283
	private _tryFindDebugPort(): TPromise<{ expected: number; actual: number }> {
		let expected: number;
		let startPort = 9333;
		if (typeof this._environmentService.debugExtensionHost.port === 'number') {
			startPort = expected = this._environmentService.debugExtensionHost.port;
J
Johannes Rieken 已提交
284 285
		} else {
			return TPromise.as({ expected: undefined, actual: 0 });
286
		}
J
Johannes Rieken 已提交
287 288
		return new TPromise((c, e) => {
			return findFreePort(startPort, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */).then(port => {
289 290 291
				if (!port) {
					console.warn('%c[Extension Host] %cCould not find a free port for debugging', 'color: blue', 'color: black');
				} else {
J
Johannes Rieken 已提交
292 293 294 295 296 297 298 299
					if (expected && port !== expected) {
						console.warn(`%c[Extension Host] %cProvided debugging port ${expected} is not free, using ${port} instead.`, 'color: blue', 'color: black');
					}
					if (this._isExtensionDevDebugBrk) {
						console.warn(`%c[Extension Host] %cSTOPPED on first line for debugging on port ${port}`, 'color: blue', 'color: black');
					} else {
						console.info(`%c[Extension Host] %cdebugger listening on port ${port}`, 'color: blue', 'color: black');
					}
300
				}
J
Johannes Rieken 已提交
301
				return c({ expected, actual: port });
302 303 304 305
			});
		});
	}

306
	private _tryExtHostHandshake(): TPromise<IMessagePassingProtocol> {
307

308
		return new TPromise<IMessagePassingProtocol>((resolve, reject) => {
A
Alex Dima 已提交
309

A
Alex Dima 已提交
310 311
			// Wait for the extension host to connect to our named pipe
			// and wrap the socket in the message passing protocol
312 313 314 315 316 317
			let handle = setTimeout(() => {
				this._namedPipeServer.close();
				this._namedPipeServer = null;
				reject('timeout');
			}, 60 * 1000);

A
Alex Dima 已提交
318
			this._namedPipeServer.on('connection', socket => {
319
				clearTimeout(handle);
320 321
				this._namedPipeServer.close();
				this._namedPipeServer = null;
A
Alex Dima 已提交
322
				this._extensionHostConnection = socket;
323
				resolve(new Protocol(this._extensionHostConnection));
324
			});
325

326
		}).then((protocol) => {
A
Alex Dima 已提交
327 328 329

			// 1) wait for the incoming `ready` event and send the initialization data.
			// 2) wait for the incoming `initialized` event.
330
			return new TPromise<IMessagePassingProtocol>((resolve, reject) => {
A
Alex Dima 已提交
331

332 333 334 335 336
				let handle = setTimeout(() => {
					reject('timeout');
				}, 60 * 1000);

				const disposable = protocol.onMessage(msg => {
337

338
					if (msg === 'ready') {
339
						// 1) Extension Host is ready to receive messages, initialize it
340
						this._createExtHostInitData().then(data => protocol.send(JSON.stringify(data)));
A
Alex Dima 已提交
341 342 343 344
						return;
					}

					if (msg === 'initialized') {
345 346 347
						// 2) Extension Host is initialized

						clearTimeout(handle);
A
Alex Dima 已提交
348 349 350 351 352

						// stop listening for messages here
						disposable.dispose();

						// release this promise
353
						resolve(protocol);
A
Alex Dima 已提交
354
						return;
355
					}
A
Alex Dima 已提交
356 357

					console.error(`received unexpected message during handshake phase from the extension host: `, msg);
358
				});
A
Alex Dima 已提交
359

360
			});
A
Alex Dima 已提交
361

362 363 364
		});
	}

A
Alex Dima 已提交
365
	private _createExtHostInitData(): TPromise<IInitData> {
366
		return TPromise.join<any>([this._telemetryService.getTelemetryInfo(), this._extensionService.getExtensions()]).then(([telemetryInfo, extensionDescriptions]) => {
S
Sandeep Somavarapu 已提交
367
			const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: {} };
A
Alex Dima 已提交
368
			const r: IInitData = {
369 370
				parentPid: process.pid,
				environment: {
A
Alex Dima 已提交
371
					isExtensionDevelopmentDebug: this._isExtensionDevDebug,
J
Johannes Rieken 已提交
372
					appRoot: this._environmentService.appRoot,
A
Alex Dima 已提交
373 374 375 376 377
					appSettingsHome: this._environmentService.appSettingsHome,
					disableExtensions: this._environmentService.disableExtensions,
					userExtensionsHome: this._environmentService.extensionsPath,
					extensionDevelopmentPath: this._environmentService.extensionDevelopmentPath,
					extensionTestsPath: this._environmentService.extensionTestsPath,
378
					// globally disable proposed api when built and not insiders developing extensions
A
Alex Dima 已提交
379 380
					enableProposedApiForAll: !this._environmentService.isBuilt || (!!this._environmentService.extensionDevelopmentPath && product.nameLong.indexOf('Insiders') >= 0),
					enableProposedApiFor: this._environmentService.args['enable-proposed-api'] || []
381
				},
382
				workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : <IWorkspaceData>this._contextService.getWorkspace(),
383
				extensions: extensionDescriptions,
S
Sandeep Somavarapu 已提交
384
				// Send configurations scopes only in development mode.
S
Sandeep Somavarapu 已提交
385
				configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes() } : configurationData,
J
Joao Moreno 已提交
386 387
				telemetryInfo,
				args: this._environmentService.args,
388
				execPath: this._environmentService.execPath,
S
Sandeep Somavarapu 已提交
389 390
				windowId: this._windowService.getCurrentWindowId(),
				logLevel: this._logService.getLevel()
391
			};
392
			return r;
393 394 395
		});
	}

396
	private _logExtensionHostMessage(entry: IRemoteConsoleLog) {
397 398

		// Send to local console unless we run tests from cli
A
Alex Dima 已提交
399
		if (!this._isExtensionDevTestFromCli) {
400
			log(entry, 'Extension Host');
401 402 403
		}

		// Log on main side if running tests from cli
A
Alex Dima 已提交
404
		if (this._isExtensionDevTestFromCli) {
405
			this._windowsService.log(entry.severity, ...parse(entry).args);
406 407 408
		}

		// Broadcast to other windows if we are in development mode
A
Alex Dima 已提交
409 410
		else if (!this._environmentService.isBuilt || this._isExtensionDevHost) {
			this._broadcastService.broadcast({
411
				channel: EXTENSION_LOG_BROADCAST_CHANNEL,
412
				payload: {
413
					logEntry: entry,
A
Alex Dima 已提交
414
					debugId: this._environmentService.debugExtensionHost.debugId
415
				}
B
Benjamin Pasero 已提交
416
			});
417 418 419
		}
	}

A
Alex Dima 已提交
420
	private _onExtHostProcessError(err: any): void {
421
		let errorMessage = toErrorMessage(err);
A
Alex Dima 已提交
422
		if (errorMessage === this._lastExtensionHostError) {
423 424 425
			return; // prevent error spam
		}

A
Alex Dima 已提交
426
		this._lastExtensionHostError = errorMessage;
427

428
		this._notificationService.error(nls.localize('extensionHostProcess.error', "Error from the extension host: {0}", errorMessage));
429 430
	}

A
Alex Dima 已提交
431 432 433
	private _onExtHostProcessExit(code: number, signal: string): void {
		if (this._terminating) {
			// Expected termination path (we asked the process to terminate)
A
Alex Dima 已提交
434 435
			return;
		}
436

A
Alex Dima 已提交
437 438
		// Unexpected termination
		if (!this._isExtensionDevHost) {
439
			this._onCrashed.fire([code, signal]);
A
Alex Dima 已提交
440
		}
441

A
Alex Dima 已提交
442 443 444 445 446 447 448 449
		// Expected development extension termination: When the extension host goes down we also shutdown the window
		else if (!this._isExtensionDevTestFromCli) {
			this._windowService.closeWindow();
		}

		// When CLI testing make sure to exit with proper exit code
		else {
			ipc.send('vscode:exit', code);
450 451 452
		}
	}

453 454 455 456
	public getInspectPort(): number {
		return this._inspectPort;
	}

457
	public terminate(): void {
458 459 460
		if (this._terminating) {
			return;
		}
A
Alex Dima 已提交
461 462
		this._terminating = true;

463 464
		dispose(this._toDispose);

465 466 467 468 469 470
		if (!this._messageProtocol) {
			// .start() was not called
			return;
		}

		this._messageProtocol.then((protocol) => {
471 472 473

			// Send the extension host a request to terminate itself
			// (graceful termination)
474
			protocol.send({
475 476
				type: '__$terminate'
			});
477 478 479 480 481 482 483 484 485 486

			// Give the extension host 60s, after which we will
			// try to kill the process and release any resources
			setTimeout(() => this._cleanResources(), 60 * 1000);

		}, (err) => {

			// Establishing a protocol with the extension host failed, so
			// try to kill the process and release any resources.
			this._cleanResources();
487
		});
488
	}
489

490 491 492 493 494 495 496 497 498 499 500 501 502
	private _cleanResources(): void {
		if (this._namedPipeServer) {
			this._namedPipeServer.close();
			this._namedPipeServer = null;
		}
		if (this._extensionHostConnection) {
			this._extensionHostConnection.end();
			this._extensionHostConnection = null;
		}
		if (this._extensionHostProcess) {
			this._extensionHostProcess.kill();
			this._extensionHostProcess = null;
		}
503 504 505 506 507 508
	}

	private _onWillShutdown(event: ShutdownEvent): void {

		// If the extension development host was started without debugger attached we need
		// to communicate this back to the main side to terminate the debug session
A
Alex Dima 已提交
509 510
		if (this._isExtensionDevHost && !this._isExtensionDevTestFromCli && !this._isExtensionDevDebug) {
			this._broadcastService.broadcast({
511
				channel: EXTENSION_TERMINATE_BROADCAST_CHANNEL,
512
				payload: {
A
Alex Dima 已提交
513
					debugId: this._environmentService.debugExtensionHost.debugId
514
				}
B
Benjamin Pasero 已提交
515
			});
516 517 518 519

			event.veto(TPromise.timeout(100 /* wait a bit for IPC to get delivered */).then(() => false));
		}
	}
520
}