Merge pull request #185243 from microsoft/tyriar/ptyhost_trace

Improve pty host diagnostics and introduce hidden simulated latency settings
This commit is contained in:
Daniel Imms
2023-06-15 08:39:06 -07:00
committed by GitHub
8 changed files with 184 additions and 69 deletions
+1 -1
View File
@@ -935,7 +935,7 @@ export class CodeApplication extends Disposable {
graceTime: LocalReconnectConstants.GraceTime,
shortGraceTime: LocalReconnectConstants.ShortGraceTime,
scrollback: this.configurationService.getValue<number>(TerminalSettingId.PersistentSessionScrollback) ?? 100
}, this.environmentMainService, this.lifecycleMainService, this.logService);
}, this.configurationService, this.environmentMainService, this.lifecycleMainService, this.logService);
const ptyHostService = new PtyHostService(
ptyHostStarter,
this.configurationService,
+8 -1
View File
@@ -107,7 +107,14 @@ export const enum TerminalSettingId {
ShellIntegrationCommandHistory = 'terminal.integrated.shellIntegration.history',
ShellIntegrationSuggestEnabled = 'terminal.integrated.shellIntegration.suggestEnabled',
EnableImages = 'terminal.integrated.enableImages',
SmoothScrolling = 'terminal.integrated.smoothScrolling'
SmoothScrolling = 'terminal.integrated.smoothScrolling',
// Debug settings that are hidden from user
/** Simulated latency applied to all calls made to the pty host */
DeveloperPtyHostLatency = 'terminal.integrated.developer.ptyHost.latency',
/** Simulated startup delay of the pty host process */
DeveloperPtyHostStartupDelay = 'terminal.integrated.developer.ptyHost.startupDelay',
}
export const enum PosixShellType {
@@ -8,7 +8,7 @@ import { parsePtyHostDebugPort } from 'vs/platform/environment/node/environmentS
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IReconnectConstants } from 'vs/platform/terminal/common/terminal';
import { IReconnectConstants, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { IPtyHostConnection, IPtyHostStarter } from 'vs/platform/terminal/node/ptyHost';
import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp';
@@ -17,6 +17,7 @@ import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { deepClone } from 'vs/base/common/objects';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class ElectronPtyHostStarter implements IPtyHostStarter {
@@ -29,6 +30,7 @@ export class ElectronPtyHostStarter implements IPtyHostStarter {
constructor(
private readonly _reconnectConstants: IReconnectConstants,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IEnvironmentService private readonly _environmentService: INativeEnvironmentService,
@ILifecycleMainService private readonly _lifecycleMainService: ILifecycleMainService,
@ILogService private readonly _logService: ILogService
@@ -73,7 +75,7 @@ export class ElectronPtyHostStarter implements IPtyHostStarter {
}
private _createPtyHostConfiguration(lastPtyId: number) {
return {
const config: { [key: string]: string } = {
...deepClone(process.env),
VSCODE_LAST_PTY_ID: String(lastPtyId),
VSCODE_AMD_ENTRYPOINT: 'vs/platform/terminal/node/ptyHostMain',
@@ -81,8 +83,17 @@ export class ElectronPtyHostStarter implements IPtyHostStarter {
VSCODE_VERBOSE_LOGGING: 'true', // transmit console logs from server to client,
VSCODE_RECONNECT_GRACE_TIME: String(this._reconnectConstants.graceTime),
VSCODE_RECONNECT_SHORT_GRACE_TIME: String(this._reconnectConstants.shortGraceTime),
VSCODE_RECONNECT_SCROLLBACK: String(this._reconnectConstants.scrollback)
VSCODE_RECONNECT_SCROLLBACK: String(this._reconnectConstants.scrollback),
};
const simulatedLatency = this._configurationService.getValue(TerminalSettingId.DeveloperPtyHostLatency);
if (simulatedLatency && typeof simulatedLatency === 'number') {
config.VSCODE_LATENCY = String(simulatedLatency);
}
const startupDelay = this._configurationService.getValue(TerminalSettingId.DeveloperPtyHostStartupDelay);
if (startupDelay && typeof startupDelay === 'number') {
config.VSCODE_STARTUP_DELAY = String(startupDelay);
}
return config;
}
private _onWindowConnection(e: IpcMainEvent, nonce: string) {
+67 -43
View File
@@ -20,49 +20,73 @@ import { IReconnectConstants, TerminalIpcChannels } from 'vs/platform/terminal/c
import { HeartbeatService } from 'vs/platform/terminal/node/heartbeatService';
import { PtyService } from 'vs/platform/terminal/node/ptyService';
import { isUtilityProcess } from 'vs/base/parts/sandbox/node/electronTypes';
import { timeout } from 'vs/base/common/async';
const _isUtilityProcess = isUtilityProcess(process);
startPtyHost();
let server: ChildProcessServer<string> | UtilityProcessServer;
if (_isUtilityProcess) {
server = new UtilityProcessServer();
} else {
server = new ChildProcessServer(TerminalIpcChannels.PtyHost);
async function startPtyHost() {
// Parse environment variables
const startupDelay = parseInt(process.env.VSCODE_STARTUP_DELAY ?? '0');
const simulatedLatency = parseInt(process.env.VSCODE_LATENCY ?? '0');
const reconnectConstants: IReconnectConstants = {
graceTime: parseInt(process.env.VSCODE_RECONNECT_GRACE_TIME || '0'),
shortGraceTime: parseInt(process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME || '0'),
scrollback: parseInt(process.env.VSCODE_RECONNECT_SCROLLBACK || '100')
};
const lastPtyId = parseInt(process.env.VSCODE_LAST_PTY_ID || '0');
// Sanitize environment
delete process.env.VSCODE_RECONNECT_GRACE_TIME;
delete process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME;
delete process.env.VSCODE_RECONNECT_SCROLLBACK;
delete process.env.VSCODE_LATENCY;
delete process.env.VSCODE_STARTUP_DELAY;
delete process.env.VSCODE_LAST_PTY_ID;
// Setup RPC
const _isUtilityProcess = isUtilityProcess(process);
let server: ChildProcessServer<string> | UtilityProcessServer;
if (_isUtilityProcess) {
server = new UtilityProcessServer();
} else {
server = new ChildProcessServer(TerminalIpcChannels.PtyHost);
}
// Services
const productService: IProductService = { _serviceBrand: undefined, ...product };
const environmentService = new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService);
const loggerService = new LoggerService(getLogLevel(environmentService), environmentService.logsHome);
server.registerChannel(TerminalIpcChannels.Logger, new LoggerChannel(loggerService, () => DefaultURITransformer));
const logger = loggerService.createLogger('ptyhost', { name: localize('ptyHost', "Pty Host") });
const logService = new LogService(logger, [new ConsoleLogger()]);
// Log and apply developer config
if (startupDelay) {
logService.warn(`Pty Host startup is delayed ${startupDelay}ms`);
await timeout(startupDelay);
}
if (simulatedLatency) {
logService.warn(`Pty host is simulating ${simulatedLatency}ms latency`);
}
// Heartbeat responsiveness tracking
const heartbeatService = new HeartbeatService();
server.registerChannel(TerminalIpcChannels.Heartbeat, ProxyChannel.fromService(heartbeatService));
// Init pty service
const ptyService = new PtyService(lastPtyId, logService, productService, reconnectConstants, simulatedLatency);
const ptyServiceChannel = ProxyChannel.fromService(ptyService);
server.registerChannel(TerminalIpcChannels.PtyHost, ptyServiceChannel);
// Register a channel for direct communication via Message Port
if (_isUtilityProcess) {
server.registerChannel(TerminalIpcChannels.PtyHostWindow, ptyServiceChannel);
}
// Clean up
process.once('exit', () => {
logService.dispose();
heartbeatService.dispose();
ptyService.dispose();
});
}
const lastPtyId = parseInt(process.env.VSCODE_LAST_PTY_ID || '0');
delete process.env.VSCODE_LAST_PTY_ID;
const productService: IProductService = { _serviceBrand: undefined, ...product };
const environmentService = new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService);
// Logging
const loggerService = new LoggerService(getLogLevel(environmentService), environmentService.logsHome);
server.registerChannel(TerminalIpcChannels.Logger, new LoggerChannel(loggerService, () => DefaultURITransformer));
const logger = loggerService.createLogger('ptyhost', { name: localize('ptyHost', "Pty Host") });
const logService = new LogService(logger, [new ConsoleLogger()]);
const heartbeatService = new HeartbeatService();
server.registerChannel(TerminalIpcChannels.Heartbeat, ProxyChannel.fromService(heartbeatService));
const reconnectConstants: IReconnectConstants = {
graceTime: parseInt(process.env.VSCODE_RECONNECT_GRACE_TIME || '0'),
shortGraceTime: parseInt(process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME || '0'),
scrollback: parseInt(process.env.VSCODE_RECONNECT_SCROLLBACK || '100')
};
delete process.env.VSCODE_RECONNECT_GRACE_TIME;
delete process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME;
delete process.env.VSCODE_RECONNECT_SCROLLBACK;
const ptyService = new PtyService(lastPtyId, logService, productService, reconnectConstants);
const ptyServiceChannel = ProxyChannel.fromService(ptyService);
server.registerChannel(TerminalIpcChannels.PtyHost, ptyServiceChannel);
if (_isUtilityProcess) {
server.registerChannel(TerminalIpcChannels.PtyHostWindow, ptyServiceChannel);
}
process.once('exit', () => {
logService.dispose();
heartbeatService.dispose();
ptyService.dispose();
});
@@ -149,6 +149,7 @@ export class PtyHostService extends Disposable implements IPtyService {
// Setup heartbeat service and trigger a heartbeat immediately to reset the timeouts
const heartbeatService = ProxyChannel.toService<IHeartbeatService>(client.getChannel(TerminalIpcChannels.Heartbeat));
heartbeatService.onBeat(() => this._handleHeartbeat());
// TODO: Starting the heartbeat tracking now causes problems
this._handleHeartbeat();
// Handle exit
+83 -12
View File
@@ -4,13 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { execFile, exec } from 'child_process';
import { AutoOpenBarrier, ProcessTimeRunOnceScheduler, Promises, Queue } from 'vs/base/common/async';
import { AutoOpenBarrier, ProcessTimeRunOnceScheduler, Promises, Queue, timeout } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IProcessEnvironment, isWindows, OperatingSystem, OS } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { getSystemShell } from 'vs/base/node/shell';
import { ILogService } from 'vs/platform/log/common/log';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { RequestStore } from 'vs/platform/terminal/common/requestStore';
import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, IProcessProperty, TitleEventSource, ProcessPropertyType, IProcessPropertyMap, IFixedTerminalDimensions, IPersistentTerminalProcessLaunchConfig, ICrossVersionSerializedTerminalState, ISerializedTerminalState, ITerminalProcessOptions } from 'vs/platform/terminal/common/terminal';
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
@@ -30,6 +30,39 @@ import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalSt
import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/capabilities/capabilities';
import { IProductService } from 'vs/platform/product/common/productService';
import { join } from 'path';
import { memoize } from 'vs/base/common/decorators';
export function traceRpc(_target: any, key: string, descriptor: any) {
let fnKey: string | null = null;
let fn: Function | null = null;
if (typeof descriptor.value === 'function') {
fnKey = 'value';
fn = descriptor.value;
if (fn!.length !== 0) {
console.warn('Memoize should only be used in functions with zero parameters');
}
}
if (!fn) {
throw new Error('not supported');
}
descriptor[fnKey!] = async function (...args: any[]) {
if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) {
this.traceRpcArgs.logService.trace(`[RPC Request] PtyService#${fnKey}(${args.map(e => JSON.stringify(e)).join(', ')})`);
}
if (this.traceRpcArgs.simulatedLatency) {
await timeout(this.traceRpcArgs.simulatedLatency);
}
const result = await fn!.apply(this, args);
if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) {
this.traceRpcArgs.logService.trace(`[RPC Response] PtyService#${fnKey}`, result);
}
return result;
};
}
type WorkspaceId = string;
@@ -63,11 +96,20 @@ export class PtyService extends Disposable implements IPtyService {
private readonly _onDidChangeProperty = this._register(new Emitter<{ id: number; property: IProcessProperty<any> }>());
readonly onDidChangeProperty = this._onDidChangeProperty.event;
@memoize
get traceRpcArgs(): { logService: ILogService; simulatedLatency: number } {
return {
logService: this._logService,
simulatedLatency: this._simulatedLatency
};
}
constructor(
private _lastPtyId: number,
private readonly _logService: ILogService,
private readonly _productService: IProductService,
private readonly _reconnectConstants: IReconnectConstants
private readonly _reconnectConstants: IReconnectConstants,
private readonly _simulatedLatency: number
) {
super();
@@ -82,6 +124,7 @@ export class PtyService extends Disposable implements IPtyService {
this._detachInstanceRequestStore.onCreateRequest(this._onDidRequestDetach.fire, this._onDidRequestDetach);
}
@traceRpc
async refreshIgnoreProcessNames(names: string[]): Promise<void> {
ignoreProcessNames.length = 0;
ignoreProcessNames.push(...names);
@@ -93,10 +136,12 @@ export class PtyService extends Disposable implements IPtyService {
onPtyHostResponsive?: Event<void> | undefined;
onPtyHostRequestResolveVariables?: Event<IRequestResolveVariablesEvent> | undefined;
@traceRpc
async requestDetachInstance(workspaceId: string, instanceId: number): Promise<IProcessDetails | undefined> {
return this._detachInstanceRequestStore.createRequest({ workspaceId, instanceId });
}
@traceRpc
async acceptDetachInstanceReply(requestId: number, persistentProcessId: number): Promise<void> {
let processDetails: IProcessDetails | undefined = undefined;
const pty = this._ptys.get(persistentProcessId);
@@ -106,6 +151,7 @@ export class PtyService extends Disposable implements IPtyService {
this._detachInstanceRequestStore.acceptReply(requestId, processDetails);
}
@traceRpc
async freePortKillProcess(port: string): Promise<{ port: string; processId: string }> {
const stdout = await new Promise<string>((resolve, reject) => {
exec(isWindows ? `netstat -ano | findstr "${port}"` : `lsof -nP -iTCP -sTCP:LISTEN | grep ${port}`, {}, (err, stdout) => {
@@ -131,6 +177,7 @@ export class PtyService extends Disposable implements IPtyService {
throw new Error(`Could not kill process with port ${port}`);
}
@traceRpc
async serializeTerminalState(ids: number[]): Promise<string> {
const promises: Promise<ISerializedTerminalState>[] = [];
for (const [persistentProcessId, persistentProcess] of this._ptys.entries()) {
@@ -156,6 +203,7 @@ export class PtyService extends Disposable implements IPtyService {
return JSON.stringify(serialized);
}
@traceRpc
async reviveTerminalProcesses(state: ISerializedTerminalState[], dateTimeFormatLocale: string) {
for (const terminal of state) {
const restoreMessage = localize('terminal-history-restored', "History restored");
@@ -189,10 +237,12 @@ export class PtyService extends Disposable implements IPtyService {
}
}
@traceRpc
async shutdownAll(): Promise<void> {
this.dispose();
}
@traceRpc
async createProcess(
shellLaunchConfig: IShellLaunchConfig,
cwd: string,
@@ -239,6 +289,7 @@ export class PtyService extends Disposable implements IPtyService {
return id;
}
@traceRpc
async attachToProcess(id: number): Promise<void> {
try {
await this._throwIfNoPty(id).attach();
@@ -249,36 +300,44 @@ export class PtyService extends Disposable implements IPtyService {
}
}
@traceRpc
async updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise<void> {
this._throwIfNoPty(id).setTitle(title, titleSource);
}
@traceRpc
async updateIcon(id: number, userInitiated: boolean, icon: URI | { light: URI; dark: URI } | { id: string; color?: { id: string } }, color?: string): Promise<void> {
this._throwIfNoPty(id).setIcon(userInitiated, icon, color);
}
@traceRpc
async clearBuffer(id: number): Promise<void> {
this._throwIfNoPty(id).clearBuffer();
}
@traceRpc
async refreshProperty<T extends ProcessPropertyType>(id: number, type: T): Promise<IProcessPropertyMap[T]> {
return this._throwIfNoPty(id).refreshProperty(type);
}
@traceRpc
async updateProperty<T extends ProcessPropertyType>(id: number, type: T, value: IProcessPropertyMap[T]): Promise<void> {
return this._throwIfNoPty(id).updateProperty(type, value);
}
@traceRpc
async detachFromProcess(id: number, forcePersist?: boolean): Promise<void> {
return this._throwIfNoPty(id).detach(forcePersist);
}
@traceRpc
async reduceConnectionGraceTime(): Promise<void> {
for (const pty of this._ptys.values()) {
pty.reduceGraceTime();
}
}
@traceRpc
async listProcesses(): Promise<IProcessDetails[]> {
const persistentProcesses = Array.from(this._ptys.entries()).filter(([_, pty]) => pty.shouldPersistTerminal);
@@ -288,45 +347,55 @@ export class PtyService extends Disposable implements IPtyService {
return allTerminals.filter(entry => entry.isOrphan);
}
@traceRpc
async start(id: number): Promise<ITerminalLaunchError | { injectedArgs: string[] } | undefined> {
this._logService.trace('ptyService#start', id);
const pty = this._ptys.get(id);
return pty ? pty.start() : { message: `Could not find pty with id "${id}"` };
}
@traceRpc
async shutdown(id: number, immediate: boolean): Promise<void> {
// Don't throw if the pty is already shutdown
this._logService.trace('ptyService#shutDown', id, immediate);
return this._ptys.get(id)?.shutdown(immediate);
}
@traceRpc
async input(id: number, data: string): Promise<void> {
return this._throwIfNoPty(id).input(data);
}
@traceRpc
async processBinary(id: number, data: string): Promise<void> {
return this._throwIfNoPty(id).writeBinary(data);
}
@traceRpc
async resize(id: number, cols: number, rows: number): Promise<void> {
return this._throwIfNoPty(id).resize(cols, rows);
}
@traceRpc
async getInitialCwd(id: number): Promise<string> {
return this._throwIfNoPty(id).getInitialCwd();
}
@traceRpc
async getCwd(id: number): Promise<string> {
return this._throwIfNoPty(id).getCwd();
}
@traceRpc
async acknowledgeDataEvent(id: number, charCount: number): Promise<void> {
return this._throwIfNoPty(id).acknowledgeDataEvent(charCount);
}
@traceRpc
async setUnicodeVersion(id: number, version: '6' | '11'): Promise<void> {
return this._throwIfNoPty(id).setUnicodeVersion(version);
}
@traceRpc
async getLatency(id: number): Promise<number> {
return 0;
}
@traceRpc
async orphanQuestionReply(id: number): Promise<void> {
return this._throwIfNoPty(id).orphanQuestionReply();
}
@traceRpc
async installAutoReply(match: string, reply: string) {
this._autoReplies.set(match, reply);
// If the auto reply exists on any existing terminals it will be overridden
@@ -334,6 +403,7 @@ export class PtyService extends Disposable implements IPtyService {
p.installAutoReply(match, reply);
}
}
@traceRpc
async uninstallAllAutoReplies() {
for (const match of this._autoReplies.keys()) {
for (const p of this._ptys.values()) {
@@ -341,20 +411,24 @@ export class PtyService extends Disposable implements IPtyService {
}
}
}
@traceRpc
async uninstallAutoReply(match: string) {
for (const p of this._ptys.values()) {
p.uninstallAutoReply(match);
}
}
@traceRpc
async getDefaultSystemShell(osOverride: OperatingSystem = OS): Promise<string> {
return getSystemShell(osOverride, process.env);
}
@traceRpc
async getEnvironment(): Promise<IProcessEnvironment> {
return { ...process.env };
}
@traceRpc
async getWslPath(original: string, direction: 'unix-to-win' | 'win-to-unix' | unknown): Promise<string> {
if (direction === 'win-to-unix') {
if (!isWindows) {
@@ -407,6 +481,7 @@ export class PtyService extends Disposable implements IPtyService {
return undefined;
}
@traceRpc
async getRevivedPtyNewId(id: number): Promise<number | undefined> {
try {
return this._revivedPtyIdMap.get(id)?.newId;
@@ -416,18 +491,18 @@ export class PtyService extends Disposable implements IPtyService {
return undefined;
}
@traceRpc
async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void> {
this._logService.trace('ptyService#setLayoutInfo', args.tabs);
this._workspaceLayoutInfos.set(args.workspaceId, args);
}
@traceRpc
async getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise<ITerminalsLayoutInfo | undefined> {
const layout = this._workspaceLayoutInfos.get(args.workspaceId);
this._logService.trace('ptyService#getLayoutInfo', args);
if (layout) {
const expandedTabs = await Promise.all(layout.tabs.map(async tab => this._expandTerminalTab(tab)));
const tabs = expandedTabs.filter(t => t.terminals.length > 0);
this._logService.trace('ptyService#returnLayoutInfo', tabs);
this._logService.trace('PtyService.getTerminalLayoutInfo result', tabs);
return { tabs };
}
return undefined;
@@ -605,7 +680,6 @@ class PersistentTerminalProcess extends Disposable {
fixedDimensions?: IFixedTerminalDimensions
) {
super();
this._logService.trace('persistentTerminalProcess#ctor', _persistentProcessId, arguments);
this._interactionState = new MutationLogger(`Persistent process "${this._persistentProcessId}" interaction state`, InteractionState.None, this._logService);
this._wasRevived = reviveBuffer !== undefined;
this._serializer = new XtermSerializer(
@@ -659,7 +733,6 @@ class PersistentTerminalProcess extends Disposable {
}
async attach(): Promise<void> {
this._logService.trace('persistentTerminalProcess#attach', this._persistentProcessId);
// Something wrong happened if the disconnect runner is not canceled, this likely means
// multiple windows attempted to attach.
if (!await this._isOrphaned()) {
@@ -673,7 +746,6 @@ class PersistentTerminalProcess extends Disposable {
}
async detach(forcePersist?: boolean): Promise<void> {
this._logService.trace('persistentTerminalProcess#detach', this._persistentProcessId, forcePersist);
// Keep the process around if it was indicated to persist and it has had some iteraction or
// was replayed
if (this.shouldPersistTerminal && (this._interactionState.value !== InteractionState.None || forcePersist)) {
@@ -698,7 +770,6 @@ class PersistentTerminalProcess extends Disposable {
}
async start(): Promise<ITerminalLaunchError | { injectedArgs: string[] } | undefined> {
this._logService.trace('persistentTerminalProcess#start', this._persistentProcessId, this._isStarted);
if (!this._isStarted) {
const result = await this._terminalProcess.start();
if (result && 'message' in result) {
@@ -236,7 +236,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
}
return undefined;
} catch (err) {
this._logService.trace('IPty#spawn native exception', err);
this._logService.trace('node-pty.node-pty.IPty#spawn native exception', err);
return { message: `A native exception occurred during launch (${err.message})` };
}
}
@@ -294,7 +294,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
): Promise<void> {
const args = shellIntegrationInjection?.newArgs || shellLaunchConfig.args || [];
await this._throttleKillSpawn();
this._logService.trace('IPty#spawn', shellLaunchConfig.executable, args, options);
this._logService.trace('node-pty.IPty#spawn', shellLaunchConfig.executable, args, options);
const ptyProcess = spawn(shellLaunchConfig.executable!, args, options);
this._ptyProcess = ptyProcess;
this._childProcessMonitor = this._register(new ChildProcessMonitor(ptyProcess.pid, this._logService));
@@ -312,7 +312,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
}
// Refire the data event
this._logService.trace('IPty#onData', data);
this._logService.trace('node-pty.IPty#onData', data);
this._onProcessData.fire(data);
if (this._closeTimeout) {
this._queueProcessExit();
@@ -374,7 +374,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
try {
if (this._ptyProcess) {
await this._throttleKillSpawn();
this._logService.trace('IPty#kill');
this._logService.trace('node-pty.IPty#kill');
this._ptyProcess.kill();
}
} catch (ex) {
@@ -508,7 +508,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
private _doWrite(): void {
const object = this._writeQueue.shift()!;
this._logService.trace('IPty#write', object.data);
this._logService.trace('node-pty.IPty#write', object.data);
if (object.isBinary) {
this._ptyProcess!.write(Buffer.from(object.data, 'binary') as any);
} else {
@@ -537,12 +537,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
return;
}
this._logService.trace('IPty#resize', cols, rows);
this._logService.trace('node-pty.IPty#resize', cols, rows);
try {
this._ptyProcess.resize(cols, rows);
} catch (e) {
// Swallow error if the pty has already exited
this._logService.trace('IPty#resize exception ' + e.message);
this._logService.trace('node-pty.IPty#resize exception ' + e.message);
if (this._exitCode !== undefined &&
e.message !== 'ioctl(2) failed, EBADF' &&
e.message !== 'Cannot resize a pty that has already exited') {
@@ -594,7 +594,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
resolve(this._initialCwd);
return;
}
this._logService.trace('IPty#pid');
this._logService.trace('node-pty.IPty#pid');
exec('lsof -OPln -p ' + this._ptyProcess.pid + ' | grep cwd', { env: { ...process.env, LANG: 'en_US.UTF-8' } }, (error, stdout, stderr) => {
if (!error && stdout !== '') {
resolve(stdout.substring(stdout.indexOf('/'), stdout.length - 1));
@@ -610,7 +610,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
if (!this._ptyProcess) {
return this._initialCwd;
}
this._logService.trace('IPty#pid');
this._logService.trace('node-pty.IPty#pid');
try {
return await Promises.readlink(`/proc/${this._ptyProcess.pid}/cwd`);
} catch (error) {
@@ -183,6 +183,7 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke
shouldPersist: boolean
): Promise<ITerminalChildProcess> {
const executableEnv = await this._shellEnvironmentService.getShellEnv();
// TODO: Using _proxy here bypasses the lastPtyId tracking on the main process
const id = await this._proxy.createProcess(shellLaunchConfig, cwd, cols, rows, unicodeVersion, env, executableEnv, options, shouldPersist, this._getWorkspaceId(), this._getWorkspaceName());
const pty = this._instantiationService.createInstance(LocalPty, id, shouldPersist);
this._ptys.set(id, pty);