mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-12 11:08:31 -05:00
246 lines
10 KiB
TypeScript
246 lines
10 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as errors from 'vs/base/common/errors';
|
|
import { Emitter, Event } from 'vs/base/common/event';
|
|
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
|
import * as strings from 'vs/base/common/strings';
|
|
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
|
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
|
import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
|
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol';
|
|
import { ProfileSession } from 'vs/workbench/services/extensions/common/extensions';
|
|
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
|
|
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
|
|
import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
|
|
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
|
|
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
|
|
|
// Enable to see detailed message communication between window and extension host
|
|
const LOG_EXTENSION_HOST_COMMUNICATION = false;
|
|
const LOG_USE_COLORS = true;
|
|
|
|
const NO_OP_VOID_PROMISE = Promise.resolve<void>(void 0);
|
|
|
|
export class ExtensionHostProcessManager extends Disposable {
|
|
|
|
public readonly onDidCrash: Event<[number, string]>;
|
|
|
|
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
|
|
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
|
|
|
|
/**
|
|
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
|
|
*/
|
|
private readonly _extensionHostProcessFinishedActivateEvents: { [activationEvent: string]: boolean; };
|
|
private _extensionHostProcessRPCProtocol: RPCProtocol;
|
|
private readonly _extensionHostProcessCustomers: IDisposable[];
|
|
private readonly _extensionHostProcessWorker: IExtensionHostStarter;
|
|
/**
|
|
* winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object.
|
|
*/
|
|
private _extensionHostProcessProxy: Thenable<{ value: ExtHostExtensionServiceShape; }>;
|
|
|
|
constructor(
|
|
extensionHostProcessWorker: IExtensionHostStarter,
|
|
private readonly _remoteAuthority: string,
|
|
initialActivationEvents: string[],
|
|
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
|
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
|
) {
|
|
super();
|
|
this._extensionHostProcessFinishedActivateEvents = Object.create(null);
|
|
this._extensionHostProcessRPCProtocol = null;
|
|
this._extensionHostProcessCustomers = [];
|
|
|
|
this._extensionHostProcessWorker = extensionHostProcessWorker;
|
|
this.onDidCrash = this._extensionHostProcessWorker.onCrashed;
|
|
this._extensionHostProcessProxy = this._extensionHostProcessWorker.start().then(
|
|
(protocol) => {
|
|
return { value: this._createExtensionHostCustomers(protocol) };
|
|
},
|
|
(err) => {
|
|
console.error('Error received from starting extension host');
|
|
console.error(err);
|
|
return null;
|
|
}
|
|
);
|
|
this._extensionHostProcessProxy.then(() => {
|
|
initialActivationEvents.forEach((activationEvent) => this.activateByEvent(activationEvent));
|
|
});
|
|
}
|
|
|
|
public dispose(): void {
|
|
if (this._extensionHostProcessWorker) {
|
|
this._extensionHostProcessWorker.dispose();
|
|
}
|
|
if (this._extensionHostProcessRPCProtocol) {
|
|
this._extensionHostProcessRPCProtocol.dispose();
|
|
}
|
|
for (let i = 0, len = this._extensionHostProcessCustomers.length; i < len; i++) {
|
|
const customer = this._extensionHostProcessCustomers[i];
|
|
try {
|
|
customer.dispose();
|
|
} catch (err) {
|
|
errors.onUnexpectedError(err);
|
|
}
|
|
}
|
|
this._extensionHostProcessProxy = null;
|
|
|
|
super.dispose();
|
|
}
|
|
|
|
// {{SQL CARBON EDIT}}
|
|
public getExtenstionHostProcessWorker(): IExtensionHostStarter {
|
|
return this._extensionHostProcessWorker;
|
|
}
|
|
// {{SQL CARBON EDIT}} - End
|
|
|
|
public canProfileExtensionHost(): boolean {
|
|
return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort());
|
|
}
|
|
|
|
private _createExtensionHostCustomers(protocol: IMessagePassingProtocol): ExtHostExtensionServiceShape {
|
|
|
|
let logger: IRPCProtocolLogger | null = null;
|
|
if (LOG_EXTENSION_HOST_COMMUNICATION || this._environmentService.logExtensionHostCommunication) {
|
|
logger = new RPCLogger();
|
|
}
|
|
|
|
this._extensionHostProcessRPCProtocol = new RPCProtocol(protocol, logger);
|
|
this._register(this._extensionHostProcessRPCProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState)));
|
|
const extHostContext: IExtHostContext = {
|
|
remoteAuthority: this._remoteAuthority,
|
|
getProxy: <T>(identifier: ProxyIdentifier<T>): T => this._extensionHostProcessRPCProtocol.getProxy(identifier),
|
|
set: <T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R => this._extensionHostProcessRPCProtocol.set(identifier, instance),
|
|
assertRegistered: (identifiers: ProxyIdentifier<any>[]): void => this._extensionHostProcessRPCProtocol.assertRegistered(identifiers),
|
|
};
|
|
|
|
// Named customers
|
|
const namedCustomers = ExtHostCustomersRegistry.getNamedCustomers();
|
|
for (let i = 0, len = namedCustomers.length; i < len; i++) {
|
|
const [id, ctor] = namedCustomers[i];
|
|
const instance = this._instantiationService.createInstance(ctor, extHostContext);
|
|
this._extensionHostProcessCustomers.push(instance);
|
|
this._extensionHostProcessRPCProtocol.set(id, instance);
|
|
}
|
|
|
|
// Customers
|
|
const customers = ExtHostCustomersRegistry.getCustomers();
|
|
for (let i = 0, len = customers.length; i < len; i++) {
|
|
const ctor = customers[i];
|
|
const instance = this._instantiationService.createInstance(ctor, extHostContext);
|
|
this._extensionHostProcessCustomers.push(instance);
|
|
}
|
|
|
|
// Check that no named customers are missing
|
|
const expected: ProxyIdentifier<any>[] = Object.keys(MainContext).map((key) => (<any>MainContext)[key]);
|
|
this._extensionHostProcessRPCProtocol.assertRegistered(expected);
|
|
|
|
return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService);
|
|
}
|
|
|
|
public activateByEvent(activationEvent: string): Thenable<void> {
|
|
if (this._extensionHostProcessFinishedActivateEvents[activationEvent] || !this._extensionHostProcessProxy) {
|
|
return NO_OP_VOID_PROMISE;
|
|
}
|
|
return this._extensionHostProcessProxy.then((proxy) => {
|
|
if (!proxy) {
|
|
// this case is already covered above and logged.
|
|
// i.e. the extension host could not be started
|
|
return NO_OP_VOID_PROMISE;
|
|
}
|
|
return proxy.value.$activateByEvent(activationEvent);
|
|
}).then(() => {
|
|
this._extensionHostProcessFinishedActivateEvents[activationEvent] = true;
|
|
});
|
|
}
|
|
|
|
public startExtensionHostProfile(): Promise<ProfileSession> {
|
|
if (this._extensionHostProcessWorker) {
|
|
let port = this._extensionHostProcessWorker.getInspectPort();
|
|
if (port) {
|
|
return this._instantiationService.createInstance(ExtensionHostProfiler, port).start();
|
|
}
|
|
}
|
|
throw new Error('Extension host not running or no inspect port available');
|
|
}
|
|
|
|
public getInspectPort(): number {
|
|
if (this._extensionHostProcessWorker) {
|
|
let port = this._extensionHostProcessWorker.getInspectPort();
|
|
if (port) {
|
|
return port;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public resolveAuthority(remoteAuthority: string): Thenable<ResolvedAuthority> {
|
|
return this._extensionHostProcessProxy.then(proxy => proxy.value.$resolveAuthority(remoteAuthority));
|
|
}
|
|
|
|
public start(enabledExtensionIds: string[]): Thenable<void> {
|
|
return this._extensionHostProcessProxy.then(proxy => proxy.value.$startExtensionHost(enabledExtensionIds));
|
|
}
|
|
}
|
|
|
|
const colorTables = [
|
|
['#2977B1', '#FC802D', '#34A13A', '#D3282F', '#9366BA'],
|
|
['#8B564C', '#E177C0', '#7F7F7F', '#BBBE3D', '#2EBECD']
|
|
];
|
|
|
|
function prettyWithoutArrays(data: any): any {
|
|
if (Array.isArray(data)) {
|
|
return data;
|
|
}
|
|
if (data && typeof data === 'object' && typeof data.toString === 'function') {
|
|
let result = data.toString();
|
|
if (result !== '[object Object]') {
|
|
return result;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function pretty(data: any): any {
|
|
if (Array.isArray(data)) {
|
|
return data.map(prettyWithoutArrays);
|
|
}
|
|
return prettyWithoutArrays(data);
|
|
}
|
|
|
|
class RPCLogger implements IRPCProtocolLogger {
|
|
|
|
private _totalIncoming = 0;
|
|
private _totalOutgoing = 0;
|
|
|
|
private _log(direction: string, totalLength, msgLength: number, req: number, initiator: RequestInitiator, str: string, data: any): void {
|
|
data = pretty(data);
|
|
|
|
const colorTable = colorTables[initiator];
|
|
const color = LOG_USE_COLORS ? colorTable[req % colorTable.length] : '#000000';
|
|
let args = [`%c[${direction}]%c[${strings.pad(totalLength, 7, ' ')}]%c[len: ${strings.pad(msgLength, 5, ' ')}]%c${strings.pad(req, 5, ' ')} - ${str}`, 'color: darkgreen', 'color: grey', 'color: grey', `color: ${color}`];
|
|
if (/\($/.test(str)) {
|
|
args = args.concat(data);
|
|
args.push(')');
|
|
} else {
|
|
args.push(data);
|
|
}
|
|
console.log.apply(console, args);
|
|
}
|
|
|
|
logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
|
|
this._totalIncoming += msgLength;
|
|
this._log('Ext \u2192 Win', this._totalIncoming, msgLength, req, initiator, str, data);
|
|
}
|
|
|
|
logOutgoing(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
|
|
this._totalOutgoing += msgLength;
|
|
this._log('Win \u2192 Ext', this._totalOutgoing, msgLength, req, initiator, str, data);
|
|
}
|
|
}
|