Skip to content

fix: process leaks in extensionHostConnection#319223

Merged
connor4312 merged 5 commits into
microsoft:mainfrom
SimonSiefke:fix/memory-leak-ssh-2
Jun 1, 2026
Merged

fix: process leaks in extensionHostConnection#319223
connor4312 merged 5 commits into
microsoft:mainfrom
SimonSiefke:fix/memory-leak-ssh-2

Conversation

@SimonSiefke

@SimonSiefke SimonSiefke commented May 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #211462.

Details

When the connection closes, the extension host sends a disconnect message, and then closes the socket. The remote agent host, receives the disconnect message, starts its graceful cleanup but then the socket close event comes which immediately triggers extHostSocket.destroy() before the graceful cleanup is finished

Change

The change tries to ensure the extHostSocket gets shutdown gracefully, so that the cleanup code and still run and finish.

Test out locally (Linux)

  1. Ensure vscode repo is set up in this branch using node version from nvmrc and run npm ci
  2. Run npm run core-ci
  3. Add product.overrides.json
{
	"nameShort": "Code",
	"nameLong": "Visual Studio Code",
	"applicationName": "code",
	"extensionsGallery": {
		"nlsBaseUrl": "https://www.vscode-unpkg.net/_lp/",
		"serviceUrl": "https://marketplace.visualstudio.com/_apis/public/gallery",
		"itemUrl": "https://marketplace.visualstudio.com/items",
		"publisherUrl": "https://marketplace.visualstudio.com/publishers",
		"resourceUrlTemplate": "https://{publisher}.vscode-unpkg.net/{publisher}/{name}/{version}/{path}",
		"extensionUrlTemplate": "https://www.vscode-unpkg.net/_gallery/{publisher}/{name}/latest",
		"controlUrl": "https://main.vscode-cdn.net/extensions/marketplace.json",
		"mcpUrl": "https://main.vscode-cdn.net/mcp/servers.json",
		"accessSKUs": [
			"copilot_enterprise_seat",
			"copilot_enterprise_seat_quota",
			"copilot_enterprise_seat_multi_quota",
			"copilot_enterprise_seat_assignment",
			"copilot_enterprise_seat_assignment_quota",
			"copilot_enterprise_seat_assignment_multi_quota",
			"copilot_enterprise_trial_seat",
			"copilot_enterprise_trial_seat_quota",
			"copilot_for_business_seat",
			"copilot_for_business_seat_quota",
			"copilot_for_business_seat_multi_quota",
			"copilot_for_business_seat_assignment",
			"copilot_for_business_seat_assignment_quota",
			"copilot_for_business_seat_assignment_multi_quota",
			"copilot_for_business_trial_seat",
			"copilot_for_business_trial_seat_quota"
		]
	}
}
  1. Ensure sshd is installed ( sudo apt-get install openssh-server)
  2. Add ssh config for starting local ssh server
  3. Start ssh server
  4. Launch VScode using ./scripts/code.sh
  5. Install remote ssh extension
  6. Open a new VSCode window
  7. Open Quickpick and run Remote-SSH: Connect Current Window to Host
  8. Enter the launched ssh server address
  9. Wait for successful connection
  10. Close the second VSCode window now
  11. Use Resources App to check for still running processes
  12. Repeat steps 8-13 a few times.

If there are leaks, the number of processes increases each time as described in the issue

Kooha-2026-06-01-12-05-37.mp4

Before

When opening a new window and connecting it to SSH and then closing it 3 times, the number of processes seems to increase each time:

{
  "processCount": {
    "after": 22,
    "before": 10
  },
  "isLeak": true
}

After

When opening a new window and connecting it to SSH and then closing it 3 times, the number of processes seems to stay constant:

{
  "processCount": {
    "after": 13,
    "before": 13
  },
  "isLeak": false
}

Copilot AI review requested due to automatic review settings May 31, 2026 22:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates extension host socket disposal to perform a graceful shutdown rather than an immediate destroy, aiming to reduce abrupt termination during cleanup.

Changes:

  • Replace extHostSocket.destroy() with a conditional extHostSocket.end() on dispose.
  • Add guards to avoid ending an already-destroyed or already-ended socket.

Comment on lines +166 to +168
if (!extHostSocket.destroyed && !extHostSocket.writableEnded) {
extHostSocket.end();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agreed!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This can be a helper, something like "gracefulSocketEnd" which can potentially be reused

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Perhaps? I'm not actually sure what's the right way to gracefully close a NodeJS socket. The code with socket.end() just seemed to work.

Can socket.end leave the socket half-open forever if the other side never closes the socket? I don't know...


There seem to be other places where, on socket.end event, after 30 seconds, it destroys the socket like

endTimeoutHandle = setTimeout(() => socket.destroy(), socketEndTimeoutMs);



I just looked at the NodeJS docs, and there is also a method socket.destroySoon, which looks like even better:

Destroys the socket after all data is written. If the 'finish' event was already emitted the socket is destroyed immediately. If the socket is still writable it implicitly calls socket.end().


If I would make this PR again I'd probably choose socket.destroySoon

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Well, perhaps a follow up PR? I think a timeout race would still be useful in this case.

connor4312
connor4312 previously approved these changes Jun 1, 2026
@connor4312 connor4312 enabled auto-merge (squash) June 1, 2026 15:12
auto-merge was automatically disabled June 1, 2026 16:41

Head branch was pushed to by a user without write access

@SimonSiefke

Copy link
Copy Markdown
Contributor Author

(made another small change to try to fix windows ci)

@connor4312 connor4312 enabled auto-merge (squash) June 1, 2026 17:49
@connor4312

Copy link
Copy Markdown
Member

thank you!

@connor4312 connor4312 merged commit a24c0f1 into microsoft:main Jun 1, 2026
25 checks passed
Comment thread extensions/vscode-test-resolver/src/extension.ts

const SLOWED_DOWN_CONNECTION_DELAY = 800;

function isExpectedSocketCloseError(error: NodeJS.ErrnoException): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: isBenignSocketTeardownError name would probably be better here

@vs-code-engineering vs-code-engineering Bot added this to the 1.124.0 milestone Jun 1, 2026
@dmitrivMS

Copy link
Copy Markdown
Contributor

@SimonSiefke Thanks a lot! Please take a look at some feedback

@SimonSiefke SimonSiefke deleted the fix/memory-leak-ssh-2 branch June 3, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Number of processes increases every time remote window is reloaded

5 participants