Funnel ext host pid to the renderer and do more logging

This commit is contained in:
Alex Dima 2023-11-04 09:51:03 +01:00
parent 98417b150a
commit 1ace1a817a
No known key found for this signature in database
GPG key ID: 4FA498B1FFF19E4D
9 changed files with 90 additions and 84 deletions

View file

@ -29,7 +29,7 @@ export interface IExtensionHostStarter {
onDynamicExit(id: string): Event<{ code: number; signal: string }>;
createExtensionHost(): Promise<{ id: string }>;
start(id: string, opts: IExtensionHostProcessOptions): Promise<void>;
start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number | undefined }>;
enableInspectPort(id: string): Promise<boolean>;
kill(id: string): Promise<void>;

View file

@ -97,11 +97,12 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
return { id };
}
async start(id: string, opts: IExtensionHostProcessOptions): Promise<void> {
async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number | undefined }> {
if (this._shutdown) {
throw canceled();
}
this._getExtHost(id).start({
const extHost = this._getExtHost(id);
extHost.start({
...opts,
type: 'extensionHost',
entryPoint: 'vs/workbench/api/node/extensionHostProcess',
@ -111,6 +112,8 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
forceAllocationsToV8Sandbox: true,
correlationId: id
});
const pid = await Event.toPromise(extHost.onSpawn);
return { pid };
}
async enableInspectPort(id: string): Promise<boolean> {

View file

@ -156,6 +156,9 @@ export class UtilityProcess extends Disposable {
private readonly _onMessage = this._register(new Emitter<unknown>());
readonly onMessage = this._onMessage.event;
private readonly _onSpawn = this._register(new Emitter<number | undefined>());
readonly onSpawn = this._onSpawn.event;
private readonly _onExit = this._register(new Emitter<IUtilityProcessExitEvent>());
readonly onExit = this._onExit.event;
@ -303,6 +306,7 @@ export class UtilityProcess extends Disposable {
}
this.log('successfully created', Severity.Info);
this._onSpawn.fire(process.pid);
}));
// Exit

View file

@ -40,6 +40,7 @@ export interface IWebWorkerExtensionHostDataProvider {
export class WebWorkerExtensionHost extends Disposable implements IExtensionHost {
public readonly pid = null;
public readonly remoteAuthority = null;
public extensions: ExtensionHostExtensions | null = null;

View file

@ -753,6 +753,11 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
const processManager: IExtensionHostManager = this._doCreateExtensionHostManager(extensionHost, initialActivationEvents);
processManager.onDidExit(([code, signal]) => this._onExtensionHostCrashOrExit(processManager, code, signal));
processManager.onDidChangeResponsiveState((responsiveState) => {
if (responsiveState === ResponsiveState.Responsive) {
this._logService.info(`Extension host (${extensionHostKindToString(processManager.kind)}) pid (${processManager.pid}) is unresponsive.`);
} else {
this._logService.info(`Extension host (${extensionHostKindToString(processManager.kind)}) pid (${processManager.pid}) is responsive.`);
}
this._onDidChangeResponsiveChange.fire({
extensionHostKind: processManager.kind,
isResponsive: responsiveState === ResponsiveState.Responsive,
@ -852,9 +857,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}
if (activatedExtensions.length > 0) {
this._logService.error(`Extension host (${extensionHostKindToString(extensionHost.kind)}) terminated unexpectedly. The following extensions were running: ${activatedExtensions.map(id => id.value).join(', ')}`);
this._logService.error(`Extension host (${extensionHostKindToString(extensionHost.kind)}) pid (${extensionHost.pid}) terminated unexpectedly. The following extensions were running: ${activatedExtensions.map(id => id.value).join(', ')}`);
} else {
this._logService.error(`Extension host (${extensionHostKindToString(extensionHost.kind)}) terminated unexpectedly. No extensions were activated.`);
this._logService.error(`Extension host (${extensionHostKindToString(extensionHost.kind)}) pid (${extensionHost.pid}) terminated unexpectedly. No extensions were activated.`);
}
}

View file

@ -35,6 +35,7 @@ const LOG_EXTENSION_HOST_COMMUNICATION = false;
const LOG_USE_COLORS = true;
export interface IExtensionHostManager {
readonly pid: number | null;
readonly kind: ExtensionHostKind;
readonly startup: ExtensionHostStartup;
readonly onDidExit: Event<[number, string | null]>;
@ -103,6 +104,10 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
private _proxy: Promise<IExtensionHostProxy | null> | null;
private _hasStarted = false;
public get pid(): number | null {
return this._extensionHost.pid;
}
public get kind(): ExtensionHostKind {
return this._extensionHost.runningLocation.kind;
}
@ -498,6 +503,13 @@ class LazyCreateExtensionHostManager extends Disposable implements IExtensionHos
private _actual: ExtensionHostManager | null;
private _lazyStartExtensions: ExtensionHostExtensions | null;
public get pid(): number | null {
if (this._actual) {
return this._actual.pid;
}
return null;
}
public get kind(): ExtensionHostKind {
return this._extensionHost.runningLocation.kind;
}

View file

@ -112,6 +112,7 @@ export const enum ExtensionHostStartup {
}
export interface IExtensionHost {
readonly pid: number | null;
readonly runningLocation: ExtensionRunningLocation;
readonly remoteAuthority: string | null;
readonly startup: ExtensionHostStartup;

View file

@ -45,6 +45,7 @@ export interface IRemoteExtensionHostDataProvider {
export class RemoteExtensionHost extends Disposable implements IExtensionHost {
public readonly pid = null;
public readonly remoteAuthority: string;
public readonly startup = ExtensionHostStartup.EagerAutoStart;
public extensions: ExtensionHostExtensions | null = null;

View file

@ -7,7 +7,7 @@ import { timeout } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import * as objects from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import { removeDangerousEnvVariables } from 'vs/base/common/processes';
@ -18,7 +18,6 @@ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { BufferedEmitter } from 'vs/base/parts/ipc/common/ipc.net';
import { acquirePort } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp';
import * as nls from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter';
import { ILabelService } from 'vs/platform/label/common/label';
@ -75,7 +74,7 @@ export class ExtensionHostProcess {
this._id = id;
}
public start(opts: IExtensionHostProcessOptions): Promise<void> {
public start(opts: IExtensionHostProcessOptions): Promise<{ pid: number | undefined }> {
return this._extensionHostStarter.start(this._id, opts);
}
@ -90,6 +89,7 @@ export class ExtensionHostProcess {
export class NativeLocalProcessExtensionHost implements IExtensionHost {
public pid: number | null = null;
public readonly remoteAuthority = null;
public extensions: ExtensionHostExtensions | null = null;
@ -98,7 +98,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost {
private readonly _onDidSetInspectPort = new Emitter<void>();
protected readonly _toDispose = new DisposableStore();
private readonly _toDispose = new DisposableStore();
private readonly _isExtensionDevHost: boolean;
private readonly _isExtensionDevDebug: boolean;
@ -124,15 +124,14 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost {
@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService,
@IUserDataProfilesService private readonly _userDataProfilesService: IUserDataProfilesService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ILogService protected readonly _logService: ILogService,
@ILoggerService protected readonly _loggerService: ILoggerService,
@ILogService private readonly _logService: ILogService,
@ILoggerService private readonly _loggerService: ILoggerService,
@ILabelService private readonly _labelService: ILabelService,
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,
@IHostService private readonly _hostService: IHostService,
@IProductService private readonly _productService: IProductService,
@IShellEnvironmentService private readonly _shellEnvironmentService: IShellEnvironmentService,
@IExtensionHostStarter protected readonly _extensionHostStarter: IExtensionHostStarter,
@IConfigurationService protected readonly _configurationService: IConfigurationService,
@IExtensionHostStarter private readonly _extensionHostStarter: IExtensionHostStarter,
) {
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
@ -182,16 +181,9 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost {
return this._messageProtocol;
}
protected async _start(): Promise<IMessagePassingProtocol> {
const communication = this._toDispose.add(new ExtHostMessagePortCommunication(this._logService));
return this._startWithCommunication(communication);
}
protected async _startWithCommunication<T>(communication: IExtHostCommunication<T>): Promise<IMessagePassingProtocol> {
const [extensionHostCreationResult, communicationPreparedData, portNumber, processEnv] = await Promise.all([
private async _start(): Promise<IMessagePassingProtocol> {
const [extensionHostCreationResult, portNumber, processEnv] = await Promise.all([
this._extensionHostStarter.createExtensionHost(),
communication.prepare(),
this._tryFindDebugPort(),
this._shellEnvironmentService.getShellEnv(),
]);
@ -322,7 +314,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost {
}
// Initialize extension host process with hand shakes
const protocol = await communication.establishProtocol(communicationPreparedData, this._extensionHostProcess, opts);
const protocol = await this._establishProtocol(this._extensionHostProcess, opts);
await this._performHandshake(protocol);
clearTimeout(startupTimeoutHandle);
return protocol;
@ -358,6 +350,54 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost {
return port || 0;
}
private _establishProtocol(extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise<IMessagePassingProtocol> {
writeExtHostConnection(new MessagePortExtHostConnection(), opts.env);
// Get ready to acquire the message port from the shared process worker
const portPromise = acquirePort(undefined /* we trigger the request via service call! */, opts.responseChannel, opts.responseNonce);
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
const handle = setTimeout(() => {
reject('The local extension host took longer than 60s to connect.');
}, 60 * 1000);
portPromise.then((port) => {
this._toDispose.add(toDisposable(() => {
// Close the message port when the extension host is disposed
port.close();
}));
clearTimeout(handle);
const onMessage = new BufferedEmitter<VSBuffer>();
port.onmessage = ((e) => onMessage.fire(VSBuffer.wrap(e.data)));
port.start();
resolve({
onMessage: onMessage.event,
send: message => port.postMessage(message.buffer),
});
});
// Now that the message port listener is installed, start the ext host process
const sw = StopWatch.create(false);
extensionHostProcess.start(opts).then(({ pid }) => {
if (pid) {
this.pid = pid;
}
this._logService.info(`Started local extension host with pid ${pid}.`);
const duration = sw.elapsed();
if (platform.isCI) {
this._logService.info(`IExtensionHostStarter.start() took ${duration} ms.`);
}
}, (err) => {
// Starting the ext host process resulted in an error
reject(err);
});
});
}
private _performHandshake(protocol: IMessagePassingProtocol): Promise<void> {
// 1) wait for the incoming `ready` event and send the initialization data.
// 2) wait for the incoming `initialized` event.
@ -540,64 +580,3 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost {
}
}
}
export interface IExtHostCommunication<T> {
prepare(): Promise<T>;
establishProtocol(prepared: T, extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise<IMessagePassingProtocol>;
}
export class ExtHostMessagePortCommunication extends Disposable implements IExtHostCommunication<void> {
constructor(
@ILogService private readonly _logService: ILogService
) {
super();
}
async prepare(): Promise<void> {
}
establishProtocol(prepared: void, extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise<IMessagePassingProtocol> {
writeExtHostConnection(new MessagePortExtHostConnection(), opts.env);
// Get ready to acquire the message port from the shared process worker
const portPromise = acquirePort(undefined /* we trigger the request via service call! */, opts.responseChannel, opts.responseNonce);
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
const handle = setTimeout(() => {
reject('The local extension host took longer than 60s to connect.');
}, 60 * 1000);
portPromise.then((port) => {
this._register(toDisposable(() => {
// Close the message port when the extension host is disposed
port.close();
}));
clearTimeout(handle);
const onMessage = new BufferedEmitter<VSBuffer>();
port.onmessage = ((e) => onMessage.fire(VSBuffer.wrap(e.data)));
port.start();
resolve({
onMessage: onMessage.event,
send: message => port.postMessage(message.buffer),
});
});
// Now that the message port listener is installed, start the ext host process
const sw = StopWatch.create(false);
extensionHostProcess.start(opts).then(() => {
const duration = sw.elapsed();
if (platform.isCI) {
this._logService.info(`IExtensionHostStarter.start() took ${duration} ms.`);
}
}, (err) => {
// Starting the ext host process resulted in an error
reject(err);
});
});
}
}