From 72006e35969f965883bc34ad5bd9b2252596e71e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Jun 2023 07:22:30 -0700 Subject: [PATCH 1/5] Improve pty host tracing and add simulated latency/startup delay settings --- src/vs/code/electron-main/app.ts | 2 +- src/vs/platform/terminal/common/terminal.ts | 9 +- .../electron-main/electronPtyHostStarter.ts | 17 +- src/vs/platform/terminal/node/ptyHostMain.ts | 110 ++++++----- .../platform/terminal/node/ptyHostService.ts | 1 + src/vs/platform/terminal/node/ptyService.ts | 174 ++++++++++++------ .../platform/terminal/node/terminalProcess.ts | 18 +- .../electron-sandbox/localTerminalBackend.ts | 1 + 8 files changed, 218 insertions(+), 114 deletions(-) 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..99fad722035 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'; @@ -63,14 +63,19 @@ export class PtyService extends Disposable implements IPtyService { private readonly _onDidChangeProperty = this._register(new Emitter<{ id: number; property: IProcessProperty }>()); readonly onDidChangeProperty = this._onDidChangeProperty.event; + private _traceCalls: boolean = false; + 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(); + Event.runAndSubscribe(this._logService.onDidChangeLogLevel, e => this._traceCalls = (e ?? this._logService.getLevel()) === LogLevel.Trace); + this._register(toDisposable(() => { for (const pty of this._ptys.values()) { pty.shutdown(true); @@ -83,6 +88,7 @@ export class PtyService extends Disposable implements IPtyService { } async refreshIgnoreProcessNames(names: string[]): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); ignoreProcessNames.length = 0; ignoreProcessNames.push(...names); } @@ -94,10 +100,12 @@ export class PtyService extends Disposable implements IPtyService { onPtyHostRequestResolveVariables?: Event | undefined; async requestDetachInstance(workspaceId: string, instanceId: number): Promise { - return this._detachInstanceRequestStore.createRequest({ workspaceId, instanceId }); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._detachInstanceRequestStore.createRequest({ workspaceId, instanceId })); } async acceptDetachInstanceReply(requestId: number, persistentProcessId: number): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); let processDetails: IProcessDetails | undefined = undefined; const pty = this._ptys.get(persistentProcessId); if (pty) { @@ -107,6 +115,7 @@ export class PtyService extends Disposable implements IPtyService { } async freePortKillProcess(port: string): Promise<{ port: string; processId: string }> { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); const stdout = await new Promise((resolve, reject) => { exec(isWindows ? `netstat -ano | findstr "${port}"` : `lsof -nP -iTCP -sTCP:LISTEN | grep ${port}`, {}, (err, stdout) => { if (err) { @@ -126,12 +135,13 @@ export class PtyService extends Disposable implements IPtyService { } else { throw new Error(`Processes for port ${port} were not found`); } - return { port, processId }; + return this._traceOutgoingRpc({ port, processId }); } throw new Error(`Could not kill process with port ${port}`); } async serializeTerminalState(ids: number[]): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); const promises: Promise[] = []; for (const [persistentProcessId, persistentProcess] of this._ptys.entries()) { // Only serialize persistent processes that have had data written or performed a replay @@ -153,10 +163,11 @@ export class PtyService extends Disposable implements IPtyService { version: 1, state: await Promise.all(promises) }; - return JSON.stringify(serialized); + return this._traceOutgoingRpc(JSON.stringify(serialized)); } async reviveTerminalProcesses(state: ISerializedTerminalState[], dateTimeFormatLocale: string) { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); for (const terminal of state) { const restoreMessage = localize('terminal-history-restored', "History restored"); // TODO: We may at some point want to show date information in a hover via a custom sequence: @@ -190,6 +201,7 @@ export class PtyService extends Disposable implements IPtyService { } async shutdownAll(): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this.dispose(); } @@ -208,6 +220,8 @@ export class PtyService extends Disposable implements IPtyService { isReviving?: boolean, rawReviveBuffer?: string ): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + if (shellLaunchConfig.attachPersistentProcess) { throw new Error('Attempt to create a process when attach object was provided'); } @@ -236,10 +250,12 @@ export class PtyService extends Disposable implements IPtyService { } }); this._ptys.set(id, persistentProcess); - return id; + + return this._traceOutgoingRpc(id); } async attachToProcess(id: number): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); try { await this._throwIfNoPty(id).attach(); this._logService.info(`Persistent process reconnection "${id}"`); @@ -250,84 +266,102 @@ export class PtyService extends Disposable implements IPtyService { } async updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this._throwIfNoPty(id).setTitle(title, titleSource); } async updateIcon(id: number, userInitiated: boolean, icon: URI | { light: URI; dark: URI } | { id: string; color?: { id: string } }, color?: string): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this._throwIfNoPty(id).setIcon(userInitiated, icon, color); } async clearBuffer(id: number): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this._throwIfNoPty(id).clearBuffer(); } async refreshProperty(id: number, type: T): Promise { - return this._throwIfNoPty(id).refreshProperty(type); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).refreshProperty(type)); } async updateProperty(id: number, type: T, value: IProcessPropertyMap[T]): Promise { - return this._throwIfNoPty(id).updateProperty(type, value); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).updateProperty(type, value)); } async detachFromProcess(id: number, forcePersist?: boolean): Promise { - return this._throwIfNoPty(id).detach(forcePersist); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).detach(forcePersist)); } async reduceConnectionGraceTime(): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); for (const pty of this._ptys.values()) { pty.reduceGraceTime(); } } async listProcesses(): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); const persistentProcesses = Array.from(this._ptys.entries()).filter(([_, pty]) => pty.shouldPersistTerminal); this._logService.info(`Listing ${persistentProcesses.length} persistent terminals, ${this._ptys.size} total terminals`); const promises = persistentProcesses.map(async ([id, terminalProcessData]) => this._buildProcessDetails(id, terminalProcessData)); const allTerminals = await Promise.all(promises); - return allTerminals.filter(entry => entry.isOrphan); + return this._traceOutgoingRpc(allTerminals.filter(entry => entry.isOrphan)); } async start(id: number): Promise { - this._logService.trace('ptyService#start', id); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); const pty = this._ptys.get(id); - return pty ? pty.start() : { message: `Could not find pty with id "${id}"` }; + return this._traceOutgoingRpc(pty ? pty.start() : { message: `Could not find pty with id "${id}"` }); } async shutdown(id: number, immediate: boolean): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); // Don't throw if the pty is already shutdown - this._logService.trace('ptyService#shutDown', id, immediate); - return this._ptys.get(id)?.shutdown(immediate); + return this._traceOutgoingRpc(this._ptys.get(id)?.shutdown(immediate)); } async input(id: number, data: string): Promise { - return this._throwIfNoPty(id).input(data); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).input(data)); } async processBinary(id: number, data: string): Promise { - return this._throwIfNoPty(id).writeBinary(data); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).writeBinary(data)); } async resize(id: number, cols: number, rows: number): Promise { - return this._throwIfNoPty(id).resize(cols, rows); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).resize(cols, rows)); } async getInitialCwd(id: number): Promise { - return this._throwIfNoPty(id).getInitialCwd(); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).getInitialCwd()); } async getCwd(id: number): Promise { - return this._throwIfNoPty(id).getCwd(); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).getCwd()); } async acknowledgeDataEvent(id: number, charCount: number): Promise { - return this._throwIfNoPty(id).acknowledgeDataEvent(charCount); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).acknowledgeDataEvent(charCount)); } async setUnicodeVersion(id: number, version: '6' | '11'): Promise { - return this._throwIfNoPty(id).setUnicodeVersion(version); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).setUnicodeVersion(version)); } async getLatency(id: number): Promise { - return 0; + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(0); } async orphanQuestionReply(id: number): Promise { - return this._throwIfNoPty(id).orphanQuestionReply(); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(this._throwIfNoPty(id).orphanQuestionReply()); } async installAutoReply(match: string, reply: string) { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this._autoReplies.set(match, reply); // If the auto reply exists on any existing terminals it will be overridden for (const p of this._ptys.values()) { @@ -335,6 +369,7 @@ export class PtyService extends Disposable implements IPtyService { } } async uninstallAllAutoReplies() { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); for (const match of this._autoReplies.keys()) { for (const p of this._ptys.values()) { p.uninstallAutoReply(match); @@ -342,59 +377,63 @@ export class PtyService extends Disposable implements IPtyService { } } async uninstallAutoReply(match: string) { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); for (const p of this._ptys.values()) { p.uninstallAutoReply(match); } } async getDefaultSystemShell(osOverride: OperatingSystem = OS): Promise { - return getSystemShell(osOverride, process.env); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc(getSystemShell(osOverride, process.env)); } async getEnvironment(): Promise { - return { ...process.env }; + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); + return this._traceOutgoingRpc({ ...process.env }); } async getWslPath(original: string, direction: 'unix-to-win' | 'win-to-unix' | unknown): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); if (direction === 'win-to-unix') { if (!isWindows) { - return original; + return this._traceOutgoingRpc(original); } if (getWindowsBuildNumber() < 17063) { - return original.replace(/\\/g, '/'); + return this._traceOutgoingRpc(original.replace(/\\/g, '/')); } const wslExecutable = this._getWSLExecutablePath(); if (!wslExecutable) { - return original; + return this._traceOutgoingRpc(original); } - return new Promise(c => { + return this._traceOutgoingRpc(await new Promise(c => { const proc = execFile(wslExecutable, ['-e', 'wslpath', original], {}, (error, stdout, stderr) => { c(error ? original : escapeNonWindowsPath(stdout.trim())); }); proc.stdin!.end(); - }); + })); } if (direction === 'unix-to-win') { // The backend is Windows, for example a local Windows workspace with a wsl session in // the terminal. if (isWindows) { if (getWindowsBuildNumber() < 17063) { - return original; + return this._traceOutgoingRpc(original); } const wslExecutable = this._getWSLExecutablePath(); if (!wslExecutable) { - return original; + return this._traceOutgoingRpc(original); } - return new Promise(c => { + return this._traceOutgoingRpc(await new Promise(c => { const proc = execFile(wslExecutable, ['-e', 'wslpath', '-w', original], {}, (error, stdout, stderr) => { c(error ? original : stdout.trim()); }); proc.stdin!.end(); - }); + })); } } // Fallback just in case - return original; + return this._traceOutgoingRpc(original); } private _getWSLExecutablePath(): string | undefined { @@ -402,45 +441,46 @@ export class PtyService extends Disposable implements IPtyService { const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); const systemRoot = process.env['SystemRoot']; if (systemRoot) { - return join(systemRoot, is32ProcessOn64Windows ? 'Sysnative' : 'System32', useWSLexe ? 'wsl.exe' : 'bash.exe'); + return this._traceOutgoingRpc(join(systemRoot, is32ProcessOn64Windows ? 'Sysnative' : 'System32', useWSLexe ? 'wsl.exe' : 'bash.exe')); } - return undefined; + return this._traceOutgoingRpc(undefined); } async getRevivedPtyNewId(id: number): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); try { - return this._revivedPtyIdMap.get(id)?.newId; + return this._traceOutgoingRpc(this._revivedPtyIdMap.get(id)?.newId); } catch (e) { this._logService.warn(`Couldn't find terminal ID ${id}`, e.message); } - return undefined; + return this._traceOutgoingRpc(undefined); } async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise { - this._logService.trace('ptyService#setLayoutInfo', args.tabs); + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this._workspaceLayoutInfos.set(args.workspaceId, args); } async getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise { + await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); 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); - return { tabs }; + this._logService.trace('PtyService.getTerminalLayoutInfo result', tabs); + return this._traceOutgoingRpc({ tabs }); } - return undefined; + return this._traceOutgoingRpc(undefined); } private async _expandTerminalTab(tab: ITerminalTabLayoutInfoById): Promise { const expandedTerminals = (await Promise.all(tab.terminals.map(t => this._expandTerminalInstance(t)))); const filtered = expandedTerminals.filter(term => term.terminal !== null) as IRawTerminalInstanceLayoutInfo[]; - return { + return this._traceOutgoingRpc({ isActive: tab.isActive, activePersistentProcessId: tab.activePersistentProcessId, terminals: filtered - }; + }); } private async _expandTerminalInstance(t: ITerminalInstanceLayoutInfoById): Promise> { @@ -450,17 +490,17 @@ export class PtyService extends Disposable implements IPtyService { const persistentProcessId = revivedPtyId ?? t.terminal; const persistentProcess = this._throwIfNoPty(persistentProcessId); const processDetails = persistentProcess && await this._buildProcessDetails(t.terminal, persistentProcess, revivedPtyId !== undefined); - return { + return this._traceOutgoingRpc({ terminal: { ...processDetails, id: persistentProcessId }, relativeSize: t.relativeSize - }; + }); } catch (e) { this._logService.warn(`Couldn't get layout info, a terminal was probably disconnected`, e.message); // this will be filtered out and not reconnected - return { + return this._traceOutgoingRpc({ terminal: null, relativeSize: t.relativeSize - }; + }); } } @@ -468,7 +508,7 @@ export class PtyService extends Disposable implements IPtyService { // If the process was just revived, don't do the orphan check as it will // take some time const [cwd, isOrphan] = await Promise.all([persistentProcess.getCwd(), wasRevived ? true : persistentProcess.isOrphaned()]); - return { + return this._traceOutgoingRpc({ id, title: persistentProcess.title, titleSource: persistentProcess.titleSource, @@ -488,7 +528,7 @@ export class PtyService extends Disposable implements IPtyService { type: persistentProcess.shellLaunchConfig.type, hasChildProcesses: persistentProcess.hasChildProcesses, shellIntegrationNonce: persistentProcess.processLaunchOptions.options.shellIntegration.nonce - }; + }); } private _throwIfNoPty(id: number): PersistentTerminalProcess { @@ -498,6 +538,30 @@ export class PtyService extends Disposable implements IPtyService { } return pty; } + + private async _traceIncomingRpc(...args: string[]): Promise { + if (this._logService.getLevel() === LogLevel.Trace) { + const method = this._getCallingMethod(new Error().stack); + this._logService.trace(`[RPC Incoming] PtyService#${method}(${args.map(e => JSON.stringify(e)).join(', ')})`); + } + if (this._simulatedLatency) { + await timeout(this._simulatedLatency); + } + } + + private _traceOutgoingRpc(result: T): T { + if (this._logService.getLevel() === LogLevel.Trace) { + const method = this._getCallingMethod(new Error().stack); + this._logService.trace(`[RPC Outgoing] PtyService#${method} result=${JSON.stringify(result)}`); + } + return result; + } + + private _getCallingMethod(stack: string | undefined): string { + const match = stack?.split('\n')[2]?.match(/PtyService\.(?[^ ]+)/); + const method = match?.groups?.method ?? '(unknown)'; + return method; + } } const enum InteractionState { @@ -605,7 +669,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 +722,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 +735,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 +759,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); From 39c75f6a83f3e466431bf86f9b40ec9e1e1c8cdc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Jun 2023 07:48:46 -0700 Subject: [PATCH 2/5] Use a decorator instead of manual calls --- src/vs/platform/terminal/node/ptyService.ts | 252 +++++++++++--------- 1 file changed, 142 insertions(+), 110 deletions(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 99fad722035..a505a7f7d40 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -36,6 +36,49 @@ type WorkspaceId = string; let SerializeAddon: typeof XtermSerializeAddon; let Unicode11Addon: typeof XtermUnicode11Addon; +let traceLogService: ILogService | undefined; +let simulatedLatency: number = 0; +export function traceRpc(): Function { + return createDecorator((fn, key) => { + return async function (this: any, ...args: any[]) { + + if (traceLogService?.getLevel() === LogLevel.Trace) { + traceLogService?.trace(`[RPC Request] PtyService#${fn.name}(${args.map(e => JSON.stringify(e)).join(', ')})`); + } + // TODO: Use PtyService as this? + if (simulatedLatency) { + await timeout(simulatedLatency); + } + const result = await fn.apply(this, args); + if (traceLogService?.getLevel() === LogLevel.Trace) { + traceLogService?.trace(`[RPC Response] PtyService#${fn.name}`, result); + } + return result; + }; + }); +} + +function createDecorator(mapFn: (fn: Function, key: string) => Function): Function { + return (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; + } else if (typeof descriptor.get === 'function') { + fnKey = 'get'; + fn = descriptor.get; + } + + if (!fn) { + throw new Error('not supported'); + } + + descriptor[fnKey!] = mapFn(fn, key); + }; +} + export class PtyService extends Disposable implements IPtyService { declare readonly _serviceBrand: undefined; @@ -63,8 +106,6 @@ export class PtyService extends Disposable implements IPtyService { private readonly _onDidChangeProperty = this._register(new Emitter<{ id: number; property: IProcessProperty }>()); readonly onDidChangeProperty = this._onDidChangeProperty.event; - private _traceCalls: boolean = false; - constructor( private _lastPtyId: number, private readonly _logService: ILogService, @@ -74,7 +115,8 @@ export class PtyService extends Disposable implements IPtyService { ) { super(); - Event.runAndSubscribe(this._logService.onDidChangeLogLevel, e => this._traceCalls = (e ?? this._logService.getLevel()) === LogLevel.Trace); + traceLogService = this._logService; + simulatedLatency = this._simulatedLatency; this._register(toDisposable(() => { for (const pty of this._ptys.values()) { @@ -87,8 +129,8 @@ export class PtyService extends Disposable implements IPtyService { this._detachInstanceRequestStore.onCreateRequest(this._onDidRequestDetach.fire, this._onDidRequestDetach); } + @traceRpc() async refreshIgnoreProcessNames(names: string[]): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); ignoreProcessNames.length = 0; ignoreProcessNames.push(...names); } @@ -99,13 +141,13 @@ export class PtyService extends Disposable implements IPtyService { onPtyHostResponsive?: Event | undefined; onPtyHostRequestResolveVariables?: Event | undefined; + @traceRpc() async requestDetachInstance(workspaceId: string, instanceId: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._detachInstanceRequestStore.createRequest({ workspaceId, instanceId })); + return this._detachInstanceRequestStore.createRequest({ workspaceId, instanceId }); } + @traceRpc() async acceptDetachInstanceReply(requestId: number, persistentProcessId: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); let processDetails: IProcessDetails | undefined = undefined; const pty = this._ptys.get(persistentProcessId); if (pty) { @@ -114,8 +156,8 @@ export class PtyService extends Disposable implements IPtyService { this._detachInstanceRequestStore.acceptReply(requestId, processDetails); } + @traceRpc() async freePortKillProcess(port: string): Promise<{ port: string; processId: string }> { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); const stdout = await new Promise((resolve, reject) => { exec(isWindows ? `netstat -ano | findstr "${port}"` : `lsof -nP -iTCP -sTCP:LISTEN | grep ${port}`, {}, (err, stdout) => { if (err) { @@ -135,13 +177,13 @@ export class PtyService extends Disposable implements IPtyService { } else { throw new Error(`Processes for port ${port} were not found`); } - return this._traceOutgoingRpc({ port, processId }); + return { port, processId }; } throw new Error(`Could not kill process with port ${port}`); } + @traceRpc() async serializeTerminalState(ids: number[]): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); const promises: Promise[] = []; for (const [persistentProcessId, persistentProcess] of this._ptys.entries()) { // Only serialize persistent processes that have had data written or performed a replay @@ -163,11 +205,11 @@ export class PtyService extends Disposable implements IPtyService { version: 1, state: await Promise.all(promises) }; - return this._traceOutgoingRpc(JSON.stringify(serialized)); + return JSON.stringify(serialized); } + @traceRpc() async reviveTerminalProcesses(state: ISerializedTerminalState[], dateTimeFormatLocale: string) { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); for (const terminal of state) { const restoreMessage = localize('terminal-history-restored', "History restored"); // TODO: We may at some point want to show date information in a hover via a custom sequence: @@ -200,11 +242,12 @@ export class PtyService extends Disposable implements IPtyService { } } + @traceRpc() async shutdownAll(): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this.dispose(); } + @traceRpc() async createProcess( shellLaunchConfig: IShellLaunchConfig, cwd: string, @@ -220,8 +263,6 @@ export class PtyService extends Disposable implements IPtyService { isReviving?: boolean, rawReviveBuffer?: string ): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - if (shellLaunchConfig.attachPersistentProcess) { throw new Error('Attempt to create a process when attach object was provided'); } @@ -250,12 +291,11 @@ export class PtyService extends Disposable implements IPtyService { } }); this._ptys.set(id, persistentProcess); - - return this._traceOutgoingRpc(id); + return id; } + @traceRpc() async attachToProcess(id: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); try { await this._throwIfNoPty(id).attach(); this._logService.info(`Persistent process reconnection "${id}"`); @@ -265,175 +305,175 @@ export class PtyService extends Disposable implements IPtyService { } } + @traceRpc() async updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); 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 { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this._throwIfNoPty(id).setIcon(userInitiated, icon, color); } + @traceRpc() async clearBuffer(id: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this._throwIfNoPty(id).clearBuffer(); } + @traceRpc() async refreshProperty(id: number, type: T): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).refreshProperty(type)); + return this._throwIfNoPty(id).refreshProperty(type); } + @traceRpc() async updateProperty(id: number, type: T, value: IProcessPropertyMap[T]): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).updateProperty(type, value)); + return this._throwIfNoPty(id).updateProperty(type, value); } + @traceRpc() async detachFromProcess(id: number, forcePersist?: boolean): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).detach(forcePersist)); + return this._throwIfNoPty(id).detach(forcePersist); } + @traceRpc() async reduceConnectionGraceTime(): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); for (const pty of this._ptys.values()) { pty.reduceGraceTime(); } } + @traceRpc() async listProcesses(): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); const persistentProcesses = Array.from(this._ptys.entries()).filter(([_, pty]) => pty.shouldPersistTerminal); this._logService.info(`Listing ${persistentProcesses.length} persistent terminals, ${this._ptys.size} total terminals`); const promises = persistentProcesses.map(async ([id, terminalProcessData]) => this._buildProcessDetails(id, terminalProcessData)); const allTerminals = await Promise.all(promises); - return this._traceOutgoingRpc(allTerminals.filter(entry => entry.isOrphan)); + return allTerminals.filter(entry => entry.isOrphan); } + @traceRpc() async start(id: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); const pty = this._ptys.get(id); - return this._traceOutgoingRpc(pty ? pty.start() : { message: `Could not find pty with id "${id}"` }); + return pty ? pty.start() : { message: `Could not find pty with id "${id}"` }; } + @traceRpc() async shutdown(id: number, immediate: boolean): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); // Don't throw if the pty is already shutdown - return this._traceOutgoingRpc(this._ptys.get(id)?.shutdown(immediate)); + return this._ptys.get(id)?.shutdown(immediate); } + @traceRpc() async input(id: number, data: string): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).input(data)); + return this._throwIfNoPty(id).input(data); } + @traceRpc() async processBinary(id: number, data: string): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).writeBinary(data)); + return this._throwIfNoPty(id).writeBinary(data); } + @traceRpc() async resize(id: number, cols: number, rows: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).resize(cols, rows)); + return this._throwIfNoPty(id).resize(cols, rows); } + @traceRpc() async getInitialCwd(id: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).getInitialCwd()); + return this._throwIfNoPty(id).getInitialCwd(); } + @traceRpc() async getCwd(id: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).getCwd()); + return this._throwIfNoPty(id).getCwd(); } + @traceRpc() async acknowledgeDataEvent(id: number, charCount: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).acknowledgeDataEvent(charCount)); + return this._throwIfNoPty(id).acknowledgeDataEvent(charCount); } + @traceRpc() async setUnicodeVersion(id: number, version: '6' | '11'): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).setUnicodeVersion(version)); + return this._throwIfNoPty(id).setUnicodeVersion(version); } + @traceRpc() async getLatency(id: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(0); + return 0; } + @traceRpc() async orphanQuestionReply(id: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(this._throwIfNoPty(id).orphanQuestionReply()); + return this._throwIfNoPty(id).orphanQuestionReply(); } + @traceRpc() async installAutoReply(match: string, reply: string) { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this._autoReplies.set(match, reply); // If the auto reply exists on any existing terminals it will be overridden for (const p of this._ptys.values()) { p.installAutoReply(match, reply); } } + @traceRpc() async uninstallAllAutoReplies() { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); for (const match of this._autoReplies.keys()) { for (const p of this._ptys.values()) { p.uninstallAutoReply(match); } } } + @traceRpc() async uninstallAutoReply(match: string) { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); for (const p of this._ptys.values()) { p.uninstallAutoReply(match); } } + @traceRpc() async getDefaultSystemShell(osOverride: OperatingSystem = OS): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc(getSystemShell(osOverride, process.env)); + return getSystemShell(osOverride, process.env); } + @traceRpc() async getEnvironment(): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); - return this._traceOutgoingRpc({ ...process.env }); + return { ...process.env }; } + @traceRpc() async getWslPath(original: string, direction: 'unix-to-win' | 'win-to-unix' | unknown): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); if (direction === 'win-to-unix') { if (!isWindows) { - return this._traceOutgoingRpc(original); + return original; } if (getWindowsBuildNumber() < 17063) { - return this._traceOutgoingRpc(original.replace(/\\/g, '/')); + return original.replace(/\\/g, '/'); } const wslExecutable = this._getWSLExecutablePath(); if (!wslExecutable) { - return this._traceOutgoingRpc(original); + return original; } - return this._traceOutgoingRpc(await new Promise(c => { + return new Promise(c => { const proc = execFile(wslExecutable, ['-e', 'wslpath', original], {}, (error, stdout, stderr) => { c(error ? original : escapeNonWindowsPath(stdout.trim())); }); proc.stdin!.end(); - })); + }); } if (direction === 'unix-to-win') { // The backend is Windows, for example a local Windows workspace with a wsl session in // the terminal. if (isWindows) { if (getWindowsBuildNumber() < 17063) { - return this._traceOutgoingRpc(original); + return original; } const wslExecutable = this._getWSLExecutablePath(); if (!wslExecutable) { - return this._traceOutgoingRpc(original); + return original; } - return this._traceOutgoingRpc(await new Promise(c => { + return new Promise(c => { const proc = execFile(wslExecutable, ['-e', 'wslpath', '-w', original], {}, (error, stdout, stderr) => { c(error ? original : stdout.trim()); }); proc.stdin!.end(); - })); + }); } } // Fallback just in case - return this._traceOutgoingRpc(original); + return original; } private _getWSLExecutablePath(): string | undefined { @@ -441,46 +481,46 @@ export class PtyService extends Disposable implements IPtyService { const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); const systemRoot = process.env['SystemRoot']; if (systemRoot) { - return this._traceOutgoingRpc(join(systemRoot, is32ProcessOn64Windows ? 'Sysnative' : 'System32', useWSLexe ? 'wsl.exe' : 'bash.exe')); + return join(systemRoot, is32ProcessOn64Windows ? 'Sysnative' : 'System32', useWSLexe ? 'wsl.exe' : 'bash.exe'); } - return this._traceOutgoingRpc(undefined); + return undefined; } + @traceRpc() async getRevivedPtyNewId(id: number): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); try { - return this._traceOutgoingRpc(this._revivedPtyIdMap.get(id)?.newId); + return this._revivedPtyIdMap.get(id)?.newId; } catch (e) { this._logService.warn(`Couldn't find terminal ID ${id}`, e.message); } - return this._traceOutgoingRpc(undefined); + return undefined; } + @traceRpc() async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); this._workspaceLayoutInfos.set(args.workspaceId, args); } + @traceRpc() async getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise { - await this._traceIncomingRpc(...(this._traceCalls ? arguments : [])); const layout = this._workspaceLayoutInfos.get(args.workspaceId); 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.getTerminalLayoutInfo result', tabs); - return this._traceOutgoingRpc({ tabs }); + return { tabs }; } - return this._traceOutgoingRpc(undefined); + return undefined; } private async _expandTerminalTab(tab: ITerminalTabLayoutInfoById): Promise { const expandedTerminals = (await Promise.all(tab.terminals.map(t => this._expandTerminalInstance(t)))); const filtered = expandedTerminals.filter(term => term.terminal !== null) as IRawTerminalInstanceLayoutInfo[]; - return this._traceOutgoingRpc({ + return { isActive: tab.isActive, activePersistentProcessId: tab.activePersistentProcessId, terminals: filtered - }); + }; } private async _expandTerminalInstance(t: ITerminalInstanceLayoutInfoById): Promise> { @@ -490,17 +530,17 @@ export class PtyService extends Disposable implements IPtyService { const persistentProcessId = revivedPtyId ?? t.terminal; const persistentProcess = this._throwIfNoPty(persistentProcessId); const processDetails = persistentProcess && await this._buildProcessDetails(t.terminal, persistentProcess, revivedPtyId !== undefined); - return this._traceOutgoingRpc({ + return { terminal: { ...processDetails, id: persistentProcessId }, relativeSize: t.relativeSize - }); + }; } catch (e) { this._logService.warn(`Couldn't get layout info, a terminal was probably disconnected`, e.message); // this will be filtered out and not reconnected - return this._traceOutgoingRpc({ + return { terminal: null, relativeSize: t.relativeSize - }); + }; } } @@ -508,7 +548,7 @@ export class PtyService extends Disposable implements IPtyService { // If the process was just revived, don't do the orphan check as it will // take some time const [cwd, isOrphan] = await Promise.all([persistentProcess.getCwd(), wasRevived ? true : persistentProcess.isOrphaned()]); - return this._traceOutgoingRpc({ + return { id, title: persistentProcess.title, titleSource: persistentProcess.titleSource, @@ -528,7 +568,7 @@ export class PtyService extends Disposable implements IPtyService { type: persistentProcess.shellLaunchConfig.type, hasChildProcesses: persistentProcess.hasChildProcesses, shellIntegrationNonce: persistentProcess.processLaunchOptions.options.shellIntegration.nonce - }); + }; } private _throwIfNoPty(id: number): PersistentTerminalProcess { @@ -539,29 +579,21 @@ export class PtyService extends Disposable implements IPtyService { return pty; } - private async _traceIncomingRpc(...args: string[]): Promise { - if (this._logService.getLevel() === LogLevel.Trace) { - const method = this._getCallingMethod(new Error().stack); - this._logService.trace(`[RPC Incoming] PtyService#${method}(${args.map(e => JSON.stringify(e)).join(', ')})`); - } - if (this._simulatedLatency) { - await timeout(this._simulatedLatency); - } - } - - private _traceOutgoingRpc(result: T): T { - if (this._logService.getLevel() === LogLevel.Trace) { - const method = this._getCallingMethod(new Error().stack); - this._logService.trace(`[RPC Outgoing] PtyService#${method} result=${JSON.stringify(result)}`); - } - return result; - } - - private _getCallingMethod(stack: string | undefined): string { - const match = stack?.split('\n')[2]?.match(/PtyService\.(?[^ ]+)/); - const method = match?.groups?.method ?? '(unknown)'; - return method; - } + // private async _traceRpc(impl: () => T, ...args: unknown[]): Promise { + // let method: string | undefined; + // if (this._logService.getLevel() === LogLevel.Trace) { + // method = this._getCallingMethod(new Error().stack); + // this._logService.trace(`[RPC Request] PtyService#${method}(${args.map(e => JSON.stringify(e)).join(', ')})`); + // } + // if (this._simulatedLatency) { + // await timeout(this._simulatedLatency); + // } + // const result = impl(); + // if (this._logService.getLevel() === LogLevel.Trace) { + // this._logService.trace(`[RPC Response] PtyService#${method}`, result); + // } + // return result; + // } } const enum InteractionState { From d9e7901c95e3b129328737717dcfffbc331a735f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Jun 2023 07:54:37 -0700 Subject: [PATCH 3/5] Clean up rpc tracing and use safer types --- src/vs/platform/terminal/node/ptyService.ts | 78 +++++++++++---------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index a505a7f7d40..7d89ec4b1ef 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -30,54 +30,51 @@ 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'; -type WorkspaceId = string; - -let SerializeAddon: typeof XtermSerializeAddon; -let Unicode11Addon: typeof XtermUnicode11Addon; - -let traceLogService: ILogService | undefined; -let simulatedLatency: number = 0; export function traceRpc(): Function { - return createDecorator((fn, key) => { - return async function (this: any, ...args: any[]) { + function createDecorator(mapFn: (fn: Function, key: string) => Function): Function { + return (target: any, key: string, descriptor: any) => { + let fnKey: string | null = null; + let fn: Function | null = null; - if (traceLogService?.getLevel() === LogLevel.Trace) { - traceLogService?.trace(`[RPC Request] PtyService#${fn.name}(${args.map(e => JSON.stringify(e)).join(', ')})`); + if (typeof descriptor.value === 'function') { + fnKey = 'value'; + fn = descriptor.value; + } else if (typeof descriptor.get === 'function') { + fnKey = 'get'; + fn = descriptor.get; } - // TODO: Use PtyService as this? - if (simulatedLatency) { - await timeout(simulatedLatency); + + if (!fn) { + throw new Error('not supported'); + } + + descriptor[fnKey!] = mapFn(fn, key); + }; + } + return createDecorator((fn, key) => { + // The PtyService type is unsafe, this decorator should only be used on PtyService + return async function (this: PtyService, ...args: any[]) { + if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) { + this.traceRpcArgs.logService.trace(`[RPC Request] PtyService#${fn.name}(${args.map(e => JSON.stringify(e)).join(', ')})`); + } + if (this.traceRpcArgs.simulatedLatency) { + await timeout(this.traceRpcArgs.simulatedLatency); } const result = await fn.apply(this, args); - if (traceLogService?.getLevel() === LogLevel.Trace) { - traceLogService?.trace(`[RPC Response] PtyService#${fn.name}`, result); + if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) { + this.traceRpcArgs.logService.trace(`[RPC Response] PtyService#${fn.name}`, result); } return result; }; }); } -function createDecorator(mapFn: (fn: Function, key: string) => Function): Function { - return (target: any, key: string, descriptor: any) => { - let fnKey: string | null = null; - let fn: Function | null = null; +type WorkspaceId = string; - if (typeof descriptor.value === 'function') { - fnKey = 'value'; - fn = descriptor.value; - } else if (typeof descriptor.get === 'function') { - fnKey = 'get'; - fn = descriptor.get; - } - - if (!fn) { - throw new Error('not supported'); - } - - descriptor[fnKey!] = mapFn(fn, key); - }; -} +let SerializeAddon: typeof XtermSerializeAddon; +let Unicode11Addon: typeof XtermUnicode11Addon; export class PtyService extends Disposable implements IPtyService { declare readonly _serviceBrand: undefined; @@ -106,6 +103,14 @@ 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, @@ -115,9 +120,6 @@ export class PtyService extends Disposable implements IPtyService { ) { super(); - traceLogService = this._logService; - simulatedLatency = this._simulatedLatency; - this._register(toDisposable(() => { for (const pty of this._ptys.values()) { pty.shutdown(true); From 31ebf7befc6e1bd11f51691c5e02c5c3d08fd5ce Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Jun 2023 08:19:34 -0700 Subject: [PATCH 4/5] Remove need for () on traceRpc decorator --- src/vs/platform/terminal/node/ptyService.ts | 138 ++++++++++---------- 1 file changed, 67 insertions(+), 71 deletions(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 7d89ec4b1ef..95906ea10ad 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -32,43 +32,39 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { join } from 'path'; import { memoize } from 'vs/base/common/decorators'; -export function traceRpc(): Function { - function createDecorator(mapFn: (fn: Function, key: string) => Function): Function { - return (target: any, key: string, descriptor: any) => { - let fnKey: string | null = null; - let fn: Function | null = null; +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; - } else if (typeof descriptor.get === 'function') { - fnKey = 'get'; - fn = descriptor.get; - } + if (typeof descriptor.value === 'function') { + fnKey = 'value'; + fn = descriptor.value; - if (!fn) { - throw new Error('not supported'); - } - - descriptor[fnKey!] = mapFn(fn, key); - }; + if (fn!.length !== 0) { + console.warn('Memoize should only be used in functions with zero parameters'); + } + } else if (typeof descriptor.get === 'function') { + fnKey = 'get'; + fn = descriptor.get; } - return createDecorator((fn, key) => { - // The PtyService type is unsafe, this decorator should only be used on PtyService - return async function (this: PtyService, ...args: any[]) { - if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) { - this.traceRpcArgs.logService.trace(`[RPC Request] PtyService#${fn.name}(${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#${fn.name}`, result); - } - return result; - }; - }); + + 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; @@ -131,7 +127,7 @@ export class PtyService extends Disposable implements IPtyService { this._detachInstanceRequestStore.onCreateRequest(this._onDidRequestDetach.fire, this._onDidRequestDetach); } - @traceRpc() + @traceRpc async refreshIgnoreProcessNames(names: string[]): Promise { ignoreProcessNames.length = 0; ignoreProcessNames.push(...names); @@ -143,12 +139,12 @@ export class PtyService extends Disposable implements IPtyService { onPtyHostResponsive?: Event | undefined; onPtyHostRequestResolveVariables?: Event | undefined; - @traceRpc() + @traceRpc async requestDetachInstance(workspaceId: string, instanceId: number): Promise { return this._detachInstanceRequestStore.createRequest({ workspaceId, instanceId }); } - @traceRpc() + @traceRpc async acceptDetachInstanceReply(requestId: number, persistentProcessId: number): Promise { let processDetails: IProcessDetails | undefined = undefined; const pty = this._ptys.get(persistentProcessId); @@ -158,7 +154,7 @@ export class PtyService extends Disposable implements IPtyService { this._detachInstanceRequestStore.acceptReply(requestId, processDetails); } - @traceRpc() + @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) => { @@ -184,7 +180,7 @@ export class PtyService extends Disposable implements IPtyService { throw new Error(`Could not kill process with port ${port}`); } - @traceRpc() + @traceRpc async serializeTerminalState(ids: number[]): Promise { const promises: Promise[] = []; for (const [persistentProcessId, persistentProcess] of this._ptys.entries()) { @@ -210,7 +206,7 @@ export class PtyService extends Disposable implements IPtyService { return JSON.stringify(serialized); } - @traceRpc() + @traceRpc async reviveTerminalProcesses(state: ISerializedTerminalState[], dateTimeFormatLocale: string) { for (const terminal of state) { const restoreMessage = localize('terminal-history-restored', "History restored"); @@ -244,12 +240,12 @@ export class PtyService extends Disposable implements IPtyService { } } - @traceRpc() + @traceRpc async shutdownAll(): Promise { this.dispose(); } - @traceRpc() + @traceRpc async createProcess( shellLaunchConfig: IShellLaunchConfig, cwd: string, @@ -296,7 +292,7 @@ export class PtyService extends Disposable implements IPtyService { return id; } - @traceRpc() + @traceRpc async attachToProcess(id: number): Promise { try { await this._throwIfNoPty(id).attach(); @@ -307,44 +303,44 @@ export class PtyService extends Disposable implements IPtyService { } } - @traceRpc() + @traceRpc async updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise { this._throwIfNoPty(id).setTitle(title, titleSource); } - @traceRpc() + @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() + @traceRpc async clearBuffer(id: number): Promise { this._throwIfNoPty(id).clearBuffer(); } - @traceRpc() + @traceRpc async refreshProperty(id: number, type: T): Promise { return this._throwIfNoPty(id).refreshProperty(type); } - @traceRpc() + @traceRpc async updateProperty(id: number, type: T, value: IProcessPropertyMap[T]): Promise { return this._throwIfNoPty(id).updateProperty(type, value); } - @traceRpc() + @traceRpc async detachFromProcess(id: number, forcePersist?: boolean): Promise { return this._throwIfNoPty(id).detach(forcePersist); } - @traceRpc() + @traceRpc async reduceConnectionGraceTime(): Promise { for (const pty of this._ptys.values()) { pty.reduceGraceTime(); } } - @traceRpc() + @traceRpc async listProcesses(): Promise { const persistentProcesses = Array.from(this._ptys.entries()).filter(([_, pty]) => pty.shouldPersistTerminal); @@ -354,55 +350,55 @@ export class PtyService extends Disposable implements IPtyService { return allTerminals.filter(entry => entry.isOrphan); } - @traceRpc() + @traceRpc async start(id: number): Promise { const pty = this._ptys.get(id); return pty ? pty.start() : { message: `Could not find pty with id "${id}"` }; } - @traceRpc() + @traceRpc async shutdown(id: number, immediate: boolean): Promise { // Don't throw if the pty is already shutdown return this._ptys.get(id)?.shutdown(immediate); } - @traceRpc() + @traceRpc async input(id: number, data: string): Promise { return this._throwIfNoPty(id).input(data); } - @traceRpc() + @traceRpc async processBinary(id: number, data: string): Promise { return this._throwIfNoPty(id).writeBinary(data); } - @traceRpc() + @traceRpc async resize(id: number, cols: number, rows: number): Promise { return this._throwIfNoPty(id).resize(cols, rows); } - @traceRpc() + @traceRpc async getInitialCwd(id: number): Promise { return this._throwIfNoPty(id).getInitialCwd(); } - @traceRpc() + @traceRpc async getCwd(id: number): Promise { return this._throwIfNoPty(id).getCwd(); } - @traceRpc() + @traceRpc async acknowledgeDataEvent(id: number, charCount: number): Promise { return this._throwIfNoPty(id).acknowledgeDataEvent(charCount); } - @traceRpc() + @traceRpc async setUnicodeVersion(id: number, version: '6' | '11'): Promise { return this._throwIfNoPty(id).setUnicodeVersion(version); } - @traceRpc() + @traceRpc async getLatency(id: number): Promise { return 0; } - @traceRpc() + @traceRpc async orphanQuestionReply(id: number): Promise { return this._throwIfNoPty(id).orphanQuestionReply(); } - @traceRpc() + @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 @@ -410,7 +406,7 @@ export class PtyService extends Disposable implements IPtyService { p.installAutoReply(match, reply); } } - @traceRpc() + @traceRpc async uninstallAllAutoReplies() { for (const match of this._autoReplies.keys()) { for (const p of this._ptys.values()) { @@ -418,24 +414,24 @@ export class PtyService extends Disposable implements IPtyService { } } } - @traceRpc() + @traceRpc async uninstallAutoReply(match: string) { for (const p of this._ptys.values()) { p.uninstallAutoReply(match); } } - @traceRpc() + @traceRpc async getDefaultSystemShell(osOverride: OperatingSystem = OS): Promise { return getSystemShell(osOverride, process.env); } - @traceRpc() + @traceRpc async getEnvironment(): Promise { return { ...process.env }; } - @traceRpc() + @traceRpc async getWslPath(original: string, direction: 'unix-to-win' | 'win-to-unix' | unknown): Promise { if (direction === 'win-to-unix') { if (!isWindows) { @@ -488,7 +484,7 @@ export class PtyService extends Disposable implements IPtyService { return undefined; } - @traceRpc() + @traceRpc async getRevivedPtyNewId(id: number): Promise { try { return this._revivedPtyIdMap.get(id)?.newId; @@ -498,12 +494,12 @@ export class PtyService extends Disposable implements IPtyService { return undefined; } - @traceRpc() + @traceRpc async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise { this._workspaceLayoutInfos.set(args.workspaceId, args); } - @traceRpc() + @traceRpc async getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise { const layout = this._workspaceLayoutInfos.get(args.workspaceId); if (layout) { From 1fa84df68a73ada952aab5e98cc1e458db052e50 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Jun 2023 08:21:53 -0700 Subject: [PATCH 5/5] Fix compile issue, remove unneeded function --- src/vs/platform/terminal/node/ptyService.ts | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 95906ea10ad..3819f7741df 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -43,9 +43,6 @@ export function traceRpc(_target: any, key: string, descriptor: any) { if (fn!.length !== 0) { console.warn('Memoize should only be used in functions with zero parameters'); } - } else if (typeof descriptor.get === 'function') { - fnKey = 'get'; - fn = descriptor.get; } if (!fn) { @@ -59,7 +56,7 @@ export function traceRpc(_target: any, key: string, descriptor: any) { if (this.traceRpcArgs.simulatedLatency) { await timeout(this.traceRpcArgs.simulatedLatency); } - const result = await fn.apply(this, args); + const result = await fn!.apply(this, args); if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) { this.traceRpcArgs.logService.trace(`[RPC Response] PtyService#${fnKey}`, result); } @@ -576,22 +573,6 @@ export class PtyService extends Disposable implements IPtyService { } return pty; } - - // private async _traceRpc(impl: () => T, ...args: unknown[]): Promise { - // let method: string | undefined; - // if (this._logService.getLevel() === LogLevel.Trace) { - // method = this._getCallingMethod(new Error().stack); - // this._logService.trace(`[RPC Request] PtyService#${method}(${args.map(e => JSON.stringify(e)).join(', ')})`); - // } - // if (this._simulatedLatency) { - // await timeout(this._simulatedLatency); - // } - // const result = impl(); - // if (this._logService.getLevel() === LogLevel.Trace) { - // this._logService.trace(`[RPC Response] PtyService#${method}`, result); - // } - // return result; - // } } const enum InteractionState {