mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 3d67364fbfcf676d93be64f949e9b33e7f1b969e (#5028)
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IRemoteConsoleLog } from 'vs/base/common/console';
|
||||
|
||||
@@ -26,14 +25,22 @@ export interface ITerminateSessionEvent {
|
||||
subId?: string;
|
||||
}
|
||||
|
||||
export interface IReloadSessionEvent {
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export interface ICloseSessionEvent {
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export interface IExtensionHostDebugService {
|
||||
_serviceBrand: any;
|
||||
|
||||
reload(resource: URI): void;
|
||||
onReload: Event<URI>;
|
||||
reload(sessionId: string): void;
|
||||
onReload: Event<IReloadSessionEvent>;
|
||||
|
||||
close(resource: URI): void;
|
||||
onClose: Event<URI>;
|
||||
close(sessionId: string): void;
|
||||
onClose: Event<ICloseSessionEvent>;
|
||||
|
||||
attachSession(sessionId: string, port: number, subId?: string): void;
|
||||
onAttachSession: Event<IAttachSessionEvent>;
|
||||
|
||||
@@ -12,9 +12,6 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExtHostCustomersRegistry } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/common/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/common/proxyIdentifier';
|
||||
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
|
||||
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
@@ -28,6 +25,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||
import { IUntitledResourceInput } from 'vs/workbench/common/editor';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
// Enable to see detailed message communication between window and extension host
|
||||
const LOG_EXTENSION_HOST_COMMUNICATION = false;
|
||||
@@ -169,10 +167,6 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
return ExtensionHostProcessManager._convert(SIZE, sw.elapsed());
|
||||
}
|
||||
|
||||
public canProfileExtensionHost(): boolean {
|
||||
return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort());
|
||||
}
|
||||
|
||||
private _createExtensionHostCustomers(protocol: IMessagePassingProtocol): ExtHostExtensionServiceShape {
|
||||
|
||||
let logger: IRPCProtocolLogger | null = null;
|
||||
@@ -236,16 +230,6 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -256,6 +240,10 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public canProfileExtensionHost(): boolean {
|
||||
return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort());
|
||||
}
|
||||
|
||||
public async resolveAuthority(remoteAuthority: string): Promise<ResolvedAuthority> {
|
||||
const authorityPlusIndex = remoteAuthority.indexOf('+');
|
||||
if (authorityPlusIndex === -1) {
|
||||
@@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
||||
import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
|
||||
|
||||
export const nullExtensionDescription = Object.freeze(<IExtensionDescription>{
|
||||
identifier: new ExtensionIdentifier('nullExtensionDescription'),
|
||||
@@ -82,6 +83,14 @@ export interface IExtensionHostProfile {
|
||||
getAggregatedTimes(): Map<ProfileSegmentId, number>;
|
||||
}
|
||||
|
||||
export interface IExtensionHostStarter {
|
||||
readonly onCrashed: Event<[number, string | null]>;
|
||||
start(): Promise<IMessagePassingProtocol> | null;
|
||||
getInspectPort(): number | undefined;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extension id or one of the four known program states.
|
||||
*/
|
||||
@@ -116,11 +125,10 @@ export interface IWillActivateEvent {
|
||||
}
|
||||
|
||||
export interface IResponsiveStateChangeEvent {
|
||||
target: ICpuProfilerTarget;
|
||||
isResponsive: boolean;
|
||||
}
|
||||
|
||||
export interface IExtensionService extends ICpuProfilerTarget {
|
||||
export interface IExtensionService {
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
@@ -200,7 +208,8 @@ export interface IExtensionService extends ICpuProfilerTarget {
|
||||
getExtensionsStatus(): { [id: string]: IExtensionsStatus };
|
||||
|
||||
/**
|
||||
* Return the inspect port or 0.
|
||||
* Return the inspect port or `0`, the latter means inspection
|
||||
* is not possible.
|
||||
*/
|
||||
getInspectPort(): number;
|
||||
|
||||
@@ -227,19 +236,6 @@ export interface IExtensionService extends ICpuProfilerTarget {
|
||||
_onExtensionHostExit(code: number): void;
|
||||
}
|
||||
|
||||
export interface ICpuProfilerTarget {
|
||||
|
||||
/**
|
||||
* Check if the extension host can be profiled.
|
||||
*/
|
||||
canProfileExtensionHost(): boolean;
|
||||
|
||||
/**
|
||||
* Begin an extension host process profile session.
|
||||
*/
|
||||
startExtensionHostProfile(): Promise<ProfileSession>;
|
||||
}
|
||||
|
||||
export interface ProfileSession {
|
||||
stop(): Promise<IExtensionHostProfile>;
|
||||
}
|
||||
@@ -277,9 +273,7 @@ export class NullExtensionService implements IExtensionService {
|
||||
getExtension() { return Promise.resolve(undefined); }
|
||||
readExtensionPointContributions<T>(_extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> { return Promise.resolve(Object.create(null)); }
|
||||
getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { return Object.create(null); }
|
||||
canProfileExtensionHost(): boolean { return false; }
|
||||
getInspectPort(): number { return 0; }
|
||||
startExtensionHostProfile(): Promise<ProfileSession> { return Promise.resolve(Object.create(null)); }
|
||||
restartExtensionHost(): void { }
|
||||
startExtensionHost(): void { }
|
||||
stopExtensionHost(): void { }
|
||||
|
||||
@@ -258,11 +258,6 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
|
||||
this._receiveReply(msgLength, req, value);
|
||||
break;
|
||||
}
|
||||
case MessageType.ReplyOKBuffer: {
|
||||
let value = MessageIO.deserializeReplyOKBuffer(buff);
|
||||
this._receiveReply(msgLength, req, value);
|
||||
break;
|
||||
}
|
||||
case MessageType.ReplyOKVSBuffer: {
|
||||
let value = MessageIO.deserializeReplyOKVSBuffer(buff);
|
||||
this._receiveReply(msgLength, req, value);
|
||||
@@ -517,21 +512,11 @@ class MessageBuffer {
|
||||
return str;
|
||||
}
|
||||
|
||||
public static sizeBuffer(buff: VSBuffer): number {
|
||||
return 4 /* buffer length */ + buff.byteLength /* actual buffer */;
|
||||
}
|
||||
|
||||
public writeBuffer(buff: VSBuffer): void {
|
||||
this._buff.writeUint32BE(buff.byteLength, this._offset); this._offset += 4;
|
||||
this._buff.set(buff, this._offset); this._offset += buff.byteLength;
|
||||
}
|
||||
|
||||
public readBuffer(): Buffer {
|
||||
const buffLength = this._buff.readUint32BE(this._offset); this._offset += 4;
|
||||
const buff = this._buff.slice(this._offset, this._offset + buffLength); this._offset += buffLength;
|
||||
return <Buffer>buff.buffer;
|
||||
}
|
||||
|
||||
public static sizeVSBuffer(buff: VSBuffer): number {
|
||||
return 4 /* buffer length */ + buff.byteLength /* actual buffer */;
|
||||
}
|
||||
@@ -556,8 +541,6 @@ class MessageBuffer {
|
||||
size += 1; // arg type
|
||||
if (elType === ArgType.String) {
|
||||
size += this.sizeLongString(el);
|
||||
} else if (elType === ArgType.Buffer) {
|
||||
size += this.sizeBuffer(el);
|
||||
} else {
|
||||
size += this.sizeVSBuffer(el);
|
||||
}
|
||||
@@ -573,9 +556,6 @@ class MessageBuffer {
|
||||
if (elType === ArgType.String) {
|
||||
this.writeUInt8(ArgType.String);
|
||||
this.writeLongString(el);
|
||||
} else if (elType === ArgType.Buffer) {
|
||||
this.writeUInt8(ArgType.Buffer);
|
||||
this.writeVSBuffer(el);
|
||||
} else {
|
||||
this.writeUInt8(ArgType.VSBuffer);
|
||||
this.writeVSBuffer(el);
|
||||
@@ -583,18 +563,15 @@ class MessageBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
public readMixedArray(): Array<string | Buffer | VSBuffer> {
|
||||
public readMixedArray(): Array<string | VSBuffer> {
|
||||
const arrLen = this._buff.readUint8(this._offset); this._offset += 1;
|
||||
let arr: Array<string | Buffer | VSBuffer> = new Array(arrLen);
|
||||
let arr: Array<string | VSBuffer> = new Array(arrLen);
|
||||
for (let i = 0; i < arrLen; i++) {
|
||||
const argType = <ArgType>this.readUInt8();
|
||||
switch (argType) {
|
||||
case ArgType.String:
|
||||
arr[i] = this.readLongString();
|
||||
break;
|
||||
case ArgType.Buffer:
|
||||
arr[i] = this.readBuffer();
|
||||
break;
|
||||
case ArgType.VSBuffer:
|
||||
arr[i] = this.readVSBuffer();
|
||||
break;
|
||||
@@ -608,9 +585,6 @@ class MessageIO {
|
||||
|
||||
private static _arrayContainsBuffer(arr: any[]): boolean {
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
if (Buffer.isBuffer(arr[i])) {
|
||||
return true;
|
||||
}
|
||||
if (arr[i] instanceof VSBuffer) {
|
||||
return true;
|
||||
}
|
||||
@@ -624,10 +598,7 @@ class MessageIO {
|
||||
let massagedArgsType: ArgType[] = [];
|
||||
for (let i = 0, len = args.length; i < len; i++) {
|
||||
const arg = args[i];
|
||||
if (Buffer.isBuffer(arg)) {
|
||||
massagedArgs[i] = VSBuffer.wrap(arg);
|
||||
massagedArgsType[i] = ArgType.Buffer;
|
||||
} else if (arg instanceof VSBuffer) {
|
||||
if (arg instanceof VSBuffer) {
|
||||
massagedArgs[i] = arg;
|
||||
massagedArgsType[i] = ArgType.VSBuffer;
|
||||
} else {
|
||||
@@ -714,9 +685,6 @@ class MessageIO {
|
||||
if (typeof res === 'undefined') {
|
||||
return this._serializeReplyOKEmpty(req);
|
||||
}
|
||||
if (Buffer.isBuffer(res)) {
|
||||
return this._serializeReplyOKBuffer(req, res);
|
||||
}
|
||||
if (res instanceof VSBuffer) {
|
||||
return this._serializeReplyOKVSBuffer(req, res);
|
||||
}
|
||||
@@ -727,17 +695,6 @@ class MessageIO {
|
||||
return MessageBuffer.alloc(MessageType.ReplyOKEmpty, req, 0).buffer;
|
||||
}
|
||||
|
||||
private static _serializeReplyOKBuffer(req: number, res: Buffer): VSBuffer {
|
||||
const buff = VSBuffer.wrap(res);
|
||||
|
||||
let len = 0;
|
||||
len += MessageBuffer.sizeBuffer(buff);
|
||||
|
||||
let result = MessageBuffer.alloc(MessageType.ReplyOKBuffer, req, len);
|
||||
result.writeBuffer(buff);
|
||||
return result.buffer;
|
||||
}
|
||||
|
||||
private static _serializeReplyOKVSBuffer(req: number, res: VSBuffer): VSBuffer {
|
||||
let len = 0;
|
||||
len += MessageBuffer.sizeVSBuffer(res);
|
||||
@@ -747,10 +704,6 @@ class MessageIO {
|
||||
return result.buffer;
|
||||
}
|
||||
|
||||
public static deserializeReplyOKBuffer(buff: MessageBuffer): Buffer {
|
||||
return buff.readBuffer();
|
||||
}
|
||||
|
||||
public static deserializeReplyOKVSBuffer(buff: MessageBuffer): VSBuffer {
|
||||
return buff.readVSBuffer();
|
||||
}
|
||||
@@ -807,15 +760,13 @@ const enum MessageType {
|
||||
Acknowledged = 5,
|
||||
Cancel = 6,
|
||||
ReplyOKEmpty = 7,
|
||||
ReplyOKBuffer = 8,
|
||||
ReplyOKVSBuffer = 9,
|
||||
ReplyOKJSON = 10,
|
||||
ReplyErrError = 11,
|
||||
ReplyErrEmpty = 12,
|
||||
ReplyOKVSBuffer = 8,
|
||||
ReplyOKJSON = 9,
|
||||
ReplyErrError = 10,
|
||||
ReplyErrEmpty = 11,
|
||||
}
|
||||
|
||||
const enum ArgType {
|
||||
String = 1,
|
||||
Buffer = 2,
|
||||
VSBuffer = 3
|
||||
VSBuffer = 2
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import pkg from 'vs/platform/product/node/package';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console';
|
||||
@@ -38,13 +37,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'
|
||||
import { parseExtensionDevOptions } from '../common/extensionDevOptions';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug';
|
||||
|
||||
export interface IExtensionHostStarter {
|
||||
readonly onCrashed: Event<[number, string | null]>;
|
||||
start(): Promise<IMessagePassingProtocol> | null;
|
||||
getInspectPort(): number | undefined;
|
||||
dispose(): void;
|
||||
}
|
||||
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
|
||||
@@ -102,13 +95,13 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
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._extensionHostDebugService.onClose(resource => {
|
||||
if (this._isExtensionDevHost && this.matchesExtDevLocations(resource)) {
|
||||
this._toDispose.push(this._extensionHostDebugService.onClose(event => {
|
||||
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) {
|
||||
this._windowService.closeWindow();
|
||||
}
|
||||
}));
|
||||
this._toDispose.push(this._extensionHostDebugService.onReload(resource => {
|
||||
if (this._isExtensionDevHost && this.matchesExtDevLocations(resource)) {
|
||||
this._toDispose.push(this._extensionHostDebugService.onReload(event => {
|
||||
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) {
|
||||
this._windowService.reloadWindow();
|
||||
}
|
||||
}));
|
||||
@@ -120,16 +113,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
}));
|
||||
}
|
||||
|
||||
// returns true if the given resource url matches one of the extension development paths passed to VS Code
|
||||
private matchesExtDevLocations(resource: URI): boolean {
|
||||
|
||||
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
|
||||
if (extDevLocs) {
|
||||
return extDevLocs.some(extDevLoc => isEqual(extDevLoc, resource));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.terminate();
|
||||
}
|
||||
@@ -241,8 +224,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
if (!this._environmentService.isBuilt && !this._environmentService.configuration.remoteAuthority || 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.");
|
||||
? 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.")
|
||||
: nls.localize('extensionHost.startupFail', "Extension host did not start in 10 seconds, that might be a problem.");
|
||||
|
||||
this._notificationService.prompt(Severity.Warning, msg,
|
||||
[{
|
||||
@@ -457,7 +440,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
|
||||
this._lastExtensionHostError = errorMessage;
|
||||
|
||||
this._notificationService.error(nls.localize('extensionHostProcess.error', "Error from the extension host: {0}", errorMessage));
|
||||
this._notificationService.error(nls.localize('extensionHost.error', "Error from the extension host: {0}", errorMessage));
|
||||
}
|
||||
|
||||
private _onExtHostProcessExit(code: number, signal: string): void {
|
||||
|
||||
@@ -6,23 +6,20 @@
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IExtensionHostDebugService, IAttachSessionEvent, ITerminateSessionEvent, ILogToSessionEvent } from 'vs/workbench/services/extensions/common/extensionHostDebug';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExtensionHostDebugService, IAttachSessionEvent, ITerminateSessionEvent, ILogToSessionEvent, IReloadSessionEvent, ICloseSessionEvent } from 'vs/workbench/services/extensions/common/extensionHostDebug';
|
||||
import { IRemoteConsoleLog } from 'vs/base/common/console';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
|
||||
interface IReloadBroadcast {
|
||||
interface IReloadBroadcast extends IReloadSessionEvent {
|
||||
type: 'vscode:extensionReload';
|
||||
resource: string;
|
||||
}
|
||||
|
||||
interface IAttachSessionBroadcast extends IAttachSessionEvent {
|
||||
type: 'vscode:extensionAttach';
|
||||
}
|
||||
|
||||
interface ICloseBroadcast {
|
||||
interface ICloseBroadcast extends ICloseSessionEvent {
|
||||
type: 'vscode:extensionCloseExtensionHost';
|
||||
resource: string;
|
||||
}
|
||||
|
||||
interface ILogToSessionBroadcast extends ILogToSessionEvent {
|
||||
@@ -39,8 +36,8 @@ class ExtensionHostDebugService implements IExtensionHostDebugService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private windowId: number;
|
||||
private readonly _onReload = new Emitter<URI>();
|
||||
private readonly _onClose = new Emitter<URI>();
|
||||
private readonly _onReload = new Emitter<IReloadSessionEvent>();
|
||||
private readonly _onClose = new Emitter<ICloseSessionEvent>();
|
||||
private readonly _onAttachSession = new Emitter<IAttachSessionEvent>();
|
||||
private readonly _onLogToSession = new Emitter<ILogToSessionEvent>();
|
||||
private readonly _onTerminateSession = new Emitter<ITerminateSessionEvent>();
|
||||
@@ -53,10 +50,10 @@ class ExtensionHostDebugService implements IExtensionHostDebugService {
|
||||
ipc.on(CHANNEL, (_: unknown, broadcast: IReloadBroadcast | ICloseBroadcast | IAttachSessionBroadcast | ILogToSessionBroadcast | ITerminateSessionBroadcast) => {
|
||||
switch (broadcast.type) {
|
||||
case 'vscode:extensionReload':
|
||||
this._onReload.fire(URI.parse(broadcast.resource));
|
||||
this._onReload.fire(broadcast);
|
||||
break;
|
||||
case 'vscode:extensionCloseExtensionHost':
|
||||
this._onClose.fire(URI.parse(broadcast.resource));
|
||||
this._onClose.fire(broadcast);
|
||||
break;
|
||||
case 'vscode:extensionAttach':
|
||||
this._onAttachSession.fire(broadcast);
|
||||
@@ -71,25 +68,25 @@ class ExtensionHostDebugService implements IExtensionHostDebugService {
|
||||
});
|
||||
}
|
||||
|
||||
reload(resource: URI): void {
|
||||
reload(sessionId: string): void {
|
||||
ipc.send(CHANNEL, this.windowId, <IReloadBroadcast>{
|
||||
type: 'vscode:extensionReload',
|
||||
resource: resource.toString()
|
||||
sessionId
|
||||
});
|
||||
}
|
||||
|
||||
get onReload(): Event<URI> {
|
||||
get onReload(): Event<IReloadSessionEvent> {
|
||||
return this._onReload.event;
|
||||
}
|
||||
|
||||
close(resource: URI): void {
|
||||
close(sessionId: string): void {
|
||||
ipc.send(CHANNEL, this.windowId, <ICloseBroadcast>{
|
||||
type: 'vscode:extensionCloseExtensionHost',
|
||||
resource: resource.toString()
|
||||
sessionId
|
||||
});
|
||||
}
|
||||
|
||||
get onClose(): Event<URI> {
|
||||
get onClose(): Event<ICloseSessionEvent> {
|
||||
return this._onClose.event;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ import product from 'vs/platform/product/node/product';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser, schema } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
||||
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
|
||||
import { CachedExtensionScanner, Logger } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner';
|
||||
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/electron-browser/extensionHostProcessManager';
|
||||
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager';
|
||||
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
@@ -442,7 +442,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation);
|
||||
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents);
|
||||
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal));
|
||||
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ target: extHostProcessManager, isResponsive: responsiveState === ResponsiveState.Responsive }); });
|
||||
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
|
||||
this._extensionHostProcessManagers.push(extHostProcessManager);
|
||||
}
|
||||
|
||||
@@ -453,7 +453,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
if (code === 55) {
|
||||
this._notificationService.prompt(
|
||||
Severity.Error,
|
||||
nls.localize('extensionHostProcess.versionMismatchCrash', "Extension host cannot start: version mismatch."),
|
||||
nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."),
|
||||
[{
|
||||
label: nls.localize('relaunch', "Relaunch VS Code"),
|
||||
run: () => {
|
||||
@@ -467,9 +467,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = nls.localize('extensionHostProcess.crash', "Extension host terminated unexpectedly.");
|
||||
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
|
||||
if (code === 87) {
|
||||
message = nls.localize('extensionHostProcess.unresponsiveCrash', "Extension host terminated because it was not responsive.");
|
||||
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
|
||||
}
|
||||
|
||||
this._notificationService.prompt(Severity.Error, message,
|
||||
@@ -569,26 +569,6 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
return result;
|
||||
}
|
||||
|
||||
public canProfileExtensionHost(): boolean {
|
||||
for (let i = 0, len = this._extensionHostProcessManagers.length; i < len; i++) {
|
||||
const extHostProcessManager = this._extensionHostProcessManagers[i];
|
||||
if (extHostProcessManager.canProfileExtensionHost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public startExtensionHostProfile(): Promise<ProfileSession> {
|
||||
for (let i = 0, len = this._extensionHostProcessManagers.length; i < len; i++) {
|
||||
const extHostProcessManager = this._extensionHostProcessManagers[i];
|
||||
if (extHostProcessManager.canProfileExtensionHost()) {
|
||||
return extHostProcessManager.startExtensionHostProfile();
|
||||
}
|
||||
}
|
||||
throw new Error('Extension host not running or no inspect port available');
|
||||
}
|
||||
|
||||
public getInspectPort(): number {
|
||||
if (this._extensionHostProcessManagers.length > 0) {
|
||||
return this._extensionHostProcessManagers[0].getInspectPort();
|
||||
|
||||
@@ -12,13 +12,14 @@ 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 { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { ExtHostExtensionService, IHostUtils } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures';
|
||||
|
||||
// we don't (yet) throw when extensions parse
|
||||
// uris that have no scheme
|
||||
@@ -39,17 +40,25 @@ export interface ILogServiceFn {
|
||||
export class ExtensionHostMain {
|
||||
|
||||
private _isTerminating: boolean;
|
||||
private readonly _exitFn: IExitFn;
|
||||
private readonly _hostUtils: IHostUtils;
|
||||
private readonly _extensionService: ExtHostExtensionService;
|
||||
private readonly _extHostLogService: ExtHostLogService;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
private _searchRequestIdProvider: Counter;
|
||||
|
||||
constructor(protocol: IMessagePassingProtocol, initData: IInitData, exitFn: IExitFn, consolePatchFn: IConsolePatchFn, logServiceFn: ILogServiceFn) {
|
||||
constructor(
|
||||
protocol: IMessagePassingProtocol,
|
||||
initData: IInitData,
|
||||
hostUtils: IHostUtils,
|
||||
consolePatchFn: IConsolePatchFn,
|
||||
logServiceFn: ILogServiceFn,
|
||||
uriTransformer: IURITransformer | null,
|
||||
schemeTransformer: ISchemeTransformer | null,
|
||||
outputChannelName: string,
|
||||
) {
|
||||
this._isTerminating = false;
|
||||
this._exitFn = exitFn;
|
||||
const uriTransformer: IURITransformer | null = null;
|
||||
this._hostUtils = hostUtils;
|
||||
const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
|
||||
|
||||
// ensure URIs are transformed and revived
|
||||
@@ -69,7 +78,17 @@ export class ExtensionHostMain {
|
||||
this._extHostLogService.trace('initData', initData);
|
||||
|
||||
const extHostConfiguraiton = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace);
|
||||
this._extensionService = new ExtHostExtensionService(exitFn, initData, rpcProtocol, extHostWorkspace, extHostConfiguraiton, initData.environment, this._extHostLogService);
|
||||
this._extensionService = new ExtHostExtensionService(
|
||||
hostUtils,
|
||||
initData,
|
||||
rpcProtocol,
|
||||
extHostWorkspace,
|
||||
extHostConfiguraiton,
|
||||
initData.environment,
|
||||
this._extHostLogService,
|
||||
schemeTransformer,
|
||||
outputChannelName
|
||||
);
|
||||
|
||||
// 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)
|
||||
@@ -122,7 +141,7 @@ export class ExtensionHostMain {
|
||||
|
||||
// Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds
|
||||
setTimeout(() => {
|
||||
Promise.race([timeout(4000), extensionsDeactivated]).finally(() => this._exitFn());
|
||||
Promise.race([timeout(4000), extensionsDeactivated]).finally(() => this._hostUtils.exit());
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,287 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nativeWatchdog from 'native-watchdog';
|
||||
import * as net from 'net';
|
||||
import { 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 } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
||||
import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import * as nls from 'vs/nls';
|
||||
import { startExtensionHostProcess } from 'vs/workbench/services/extensions/node/extensionHostProcessSetup';
|
||||
|
||||
// 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 a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more');
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
process.crash = function () {
|
||||
const err = new Error('An extension called process.crash() and this was prevented.');
|
||||
console.warn(err.stack);
|
||||
};
|
||||
}
|
||||
|
||||
// use IPC messages to forward console-calls
|
||||
function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
|
||||
// The console is already patched to use `process.send()`
|
||||
const nativeProcessSend = process.send!;
|
||||
process.send = (...args: any[]) => {
|
||||
if (args.length === 0 || !args[0] || args[0].type !== '__$console') {
|
||||
return nativeProcessSend.apply(process, args);
|
||||
}
|
||||
|
||||
mainThreadConsole.$logExtensionHostMessage(args[0]);
|
||||
};
|
||||
}
|
||||
|
||||
const createLogService: ILogServiceFn = initData => createSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
|
||||
|
||||
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 () {
|
||||
nativeExit();
|
||||
};
|
||||
|
||||
function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
|
||||
if (process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET) {
|
||||
|
||||
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
|
||||
|
||||
let protocol: PersistentProtocol | null = null;
|
||||
|
||||
let timer = setTimeout(() => {
|
||||
reject(new Error('VSCODE_EXTHOST_IPC_SOCKET timeout'));
|
||||
}, 60000);
|
||||
|
||||
let disconnectWaitTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
process.on('message', (msg: IExtHostSocketMessage, handle: net.Socket) => {
|
||||
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') {
|
||||
const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64'));
|
||||
if (protocol) {
|
||||
// reconnection case
|
||||
if (disconnectWaitTimer) {
|
||||
clearTimeout(disconnectWaitTimer);
|
||||
disconnectWaitTimer = null;
|
||||
}
|
||||
protocol.beginAcceptReconnection(new NodeSocket(handle), initialDataChunk);
|
||||
protocol.endAcceptReconnection();
|
||||
} else {
|
||||
clearTimeout(timer);
|
||||
protocol = new PersistentProtocol(new NodeSocket(handle), initialDataChunk);
|
||||
protocol.onClose(() => onTerminate());
|
||||
resolve(protocol);
|
||||
|
||||
protocol.onSocketClose(() => {
|
||||
// The socket has closed, let's give the renderer a certain amount of time to reconnect
|
||||
disconnectWaitTimer = setTimeout(() => {
|
||||
disconnectWaitTimer = null;
|
||||
onTerminate();
|
||||
}, ProtocolConstants.ReconnectionGraceTime);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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<IMessagePassingProtocol>((resolve, reject) => {
|
||||
|
||||
const socket = net.createConnection(pipeName, () => {
|
||||
socket.removeListener('error', reject);
|
||||
resolve(new PersistentProtocol(new NodeSocket(socket)));
|
||||
});
|
||||
socket.once('error', reject);
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
|
||||
|
||||
const protocol = await _createExtHostProtocol();
|
||||
|
||||
return new class implements IMessagePassingProtocol {
|
||||
|
||||
private _terminating = false;
|
||||
|
||||
readonly onMessage: Event<any> = Event.filter(protocol.onMessage, msg => {
|
||||
if (!isMessageOfType(msg, MessageType.Terminate)) {
|
||||
return true;
|
||||
}
|
||||
this._terminating = true;
|
||||
onTerminate();
|
||||
return false;
|
||||
});
|
||||
|
||||
send(msg: any): void {
|
||||
if (!this._terminating) {
|
||||
protocol.send(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRendererConnection> {
|
||||
return new Promise<IRendererConnection>((c, e) => {
|
||||
|
||||
// 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(55);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
console.warn(`rejected promise not handled within 1 second: ${e}`);
|
||||
if (e.stack) {
|
||||
console.warn(`stack trace: ${e.stack}`);
|
||||
}
|
||||
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);
|
||||
});
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
process.on('SIGPIPE', () => {
|
||||
onUnexpectedError(new Error('Unexpected SIGPIPE'));
|
||||
});
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
// Kill oneself if one's parent dies. Much drama.
|
||||
setInterval(function () {
|
||||
try {
|
||||
process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
|
||||
} catch (e) {
|
||||
onTerminate();
|
||||
}
|
||||
}, 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));
|
||||
});
|
||||
}
|
||||
|
||||
patchExecArgv();
|
||||
|
||||
createExtHostProtocol().then(protocol => {
|
||||
// connect to main side
|
||||
return connectToRenderer(protocol);
|
||||
}).then(renderer => {
|
||||
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)
|
||||
|
||||
const extensionHostMain = new ExtensionHostMain(renderer.protocol, initData, nativeExit, patchPatchedConsole, createLogService);
|
||||
|
||||
// rewrite onTerminate-function to be a proper shutdown
|
||||
onTerminate = () => extensionHostMain.terminate();
|
||||
}).catch(err => console.error(err));
|
||||
|
||||
function patchExecArgv() {
|
||||
// when encountering the prevent-inspect flag we delete this
|
||||
// and the prior flag
|
||||
if (process.env.VSCODE_PREVENT_FOREIGN_INSPECT) {
|
||||
for (let i = 0; i < process.execArgv.length; i++) {
|
||||
if (process.execArgv[i].match(/--inspect-brk=\d+|--inspect=\d+/)) {
|
||||
process.execArgv.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
startExtensionHostProcess(
|
||||
_ => null,
|
||||
_ => null,
|
||||
_ => nls.localize('extension host Log', "Extension Host")
|
||||
).catch((err) => console.log(err));
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { 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 } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
||||
import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures';
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
import { exists } from 'vs/base/node/pfs';
|
||||
import { realpath } from 'vs/base/node/extpath';
|
||||
import { IHostUtils } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
|
||||
// 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 a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more');
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
process.crash = function () {
|
||||
const err = new Error('An extension called process.crash() and this was prevented.');
|
||||
console.warn(err.stack);
|
||||
};
|
||||
}
|
||||
|
||||
// use IPC messages to forward console-calls
|
||||
function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
|
||||
// The console is already patched to use `process.send()`
|
||||
const nativeProcessSend = process.send!;
|
||||
process.send = (...args: any[]) => {
|
||||
if (args.length === 0 || !args[0] || args[0].type !== '__$console') {
|
||||
return nativeProcessSend.apply(process, args);
|
||||
}
|
||||
|
||||
mainThreadConsole.$logExtensionHostMessage(args[0]);
|
||||
};
|
||||
}
|
||||
|
||||
const createLogService: ILogServiceFn = initData => createSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
|
||||
|
||||
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 () {
|
||||
nativeExit();
|
||||
};
|
||||
|
||||
function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
|
||||
if (process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET) {
|
||||
|
||||
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
|
||||
|
||||
let protocol: PersistentProtocol | null = null;
|
||||
|
||||
let timer = setTimeout(() => {
|
||||
reject(new Error('VSCODE_EXTHOST_IPC_SOCKET timeout'));
|
||||
}, 60000);
|
||||
|
||||
let disconnectWaitTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
process.on('message', (msg: IExtHostSocketMessage, handle: net.Socket) => {
|
||||
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') {
|
||||
const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64'));
|
||||
if (protocol) {
|
||||
// reconnection case
|
||||
if (disconnectWaitTimer) {
|
||||
clearTimeout(disconnectWaitTimer);
|
||||
disconnectWaitTimer = null;
|
||||
}
|
||||
protocol.beginAcceptReconnection(new NodeSocket(handle), initialDataChunk);
|
||||
protocol.endAcceptReconnection();
|
||||
} else {
|
||||
clearTimeout(timer);
|
||||
protocol = new PersistentProtocol(new NodeSocket(handle), initialDataChunk);
|
||||
protocol.onClose(() => onTerminate());
|
||||
resolve(protocol);
|
||||
|
||||
protocol.onSocketClose(() => {
|
||||
// The socket has closed, let's give the renderer a certain amount of time to reconnect
|
||||
disconnectWaitTimer = setTimeout(() => {
|
||||
disconnectWaitTimer = null;
|
||||
onTerminate();
|
||||
}, ProtocolConstants.ReconnectionGraceTime);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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<IMessagePassingProtocol>((resolve, reject) => {
|
||||
|
||||
const socket = net.createConnection(pipeName, () => {
|
||||
socket.removeListener('error', reject);
|
||||
resolve(new PersistentProtocol(new NodeSocket(socket)));
|
||||
});
|
||||
socket.once('error', reject);
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
|
||||
|
||||
const protocol = await _createExtHostProtocol();
|
||||
|
||||
return new class implements IMessagePassingProtocol {
|
||||
|
||||
private _terminating = false;
|
||||
|
||||
readonly onMessage: Event<any> = Event.filter(protocol.onMessage, msg => {
|
||||
if (!isMessageOfType(msg, MessageType.Terminate)) {
|
||||
return true;
|
||||
}
|
||||
this._terminating = true;
|
||||
onTerminate();
|
||||
return false;
|
||||
});
|
||||
|
||||
send(msg: any): void {
|
||||
if (!this._terminating) {
|
||||
protocol.send(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRendererConnection> {
|
||||
return new Promise<IRendererConnection>((c, e) => {
|
||||
|
||||
// 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(55);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
console.warn(`rejected promise not handled within 1 second: ${e}`);
|
||||
if (e.stack) {
|
||||
console.warn(`stack trace: ${e.stack}`);
|
||||
}
|
||||
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);
|
||||
});
|
||||
|
||||
// Kill oneself if one's parent dies. Much drama.
|
||||
setInterval(function () {
|
||||
try {
|
||||
process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
|
||||
} catch (e) {
|
||||
onTerminate();
|
||||
}
|
||||
}, 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));
|
||||
});
|
||||
}
|
||||
|
||||
// patchExecArgv:
|
||||
(function () {
|
||||
// when encountering the prevent-inspect flag we delete this
|
||||
// and the prior flag
|
||||
if (process.env.VSCODE_PREVENT_FOREIGN_INSPECT) {
|
||||
for (let i = 0; i < process.execArgv.length; i++) {
|
||||
if (process.execArgv[i].match(/--inspect-brk=\d+|--inspect=\d+/)) {
|
||||
process.execArgv.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
export async function startExtensionHostProcess(
|
||||
uriTransformerFn: (initData: IInitData) => IURITransformer | null,
|
||||
schemeTransformerFn: (initData: IInitData) => ISchemeTransformer | null,
|
||||
outputChannelNameFn: (initData: IInitData) => string,
|
||||
): Promise<void> {
|
||||
|
||||
const protocol = await createExtHostProtocol();
|
||||
const renderer = await connectToRenderer(protocol);
|
||||
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)
|
||||
|
||||
// host abstraction
|
||||
const hostUtils = new class NodeHost implements IHostUtils {
|
||||
exit(code: number) { nativeExit(code); }
|
||||
exists(path: string) { return exists(path); }
|
||||
realpath(path: string) { return realpath(path); }
|
||||
};
|
||||
|
||||
|
||||
const extensionHostMain = new ExtensionHostMain(
|
||||
renderer.protocol,
|
||||
initData,
|
||||
hostUtils,
|
||||
patchPatchedConsole,
|
||||
createLogService,
|
||||
uriTransformerFn(initData),
|
||||
schemeTransformerFn(initData),
|
||||
outputChannelNameFn(initData)
|
||||
);
|
||||
|
||||
// rewrite onTerminate-function to be a proper shutdown
|
||||
onTerminate = () => extensionHostMain.terminate();
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
|
||||
import { localize } from 'vs/nls';
|
||||
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
|
||||
export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService {
|
||||
|
||||
@@ -35,8 +34,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
constructor(
|
||||
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
this.servers = this.extensionManagementServerService.remoteExtensionManagementServer ? [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer] : [this.extensionManagementServerService.localExtensionManagementServer];
|
||||
@@ -58,8 +56,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
if (!server) {
|
||||
return Promise.reject(`Invalid location ${extension.location.toString()}`);
|
||||
}
|
||||
const syncExtensions = await this.hasToSyncExtensions();
|
||||
if (syncExtensions || isLanguagePackExtension(extension.manifest)) {
|
||||
if (isLanguagePackExtension(extension.manifest)) {
|
||||
return this.uninstallEverywhere(extension, force);
|
||||
}
|
||||
return this.uninstallInServer(extension, server, force);
|
||||
@@ -134,9 +131,8 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
|
||||
async install(vsix: URI): Promise<IExtensionIdentifier> {
|
||||
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
|
||||
const syncExtensions = await this.hasToSyncExtensions();
|
||||
const manifest = await getManifest(vsix.fsPath);
|
||||
if (syncExtensions || isLanguagePackExtension(manifest)) {
|
||||
if (isLanguagePackExtension(manifest)) {
|
||||
// Install on both servers
|
||||
const [extensionIdentifier] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix)));
|
||||
return extensionIdentifier;
|
||||
@@ -156,9 +152,9 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
|
||||
async installFromGallery(gallery: IGalleryExtension): Promise<void> {
|
||||
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
|
||||
const [manifest, syncExtensions] = await Promise.all([this.extensionGalleryService.getManifest(gallery, CancellationToken.None), this.hasToSyncExtensions()]);
|
||||
const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);
|
||||
if (manifest) {
|
||||
if (syncExtensions || isLanguagePackExtension(manifest)) {
|
||||
if (isLanguagePackExtension(manifest)) {
|
||||
// Install on both servers
|
||||
return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(() => undefined);
|
||||
}
|
||||
@@ -199,17 +195,6 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
private getServer(extension: ILocalExtension): IExtensionManagementServer | null {
|
||||
return this.extensionManagementServerService.getExtensionManagementServer(extension.location);
|
||||
}
|
||||
|
||||
private async hasToSyncExtensions(): Promise<boolean> {
|
||||
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
|
||||
return false;
|
||||
}
|
||||
const remoteEnv = await this.remoteAgentService.getEnvironment();
|
||||
if (!remoteEnv) {
|
||||
return false;
|
||||
}
|
||||
return remoteEnv.syncExtensions;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IExtensionManagementService, MultiExtensionManagementService);
|
||||
|
||||
@@ -70,15 +70,15 @@ suite('RPCProtocol', () => {
|
||||
});
|
||||
|
||||
test('passing buffer as argument', function (done) {
|
||||
delegate = (a1: Buffer, a2: number) => {
|
||||
assert.ok(Buffer.isBuffer(a1));
|
||||
return a1[a2];
|
||||
delegate = (a1: VSBuffer, a2: number) => {
|
||||
assert.ok(a1 instanceof VSBuffer);
|
||||
return a1.buffer[a2];
|
||||
};
|
||||
let b = Buffer.allocUnsafe(4);
|
||||
b[0] = 1;
|
||||
b[1] = 2;
|
||||
b[2] = 3;
|
||||
b[3] = 4;
|
||||
let b = VSBuffer.alloc(4);
|
||||
b.buffer[0] = 1;
|
||||
b.buffer[1] = 2;
|
||||
b.buffer[2] = 3;
|
||||
b.buffer[3] = 4;
|
||||
bProxy.$m(b, 2).then((res: number) => {
|
||||
assert.equal(res, 3);
|
||||
done(null);
|
||||
@@ -87,19 +87,19 @@ suite('RPCProtocol', () => {
|
||||
|
||||
test('returning a buffer', function (done) {
|
||||
delegate = (a1: number, a2: number) => {
|
||||
let b = Buffer.allocUnsafe(4);
|
||||
b[0] = 1;
|
||||
b[1] = 2;
|
||||
b[2] = 3;
|
||||
b[3] = 4;
|
||||
let b = VSBuffer.alloc(4);
|
||||
b.buffer[0] = 1;
|
||||
b.buffer[1] = 2;
|
||||
b.buffer[2] = 3;
|
||||
b.buffer[3] = 4;
|
||||
return b;
|
||||
};
|
||||
bProxy.$m(4, 1).then((res: Buffer) => {
|
||||
assert.ok(Buffer.isBuffer(res));
|
||||
assert.equal(res[0], 1);
|
||||
assert.equal(res[1], 2);
|
||||
assert.equal(res[2], 3);
|
||||
assert.equal(res[3], 4);
|
||||
bProxy.$m(4, 1).then((res: VSBuffer) => {
|
||||
assert.ok(res instanceof VSBuffer);
|
||||
assert.equal(res.buffer[0], 1);
|
||||
assert.equal(res.buffer[1], 2);
|
||||
assert.equal(res.buffer[2], 3);
|
||||
assert.equal(res.buffer[3], 4);
|
||||
done(null);
|
||||
}, done);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user