Rearrange files to better separate between workbench/api and workbench/services (#141003)

This commit is contained in:
Alex Dima
2022-01-31 16:14:21 +01:00
parent 87140b9f95
commit 24569c613e
24 changed files with 23 additions and 23 deletions

View File

@@ -20,8 +20,8 @@ import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorksp
import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { checkGlobFileExists } from 'vs/workbench/api/common/shared/workspaceContains';
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
import { checkGlobFileExists } from 'vs/workbench/services/extensions/common/workspaceContains';
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';

View File

@@ -57,7 +57,7 @@ import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output';
import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm';
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { CoverageDetails, ExtensionRunTestsRequest, IFileCoverage, ISerializedTestResults, ITestItem, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, RunTestForControllerRequest, SerializedTestMessage, TestResultState, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { InternalTimelineOptions, Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';

View File

@@ -34,7 +34,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
import { Emitter, Event } from 'vs/base/common/event';
import { IExtensionActivationHost, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains';
import { IExtensionActivationHost, checkActivateWorkspaceContainsExtension } from 'vs/workbench/services/extensions/common/workspaceContains';
import { ExtHostSecretState, IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
import { ExtensionSecrets } from 'vs/workbench/api/common/extHostSecrets';
import { Schemas } from 'vs/base/common/network';

View File

@@ -26,7 +26,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { GlobPattern } from 'vs/workbench/api/common/extHostTypeConverters';
import { Range } from 'vs/workbench/api/common/extHostTypes';
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search';
import * as vscode from 'vscode';
import { ExtHostWorkspaceShape, IRelativePatternDto, IWorkspaceData, MainContext, MainThreadMessageOptions, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol';

View File

@@ -0,0 +1,169 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { timeout } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
import * as performance from 'vs/base/common/performance';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostRpcService, ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IURITransformerService, URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
import { IExtHostExtensionService, IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
export interface IExitFn {
(code?: number): any;
}
export interface IConsolePatchFn {
(mainThreadConsole: MainThreadConsoleShape): any;
}
export class ExtensionHostMain {
private _isTerminating: boolean;
private readonly _hostUtils: IHostUtils;
private readonly _rpcProtocol: RPCProtocol;
private readonly _extensionService: IExtHostExtensionService;
private readonly _logService: ILogService;
private readonly _disposables = new DisposableStore();
constructor(
protocol: IMessagePassingProtocol,
initData: IInitData,
hostUtils: IHostUtils,
uriTransformer: IURITransformer | null,
messagePorts?: ReadonlyMap<string, MessagePort>
) {
this._isTerminating = false;
this._hostUtils = hostUtils;
this._rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
// ensure URIs are transformed and revived
initData = ExtensionHostMain._transform(initData, this._rpcProtocol);
// bootstrap services
const services = new ServiceCollection(...getSingletonServiceDescriptors());
services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData, messagePorts });
services.set(IExtHostRpcService, new ExtHostRpcService(this._rpcProtocol));
services.set(IURITransformerService, new URITransformerService(uriTransformer));
services.set(IHostUtils, hostUtils);
const instaService: IInstantiationService = new InstantiationService(services, true);
// ugly self - inject
this._disposables.add(instaService.invokeFunction(accessor => accessor.get(IExtHostTerminalService)));
this._disposables.add(instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService)));
this._logService = instaService.invokeFunction(accessor => accessor.get(ILogService));
performance.mark(`code/extHost/didCreateServices`);
if (this._hostUtils.pid) {
this._logService.info(`Extension host with pid ${this._hostUtils.pid} started`);
} else {
this._logService.info(`Extension host started`);
}
this._logService.trace('initData', initData);
// ugly self - inject
// must call initialize *after* creating the extension service
// because `initialize` itself creates instances that depend on it
this._extensionService = instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService));
this._extensionService.initialize();
// error forwarding and stack trace scanning
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
const extensionErrors = new WeakMap<Error, IExtensionDescription | undefined>();
this._extensionService.getExtensionPathIndex().then(map => {
(<any>Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => {
let stackTraceMessage = '';
let extension: IExtensionDescription | undefined;
let fileName: string;
for (const call of stackTrace) {
stackTraceMessage += `\n\tat ${call.toString()}`;
fileName = call.getFileName();
if (!extension && fileName) {
extension = map.findSubstr(URI.file(fileName));
}
}
extensionErrors.set(error, extension);
return `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
};
});
const mainThreadExtensions = this._rpcProtocol.getProxy(MainContext.MainThreadExtensionService);
const mainThreadErrors = this._rpcProtocol.getProxy(MainContext.MainThreadErrors);
errors.setUnexpectedErrorHandler(err => {
const data = errors.transformErrorForSerialization(err);
const extension = extensionErrors.get(err);
if (extension) {
mainThreadExtensions.$onExtensionRuntimeError(extension.identifier, data);
} else {
mainThreadErrors.$onUnexpectedError(data);
}
});
}
terminate(reason: string): void {
if (this._isTerminating) {
// we are already shutting down...
return;
}
this._isTerminating = true;
this._logService.info(`extension host terminating: ${reason}`);
this._logService.flush();
this._disposables.dispose();
errors.setUnexpectedErrorHandler((err) => {
this._logService.error(err);
});
// Invalidate all proxies
this._rpcProtocol.dispose();
const extensionsDeactivated = this._extensionService.deactivateAll();
// Give extensions at most 5 seconds to wrap up any async deactivate, then exit
Promise.race([timeout(5000), extensionsDeactivated]).finally(() => {
if (this._hostUtils.pid) {
this._logService.info(`Extension host with pid ${this._hostUtils.pid} exiting with code 0`);
} else {
this._logService.info(`Extension host exiting with code 0`);
}
this._logService.flush();
this._logService.dispose();
this._hostUtils.exit(0);
});
}
private static _transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData {
initData.extensions.forEach((ext) => (<any>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)));
initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
const extDevLocs = initData.environment.extensionDevelopmentLocationURI;
if (extDevLocs) {
initData.environment.extensionDevelopmentLocationURI = extDevLocs.map(url => URI.revive(rpcProtocol.transformIncomingURIs(url)));
}
initData.environment.extensionTestsLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionTestsLocationURI));
initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome));
initData.environment.workspaceStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.workspaceStorageHome));
initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation));
initData.logFile = URI.revive(rpcProtocol.transformIncomingURIs(initData.logFile));
initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace);
return initData;
}
}

View File

@@ -1,139 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as resources from 'vs/base/common/resources';
import { URI, UriComponents } from 'vs/base/common/uri';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import * as errors from 'vs/base/common/errors';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
import { ISearchService } from 'vs/workbench/services/search/common/search';
import { toWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ILogService } from 'vs/platform/log/common/log';
const WORKSPACE_CONTAINS_TIMEOUT = 7000;
export interface IExtensionActivationHost {
readonly logService: ILogService;
readonly folders: readonly UriComponents[];
readonly forceUsingSearch: boolean;
exists(uri: URI): Promise<boolean>;
checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise<boolean>;
}
export interface IExtensionActivationResult {
activationEvent: string;
}
export function checkActivateWorkspaceContainsExtension(host: IExtensionActivationHost, desc: IExtensionDescription): Promise<IExtensionActivationResult | undefined> {
const activationEvents = desc.activationEvents;
if (!activationEvents) {
return Promise.resolve(undefined);
}
const fileNames: string[] = [];
const globPatterns: string[] = [];
for (const activationEvent of activationEvents) {
if (/^workspaceContains:/.test(activationEvent)) {
const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length);
if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0 || host.forceUsingSearch) {
globPatterns.push(fileNameOrGlob);
} else {
fileNames.push(fileNameOrGlob);
}
}
}
if (fileNames.length === 0 && globPatterns.length === 0) {
return Promise.resolve(undefined);
}
let resolveResult: (value: IExtensionActivationResult | undefined) => void;
const result = new Promise<IExtensionActivationResult | undefined>((resolve, reject) => { resolveResult = resolve; });
const activate = (activationEvent: string) => resolveResult({ activationEvent });
const fileNamePromise = Promise.all(fileNames.map((fileName) => _activateIfFileName(host, fileName, activate))).then(() => { });
const globPatternPromise = _activateIfGlobPatterns(host, desc.identifier, globPatterns, activate);
Promise.all([fileNamePromise, globPatternPromise]).then(() => {
// when all are done, resolve with undefined (relevant only if it was not activated so far)
resolveResult(undefined);
});
return result;
}
async function _activateIfFileName(host: IExtensionActivationHost, fileName: string, activate: (activationEvent: string) => void): Promise<void> {
// find exact path
for (const uri of host.folders) {
if (await host.exists(resources.joinPath(URI.revive(uri), fileName))) {
// the file was found
activate(`workspaceContains:${fileName}`);
return;
}
}
}
async function _activateIfGlobPatterns(host: IExtensionActivationHost, extensionId: ExtensionIdentifier, globPatterns: string[], activate: (activationEvent: string) => void): Promise<void> {
if (globPatterns.length === 0) {
return Promise.resolve(undefined);
}
const tokenSource = new CancellationTokenSource();
const searchP = host.checkExists(host.folders, globPatterns, tokenSource.token);
const timer = setTimeout(async () => {
tokenSource.cancel();
host.logService.info(`Not activating extension '${extensionId.value}': Timed out while searching for 'workspaceContains' pattern ${globPatterns.join(',')}`);
}, WORKSPACE_CONTAINS_TIMEOUT);
let exists: boolean = false;
try {
exists = await searchP;
} catch (err) {
if (!errors.isCancellationError(err)) {
errors.onUnexpectedError(err);
}
}
tokenSource.dispose();
clearTimeout(timer);
if (exists) {
// a file was found matching one of the glob patterns
activate(`workspaceContains:${globPatterns.join(',')}`);
}
}
export function checkGlobFileExists(
accessor: ServicesAccessor,
folders: readonly UriComponents[],
includes: string[],
token: CancellationToken,
): Promise<boolean> {
const instantiationService = accessor.get(IInstantiationService);
const searchService = accessor.get(ISearchService);
const queryBuilder = instantiationService.createInstance(QueryBuilder);
const query = queryBuilder.file(folders.map(folder => toWorkspaceFolder(URI.revive(folder))), {
_reason: 'checkExists',
includePattern: includes,
exists: true
});
return searchService.fileSearch(query, token).then(
result => {
return !!result.limitHit;
},
err => {
if (!errors.isCancellationError(err)) {
return Promise.reject(err);
}
return false;
});
}

View File

@@ -8,7 +8,7 @@ import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHo
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
import { MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator';
import { connectProxyResolver } from 'vs/workbench/services/extensions/node/proxyResolver';
import { connectProxyResolver } from 'vs/workbench/api/node/proxyResolver';
import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadService';
import { URI } from 'vs/base/common/uri';

View File

@@ -3,6 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { startExtensionHostProcess } from 'vs/workbench/services/extensions/node/extensionHostProcessSetup';
import { startExtensionHostProcess } from 'vs/workbench/api/node/extensionHostProcessSetup';
startExtensionHostProcess().catch((err) => console.log(err));

View File

@@ -0,0 +1,372 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nativeWatchdog from 'native-watchdog';
import * as net from 'net';
import * as minimist from 'minimist';
import * as performance from 'vs/base/common/performance';
import { isCancellationError, onUnexpectedError } from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { PersistentProtocol, ProtocolConstants, BufferedEmitter } from 'vs/base/parts/ipc/common/ipc.net';
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import product from 'vs/platform/product/common/product';
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage, IExtHostReduceGraceTimeMessage, ExtensionHostExitCode } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { ExtensionHostMain, IExitFn } from 'vs/workbench/api/common/extensionHostMain';
import { VSBuffer } from 'vs/base/common/buffer';
import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc';
import { Promises } from 'vs/base/node/pfs';
import { realpath } from 'vs/base/node/extpath';
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import { ProcessTimeRunOnceScheduler } from 'vs/base/common/async';
import { boolean } from 'vs/editor/common/config/editorOptions';
import 'vs/workbench/api/common/extHost.common.services';
import 'vs/workbench/api/node/extHost.node.services';
interface ParsedExtHostArgs {
uriTransformerPath?: string;
skipWorkspaceStorageLock?: boolean;
useHostProxy?: boolean;
}
// workaround for https://github.com/microsoft/vscode/issues/85490
// remove --inspect-port=0 after start so that it doesn't trigger LSP debugging
(function removeInspectPort() {
for (let i = 0; i < process.execArgv.length; i++) {
if (process.execArgv[i] === '--inspect-port=0') {
process.execArgv.splice(i, 1);
i--;
}
}
})();
const args = minimist(process.argv.slice(2), {
string: [
'uriTransformerPath'
],
boolean: [
'skipWorkspaceStorageLock',
'useHostProxy'
]
}) as ParsedExtHostArgs;
// With Electron 2.x and node.js 8.x the "natives" module
// can cause a native crash (see https://github.com/nodejs/node/issues/19891 and
// https://github.com/electron/electron/issues/10905). To prevent this from
// happening we essentially blocklist this module from getting loaded in any
// extension by patching the node require() function.
(function () {
const Module = require.__$__nodeRequire('module') as any;
const originalLoad = Module._load;
Module._load = function (request: string) {
if (request === 'natives') {
throw new Error('Either the extension or an NPM dependency is using the [unsupported "natives" node module](https://go.microsoft.com/fwlink/?linkid=871887).');
}
return originalLoad.apply(this, arguments);
};
})();
// custom process.exit logic...
const nativeExit: IExitFn = process.exit.bind(process);
function patchProcess(allowExit: boolean) {
process.exit = function (code?: number) {
if (allowExit) {
nativeExit(code);
} else {
const err = new Error('An extension called process.exit() and this was prevented.');
console.warn(err.stack);
}
} as (code?: number) => never;
// override Electron's process.crash() method
process.crash = function () {
const err = new Error('An extension called process.crash() and this was prevented.');
console.warn(err.stack);
};
}
interface IRendererConnection {
protocol: IMessagePassingProtocol;
initData: IInitData;
}
// This calls exit directly in case the initialization is not finished and we need to exit
// Otherwise, if initialization completed we go to extensionHostMain.terminate()
let onTerminate = function (reason: string) {
nativeExit();
};
function _createExtHostProtocol(): Promise<PersistentProtocol> {
if (process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET) {
return new Promise<PersistentProtocol>((resolve, reject) => {
let protocol: PersistentProtocol | null = null;
let timer = setTimeout(() => {
onTerminate('VSCODE_EXTHOST_IPC_SOCKET timeout');
}, 60000);
const reconnectionGraceTime = ProtocolConstants.ReconnectionGraceTime;
const reconnectionShortGraceTime = ProtocolConstants.ReconnectionShortGraceTime;
const disconnectRunner1 = new ProcessTimeRunOnceScheduler(() => onTerminate('renderer disconnected for too long (1)'), reconnectionGraceTime);
const disconnectRunner2 = new ProcessTimeRunOnceScheduler(() => onTerminate('renderer disconnected for too long (2)'), reconnectionShortGraceTime);
process.on('message', (msg: IExtHostSocketMessage | IExtHostReduceGraceTimeMessage, handle: net.Socket) => {
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') {
// Disable Nagle's algorithm. We also do this on the server process,
// but nodejs doesn't document if this option is transferred with the socket
handle.setNoDelay(true);
const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64'));
let socket: NodeSocket | WebSocketNodeSocket;
if (msg.skipWebSocketFrames) {
socket = new NodeSocket(handle, 'extHost-socket');
} else {
const inflateBytes = VSBuffer.wrap(Buffer.from(msg.inflateBytes, 'base64'));
socket = new WebSocketNodeSocket(new NodeSocket(handle, 'extHost-socket'), msg.permessageDeflate, inflateBytes, false);
}
if (protocol) {
// reconnection case
disconnectRunner1.cancel();
disconnectRunner2.cancel();
protocol.beginAcceptReconnection(socket, initialDataChunk);
protocol.endAcceptReconnection();
protocol.sendResume();
} else {
clearTimeout(timer);
protocol = new PersistentProtocol(socket, initialDataChunk);
protocol.sendResume();
protocol.onDidDispose(() => onTerminate('renderer disconnected'));
resolve(protocol);
// Wait for rich client to reconnect
protocol.onSocketClose(() => {
// The socket has closed, let's give the renderer a certain amount of time to reconnect
disconnectRunner1.schedule();
});
}
}
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_REDUCE_GRACE_TIME') {
if (disconnectRunner2.isScheduled()) {
// we are disconnected and already running the short reconnection timer
return;
}
if (disconnectRunner1.isScheduled()) {
// we are disconnected and running the long reconnection timer
disconnectRunner2.schedule();
}
}
});
// Now that we have managed to install a message listener, ask the other side to send us the socket
const req: IExtHostReadyMessage = { type: 'VSCODE_EXTHOST_IPC_READY' };
if (process.send) {
process.send(req);
}
});
} else {
const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST!;
return new Promise<PersistentProtocol>((resolve, reject) => {
const socket = net.createConnection(pipeName, () => {
socket.removeListener('error', reject);
resolve(new PersistentProtocol(new NodeSocket(socket, 'extHost-renderer')));
});
socket.once('error', reject);
socket.on('close', () => {
onTerminate('renderer closed the socket');
});
});
}
}
async function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
const protocol = await _createExtHostProtocol();
return new class implements IMessagePassingProtocol {
private readonly _onMessage = new BufferedEmitter<VSBuffer>();
readonly onMessage: Event<VSBuffer> = this._onMessage.event;
private _terminating: boolean;
constructor() {
this._terminating = false;
protocol.onMessage((msg) => {
if (isMessageOfType(msg, MessageType.Terminate)) {
this._terminating = true;
onTerminate('received terminate message from renderer');
} else {
this._onMessage.fire(msg);
}
});
}
send(msg: any): void {
if (!this._terminating) {
protocol.send(msg);
}
}
drain(): Promise<void> {
return protocol.drain();
}
};
}
function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRendererConnection> {
return new Promise<IRendererConnection>((c) => {
// Listen init data message
const first = protocol.onMessage(raw => {
first.dispose();
const initData = <IInitData>JSON.parse(raw.toString());
const rendererCommit = initData.commit;
const myCommit = product.commit;
if (rendererCommit && myCommit) {
// Running in the built version where commits are defined
if (rendererCommit !== myCommit) {
nativeExit(ExtensionHostExitCode.VersionMismatch);
}
}
// Kill oneself if one's parent dies. Much drama.
let epermErrors = 0;
setInterval(function () {
try {
process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
epermErrors = 0;
} catch (e) {
if (e && e.code === 'EPERM') {
// Even if the parent process is still alive,
// some antivirus software can lead to an EPERM error to be thrown here.
// Let's terminate only if we get 3 consecutive EPERM errors.
epermErrors++;
if (epermErrors >= 3) {
onTerminate(`parent process ${initData.parentPid} does not exist anymore (3 x EPERM): ${e.message} (code: ${e.code}) (errno: ${e.errno})`);
}
} else {
onTerminate(`parent process ${initData.parentPid} does not exist anymore: ${e.message} (code: ${e.code}) (errno: ${e.errno})`);
}
}
}, 1000);
// In certain cases, the event loop can become busy and never yield
// e.g. while-true or process.nextTick endless loops
// So also use the native node module to do it from a separate thread
let watchdog: typeof nativeWatchdog;
try {
watchdog = require.__$__nodeRequire('native-watchdog');
watchdog.start(initData.parentPid);
} catch (err) {
// no problem...
onUnexpectedError(err);
}
// Tell the outside that we are initialized
protocol.send(createMessageOfType(MessageType.Initialized));
c({ protocol, initData });
});
// Tell the outside that we are ready to receive messages
protocol.send(createMessageOfType(MessageType.Ready));
});
}
export async function startExtensionHostProcess(): Promise<void> {
// Print a console message when rejection isn't handled within N seconds. For details:
// see https://nodejs.org/api/process.html#process_event_unhandledrejection
// and https://nodejs.org/api/process.html#process_event_rejectionhandled
const unhandledPromises: Promise<any>[] = [];
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
unhandledPromises.push(promise);
setTimeout(() => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
promise.catch(e => {
unhandledPromises.splice(idx, 1);
if (!isCancellationError(e)) {
console.warn(`rejected promise not handled within 1 second: ${e}`);
if (e && e.stack) {
console.warn(`stack trace: ${e.stack}`);
}
if (reason) {
onUnexpectedError(reason);
}
}
});
}
}, 1000);
});
process.on('rejectionHandled', (promise: Promise<any>) => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
unhandledPromises.splice(idx, 1);
}
});
// Print a console message when an exception isn't handled.
process.on('uncaughtException', function (err: Error) {
onUnexpectedError(err);
});
performance.mark(`code/extHost/willConnectToRenderer`);
const protocol = await createExtHostProtocol();
performance.mark(`code/extHost/didConnectToRenderer`);
const renderer = await connectToRenderer(protocol);
performance.mark(`code/extHost/didWaitForInitData`);
const { initData } = renderer;
// setup things
patchProcess(!!initData.environment.extensionTestsLocationURI); // to support other test frameworks like Jasmin that use process.exit (https://github.com/microsoft/vscode/issues/37708)
initData.environment.useHostProxy = !!args.useHostProxy;
initData.environment.skipWorkspaceStorageLock = boolean(args.skipWorkspaceStorageLock, false);
// host abstraction
const hostUtils = new class NodeHost implements IHostUtils {
declare readonly _serviceBrand: undefined;
public readonly pid = process.pid;
exit(code: number) { nativeExit(code); }
exists(path: string) { return Promises.exists(path); }
realpath(path: string) { return realpath(path); }
};
// Attempt to load uri transformer
let uriTransformer: IURITransformer | null = null;
if (initData.remote.authority && args.uriTransformerPath) {
try {
const rawURITransformerFactory = <any>require.__$__nodeRequire(args.uriTransformerPath);
const rawURITransformer = <IRawURITransformer>rawURITransformerFactory(initData.remote.authority);
uriTransformer = new URITransformer(rawURITransformer);
} catch (e) {
console.error(e);
}
}
const extensionHostMain = new ExtensionHostMain(
renderer.protocol,
initData,
hostUtils,
uriTransformer
);
// rewrite onTerminate-function to be a proper shutdown
onTerminate = (reason: string) => extensionHostMain.terminate(reason);
}

View File

@@ -0,0 +1,137 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as http from 'http';
import * as https from 'https';
import * as tls from 'tls';
import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace';
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
import { MainThreadTelemetryShape, IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { LogLevel, createHttpPatch, ProxyResolveEvent, createProxyResolver, createTlsPatch, ProxySupportSetting } from 'vscode-proxy-agent';
export function connectProxyResolver(
extHostWorkspace: IExtHostWorkspaceProvider,
configProvider: ExtHostConfigProvider,
extensionService: ExtHostExtensionService,
extHostLogService: ILogService,
mainThreadTelemetry: MainThreadTelemetryShape,
initData: IInitData,
) {
const useHostProxy = initData.environment.useHostProxy;
const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote;
const resolveProxy = createProxyResolver({
resolveProxy: url => extHostWorkspace.resolveProxy(url),
getHttpProxySetting: () => configProvider.getConfiguration('http').get('proxy'),
log: (level, message, ...args) => {
switch (level) {
case LogLevel.Trace: extHostLogService.trace(message, ...args); break;
case LogLevel.Debug: extHostLogService.debug(message, ...args); break;
case LogLevel.Info: extHostLogService.info(message, ...args); break;
case LogLevel.Warning: extHostLogService.warn(message, ...args); break;
case LogLevel.Error: extHostLogService.error(message, ...args); break;
case LogLevel.Critical: extHostLogService.critical(message, ...args); break;
case LogLevel.Off: break;
default: never(level, message, args); break;
}
function never(level: never, message: string, ...args: any[]) {
extHostLogService.error('Unknown log level', level);
extHostLogService.error(message, ...args);
}
},
getLogLevel: () => extHostLogService.getLevel(),
proxyResolveTelemetry: event => {
type ResolveProxyClassification = {
count: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
duration: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
errorCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
cacheCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
cacheSize: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
cacheRolls: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
envCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
settingsCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
localhostCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
envNoProxyCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
results: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
};
mainThreadTelemetry.$publicLog2<ProxyResolveEvent, ResolveProxyClassification>('resolveProxy', event);
},
useHostProxy: doUseHostProxy,
env: process.env,
});
const lookup = createPatchedModules(configProvider, resolveProxy);
return configureModuleLoading(extensionService, lookup);
}
function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProxy: ReturnType<typeof createProxyResolver>) {
const proxySetting = {
config: configProvider.getConfiguration('http')
.get<ProxySupportSetting>('proxySupport') || 'off'
};
configProvider.onDidChangeConfiguration(e => {
proxySetting.config = configProvider.getConfiguration('http')
.get<ProxySupportSetting>('proxySupport') || 'off';
});
const certSetting = {
config: !!configProvider.getConfiguration('http')
.get<boolean>('systemCertificates')
};
configProvider.onDidChangeConfiguration(e => {
certSetting.config = !!configProvider.getConfiguration('http')
.get<boolean>('systemCertificates');
});
return {
http: {
off: Object.assign({}, http, createHttpPatch(http, resolveProxy, { config: 'off' }, certSetting, true)),
on: Object.assign({}, http, createHttpPatch(http, resolveProxy, { config: 'on' }, certSetting, true)),
override: Object.assign({}, http, createHttpPatch(http, resolveProxy, { config: 'override' }, certSetting, true)),
onRequest: Object.assign({}, http, createHttpPatch(http, resolveProxy, proxySetting, certSetting, true)),
default: Object.assign(http, createHttpPatch(http, resolveProxy, proxySetting, certSetting, false)) // run last
} as Record<string, typeof http>,
https: {
off: Object.assign({}, https, createHttpPatch(https, resolveProxy, { config: 'off' }, certSetting, true)),
on: Object.assign({}, https, createHttpPatch(https, resolveProxy, { config: 'on' }, certSetting, true)),
override: Object.assign({}, https, createHttpPatch(https, resolveProxy, { config: 'override' }, certSetting, true)),
onRequest: Object.assign({}, https, createHttpPatch(https, resolveProxy, proxySetting, certSetting, true)),
default: Object.assign(https, createHttpPatch(https, resolveProxy, proxySetting, certSetting, false)) // run last
} as Record<string, typeof https>,
tls: Object.assign(tls, createTlsPatch(tls))
};
}
const modulesCache = new Map<IExtensionDescription | undefined, { http?: typeof http, https?: typeof https }>();
function configureModuleLoading(extensionService: ExtHostExtensionService, lookup: ReturnType<typeof createPatchedModules>): Promise<void> {
return extensionService.getExtensionPathIndex()
.then(extensionPaths => {
const node_module = <any>require.__$__nodeRequire('module');
const original = node_module._load;
node_module._load = function load(request: string, parent: { filename: string; }, isMain: boolean) {
if (request === 'tls') {
return lookup.tls;
}
if (request !== 'http' && request !== 'https') {
return original.apply(this, arguments);
}
const modules = lookup[request];
const ext = extensionPaths.findSubstr(URI.file(parent.filename));
let cache = modulesCache.get(ext);
if (!cache) {
modulesCache.set(ext, cache = {});
}
if (!cache[request]) {
let mod = modules.default;
cache[request] = <any>{ ...mod }; // Copy to work around #93167.
}
return cache[request];
};
});
}

View File

@@ -18,7 +18,7 @@ import { mock } from 'vs/base/test/common/mock';
import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol';
import { ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import { IPatternInfo } from 'vs/workbench/services/search/common/search';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';