Skip to content

fix(chromium): close file descriptor on protocol stream save error#40749

Merged
pavelfeldman merged 4 commits intomicrosoft:mainfrom
SebTardif:fix/fd-leak-save-protocol-stream
May 9, 2026
Merged

fix(chromium): close file descriptor on protocol stream save error#40749
pavelfeldman merged 4 commits intomicrosoft:mainfrom
SebTardif:fix/fd-leak-save-protocol-stream

Conversation

@SebTardif
Copy link
Copy Markdown
Contributor

@SebTardif SebTardif commented May 9, 2026

Summary

  • saveProtocolStream opens a write fd but does not close it when client.send('IO.read') throws, leaking one file descriptor per failed stream save
  • readProtocolStream has the same pattern: IO.close is not called if IO.read throws
  • Wrap read loops in try/finally to ensure both the local fd and the server-side IO handle are always closed

Failure scenario: When Chromium's IO.read fails mid-stream (e.g., browser crash, session disconnect), the file descriptor opened at fs.promises.open(path, 'w') is never closed. Each failed save leaks one OS file descriptor. Under sustained failures, this can exhaust the process file descriptor limit.

Origin: The fd-based stream saving was introduced in #24414 (2023-07-26) by Pavel Feldman. The original code assumed IO.read would always succeed through EOF.

SebTardif added 3 commits May 9, 2026 06:00
saveProtocolStream opens a write fd but does not close it when
client.send('IO.read') throws, leaking one file descriptor per
failed stream save. Wrap the read loop in try/finally to ensure
the fd is always closed.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Move IO.close into the finally block alongside fd.close so the
server-side stream handle is released even when IO.read throws.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
…olStream

- fd.close() can throw in the finally block and mask the original
  IO.read error. Add .catch(() => {}) for consistency with IO.close.
- readProtocolStream has the same IO.close leak pattern. Wrap its
  read loop in try/finally too.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
}
} finally {
await fd.close().catch(() => {});
await client.send('IO.close', { handle }).catch(() => {});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We are getting a nested throw here (IO.read throws, IO.close throws within), let's avoid that.

Keep only fd.close() in the finally block. IO.close is moved back
to after the try/finally so it only runs on the success path,
avoiding nested throws when both IO.read and IO.close fail.

readProtocolStream reverted to original since it has no fd to
protect; the try/finally was unnecessary.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@SebTardif
Copy link
Copy Markdown
Contributor Author

Moved IO.close out of the finally block so it only runs on the success path. The finally now only contains fd.close() (the actual resource that needs protection). This avoids the nested throw when both IO.read and IO.close fail.

Also reverted readProtocolStream to original since it has no fd to protect.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 9, 2026

Test results for "MCP"

2 failed
❌ [firefox] › mcp/cli-devtools.spec.ts:217 › video-start-stop @mcp-windows-latest-firefox
❌ [firefox] › mcp/cli-devtools.spec.ts:231 › video-chapter @mcp-windows-latest-firefox

7042 passed, 1068 skipped


Merge workflow run.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 9, 2026

Test results for "tests 1"

3 flaky ⚠️ [installation tests] › screencast.spec.ts:18 › screencast works `@package-installations-windows-latest`
⚠️ [chromium-library] › library/video.spec.ts:647 › screencast › should capture full viewport `@chromium-ubuntu-22.04-node22`
⚠️ [webkit-page] › page/page-set-input-files.spec.ts:38 › should upload a folder `@webkit-ubuntu-22.04-node20`

41703 passed, 850 skipped


Merge workflow run.

@pavelfeldman pavelfeldman merged commit b3c9a9b into microsoft:main May 9, 2026
44 of 45 checks passed
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.

2 participants