diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 2a5be79b93e..470fc14ab9a 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -935,7 +935,7 @@ export class CodeApplication extends Disposable { graceTime: LocalReconnectConstants.GraceTime, shortGraceTime: LocalReconnectConstants.ShortGraceTime, scrollback: this.configurationService.getValue(TerminalSettingId.PersistentSessionScrollback) ?? 100 - }, this.environmentMainService, this.lifecycleMainService, this.logService); + }, this.configurationService, this.environmentMainService, this.lifecycleMainService, this.logService); const ptyHostService = new PtyHostService( ptyHostStarter, this.configurationService, diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index dd1d2ce8fca..3a449fbecf2 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -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 { diff --git a/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts b/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts index 72ab9b51824..20e916a7713 100644 --- a/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts +++ b/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts @@ -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) { diff --git a/src/vs/platform/terminal/node/ptyHostMain.ts b/src/vs/platform/terminal/node/ptyHostMain.ts index c2f5ee0396e..036e8784e7b 100644 --- a/src/vs/platform/terminal/node/ptyHostMain.ts +++ b/src/vs/platform/terminal/node/ptyHostMain.ts @@ -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 | 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 | 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(); -}); diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index c8d6d894035..eb27e7d58f9 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -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(client.getChannel(TerminalIpcChannels.Heartbeat)); heartbeatService.onBeat(() => this._handleHeartbeat()); + // TODO: Starting the heartbeat tracking now causes problems this._handleHeartbeat(); // Handle exit diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 6ad3aa2e0b1..3819f7741df 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -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 }>()); 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 { ignoreProcessNames.length = 0; ignoreProcessNames.push(...names); @@ -93,10 +136,12 @@ export class PtyService extends Disposable implements IPtyService { onPtyHostResponsive?: Event | undefined; onPtyHostRequestResolveVariables?: Event | undefined; + @traceRpc async requestDetachInstance(workspaceId: string, instanceId: number): Promise { return this._detachInstanceRequestStore.createRequest({ workspaceId, instanceId }); } + @traceRpc async acceptDetachInstanceReply(requestId: number, persistentProcessId: number): Promise { 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((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 { const promises: Promise[] = []; 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 { 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 { 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 { 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 { this._throwIfNoPty(id).setIcon(userInitiated, icon, color); } + @traceRpc async clearBuffer(id: number): Promise { this._throwIfNoPty(id).clearBuffer(); } + @traceRpc async refreshProperty(id: number, type: T): Promise { return this._throwIfNoPty(id).refreshProperty(type); } + @traceRpc async updateProperty(id: number, type: T, value: IProcessPropertyMap[T]): Promise { return this._throwIfNoPty(id).updateProperty(type, value); } + @traceRpc async detachFromProcess(id: number, forcePersist?: boolean): Promise { return this._throwIfNoPty(id).detach(forcePersist); } + @traceRpc async reduceConnectionGraceTime(): Promise { for (const pty of this._ptys.values()) { pty.reduceGraceTime(); } } + @traceRpc async listProcesses(): Promise { 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 { - 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 { // 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 { return this._throwIfNoPty(id).input(data); } + @traceRpc async processBinary(id: number, data: string): Promise { return this._throwIfNoPty(id).writeBinary(data); } + @traceRpc async resize(id: number, cols: number, rows: number): Promise { return this._throwIfNoPty(id).resize(cols, rows); } + @traceRpc async getInitialCwd(id: number): Promise { return this._throwIfNoPty(id).getInitialCwd(); } + @traceRpc async getCwd(id: number): Promise { return this._throwIfNoPty(id).getCwd(); } + @traceRpc async acknowledgeDataEvent(id: number, charCount: number): Promise { return this._throwIfNoPty(id).acknowledgeDataEvent(charCount); } + @traceRpc async setUnicodeVersion(id: number, version: '6' | '11'): Promise { return this._throwIfNoPty(id).setUnicodeVersion(version); } + @traceRpc async getLatency(id: number): Promise { return 0; } + @traceRpc async orphanQuestionReply(id: number): Promise { 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 { return getSystemShell(osOverride, process.env); } + @traceRpc async getEnvironment(): Promise { return { ...process.env }; } + @traceRpc async getWslPath(original: string, direction: 'unix-to-win' | 'win-to-unix' | unknown): Promise { 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 { 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 { - this._logService.trace('ptyService#setLayoutInfo', args.tabs); this._workspaceLayoutInfos.set(args.workspaceId, args); } + @traceRpc async getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise { 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 { - 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 { - 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 { - this._logService.trace('persistentTerminalProcess#start', this._persistentProcessId, this._isStarted); if (!this._isStarted) { const result = await this._terminalProcess.start(); if (result && 'message' in result) { diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 3c0275c37bc..84fbbc9d28e 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -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 { 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) { diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts index 55ca8432399..f13bc750222 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts @@ -183,6 +183,7 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke shouldPersist: boolean ): Promise { 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);