Skip to content

Conversation

@theanarkh
Copy link

@theanarkh theanarkh commented Dec 30, 2025

Fixes #234393

The local pty process communicates directly with the render process, but the main process save the output of pty process to buffer and there is no consumer.

Running

node -e "setInterval(() => console.log('x'.repeat(100)), 10)"

in the terminal and take a JS heap snapshot of main process.

image

take JS heap snapshot again after some time.

image
Copilot AI review requested due to automatic review settings December 30, 2025 12:43
@vs-code-engineering
Copy link

vs-code-engineering bot commented Dec 30, 2025

📬 CODENOTIFY

The following users are being notified based on files changed in this PR:

@deepak1556

Matched files:

  • src/vs/code/electron-main/app.ts
@theanarkh
Copy link
Author

@microsoft-github-policy-service agree

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a memory leak (OOM) in the main Electron process caused by unbounded buffering of PTY process output. The local PTY process communicates directly with the renderer process via MessagePort, but the main process was also buffering all onProcessData events even though they were never consumed, leading to memory exhaustion.

Key Changes:

  • Added a skip option to ProxyChannel.fromService to selectively exclude events from registration
  • Applied the skip option to exclude onProcessData events from the main process PTY service channel

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/vs/base/parts/ipc/common/ipc.ts Added skip option to ICreateServiceChannelOptions interface to allow selective event registration filtering
src/vs/code/electron-main/app.ts Applied skip option to exclude onProcessData from the PTY host channel registration in main process
@theanarkh theanarkh force-pushed the fix_pty_output_make_main_process_oom branch 3 times, most recently from bd57057 to e2bb946 Compare December 30, 2025 13:18
@bpasero bpasero assigned Tyriar and unassigned connor4312 Dec 30, 2025
@theanarkh theanarkh force-pushed the fix_pty_output_make_main_process_oom branch from e2bb946 to 4eaa235 Compare December 30, 2025 16:39
Tyriar
Tyriar previously approved these changes Dec 31, 2025
Copy link
Member

@Tyriar Tyriar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm thanks!

FYI @joaomoreno @alexdima if you have thoughts on ipc/ parts

@Tyriar Tyriar added this to the December / January 2026 milestone Dec 31, 2025
@Tyriar Tyriar enabled auto-merge December 31, 2025 13:14
* If return true for a event key, we add the handler to event
* until the first listener is registered.
*/
isLazyEvent?: (key: string) => boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am reading this comment and fail to understand what actually happens when I use this option. If I understand correctly, a "lazy" event will not buffer any data until a first listener is added. But then I would update this comment to actually explain this better. And should be called differently?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I have updated the comment, please help review again .

// Terminal
const ptyHostChannel = ProxyChannel.fromService(accessor.get(ILocalPtyService), disposables);
const ptyHostChannel = ProxyChannel.fromService(accessor.get(ILocalPtyService), disposables, {
isLazyEvent: (key: string) => key === 'onProcessData'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whats the rule here to decide if an event should be lazy? Does this apply to other proxy channels?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing ! An event should be lazy if it is triggered automatically but may not have subscribers; otherwise, the event data will be cached in the buffer (Event.buffer), causing a memory leak. I only find that ptyHostService.onProcessData should be lazy currently.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@theanarkh I think what @bpasero is likely getting at is this should get fixed generically to prevent future leaks like this in the future. onProcessData is used, but conditionally, just because an event isn't listened to shouldn't mean the data should hang around forever. Any thoughts on how we could solve this generically for all events without hardcoding this particular event or compromising performance?

auto-merge was automatically disabled December 31, 2025 16:52

Head branch was pushed to by a user without write access

@theanarkh theanarkh force-pushed the fix_pty_output_make_main_process_oom branch from 4eaa235 to 3415bc2 Compare December 31, 2025 16:52
@Tyriar Tyriar marked this pull request as draft January 1, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

4 participants