extensionHostMain.ts 7.2 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

A
Alex Dima 已提交
6
import { timeout } from 'vs/base/common/async';
7
import * as errors from 'vs/base/common/errors';
8
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
A
Alex Dima 已提交
9
import { Counter } from 'vs/base/common/numbers';
10
import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri';
A
Alex Dima 已提交
11
import { IURITransformer } from 'vs/base/common/uriIpc';
A
Alex Dima 已提交
12
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
13
import { IEnvironment, IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/node/extHost.protocol';
A
Alex Dima 已提交
14 15
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
16
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
A
Alex Dima 已提交
17 18 19
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
E
Erich Gamma 已提交
20

21 22
// we don't (yet) throw when extensions parse
// uris that have no scheme
23
setUriThrowOnMissingScheme(false);
24

A
Alex Dima 已提交
25
const nativeExit = process.exit.bind(process);
B
Benjamin Pasero 已提交
26
function patchProcess(allowExit: boolean) {
27
	process.exit = function (code?: number) {
28 29 30 31 32 33
		if (allowExit) {
			exit(code);
		} else {
			const err = new Error('An extension called process.exit() and this was prevented.');
			console.warn(err.stack);
		}
34
	} as (code?: number) => never;
B
Benjamin Pasero 已提交
35 36 37 38 39

	process.crash = function () {
		const err = new Error('An extension called process.crash() and this was prevented.');
		console.warn(err.stack);
	};
40
}
B
Benjamin Pasero 已提交
41

42
export function exit(code?: number) {
43
	nativeExit(code);
44 45
}

A
Alex Dima 已提交
46
export class ExtensionHostMain {
E
Erich Gamma 已提交
47

48

A
Alex Dima 已提交
49 50 51 52
	private _isTerminating: boolean;
	private readonly _environment: IEnvironment;
	private readonly _extensionService: ExtHostExtensionService;
	private readonly _extHostLogService: ExtHostLogService;
53
	private disposables: IDisposable[] = [];
54

55 56
	private _searchRequestIdProvider: Counter;

A
Alex Dima 已提交
57
	constructor(protocol: IMessagePassingProtocol, initData: IInitData) {
A
Alex Dima 已提交
58
		this._isTerminating = false;
59
		const uriTransformer: IURITransformer | null = null;
A
Alex Dima 已提交
60
		const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
61 62 63

		// ensure URIs are transformed and revived
		initData = this.transform(initData, rpcProtocol);
B
Benjamin Pasero 已提交
64
		this._environment = initData.environment;
65

66
		const allowExit = !!this._environment.extensionTestsLocationURI; // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708)
B
Benjamin Pasero 已提交
67
		patchProcess(allowExit);
68

69 70
		this._patchPatchedConsole(rpcProtocol.getProxy(MainContext.MainThreadConsole));

71
		// services
72
		this._extHostLogService = new ExtHostLogService(initData.logLevel, initData.logsLocation.fsPath);
73
		this.disposables.push(this._extHostLogService);
74 75

		this._searchRequestIdProvider = new Counter();
76
		const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, this._extHostLogService, this._searchRequestIdProvider, initData.workspace);
J
Joao Moreno 已提交
77

78 79
		this._extHostLogService.info('extension host started');
		this._extHostLogService.trace('initData', initData);
J
Joao Moreno 已提交
80

81 82
		const extHostConfiguraiton = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace);
		this._extensionService = new ExtHostExtensionService(nativeExit, initData, rpcProtocol, extHostWorkspace, extHostConfiguraiton, this._extHostLogService);
83

84
		// error forwarding and stack trace scanning
B
Benjamin Pasero 已提交
85
		Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
86
		const extensionErrors = new WeakMap<Error, IExtensionDescription>();
87 88 89
		this._extensionService.getExtensionPathIndex().then(map => {
			(<any>Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => {
				let stackTraceMessage = '';
90
				let extension: IExtensionDescription | undefined;
J
Johannes Rieken 已提交
91
				let fileName: string;
92 93
				for (const call of stackTrace) {
					stackTraceMessage += `\n\tat ${call.toString()}`;
J
Johannes Rieken 已提交
94 95 96 97 98
					fileName = call.getFileName();
					if (!extension && fileName) {
						extension = map.findSubstr(fileName);
					}

99
				}
100 101
				extensionErrors.set(error, extension);
				return `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
102 103
			};
		});
B
Benjamin Pasero 已提交
104

105 106
		const mainThreadExtensions = rpcProtocol.getProxy(MainContext.MainThreadExtensionService);
		const mainThreadErrors = rpcProtocol.getProxy(MainContext.MainThreadErrors);
107 108
		errors.setUnexpectedErrorHandler(err => {
			const data = errors.transformErrorForSerialization(err);
109
			const extension = extensionErrors.get(err);
110
			if (extension) {
111
				mainThreadExtensions.$onExtensionRuntimeError(extension.identifier, data);
112 113 114
			} else {
				mainThreadErrors.$onUnexpectedError(data);
			}
115
		});
E
Erich Gamma 已提交
116 117
	}

118 119
	private _patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
		// The console is already patched to use `process.send()`
120
		const nativeProcessSend = process.send!;
121 122 123 124 125 126 127 128 129
		process.send = (...args: any[]) => {
			if (args.length === 0 || !args[0] || args[0].type !== '__$console') {
				return nativeProcessSend.apply(process, args);
			}

			mainThreadConsole.$logExtensionHostMessage(args[0]);
		};
	}

B
Benjamin Pasero 已提交
130
	terminate(): void {
131 132 133 134 135 136
		if (this._isTerminating) {
			// we are already shutting down...
			return;
		}
		this._isTerminating = true;

137 138
		this.disposables = dispose(this.disposables);

139 140 141 142
		errors.setUnexpectedErrorHandler((err) => {
			// TODO: write to log once we have one
		});

A
Alex Dima 已提交
143
		const extensionsDeactivated = this._extensionService.deactivateAll();
144

A
Alex Dima 已提交
145
		// Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds
146
		setTimeout(() => {
147
			Promise.race([timeout(4000), extensionsDeactivated]).then(() => exit(), () => exit());
148 149 150
		}, 1000);
	}

151
	private transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData {
A
Alex Dima 已提交
152 153 154 155
		initData.extensions.forEach((ext) => (<any>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)));
		initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
		initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome));
		initData.environment.extensionDevelopmentLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionDevelopmentLocationURI));
156
		initData.environment.extensionTestsLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionTestsLocationURI));
A
Alex Dima 已提交
157
		initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome));
D
Daniel Imms 已提交
158
		initData.environment.userHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.userHome));
A
Alex Dima 已提交
159
		initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation));
160
		initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace);
161
		return initData;
E
Erich Gamma 已提交
162
	}
163
}