Fix potential spin in interop message relay - #41319
Fix potential spin in interop message relay#41319Feng Wang (chemwolf6922) wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes an infinite-spin condition in WSL interop’s control-message relay loop (ProcessInteropMessages) by handling the synchronous ReadFile success case where BytesRead == 0 (EOF). This aligns the synchronous path with the existing overlapped-completion path and prevents runaway CPU usage when the interop control socket reaches EOF.
Changes:
- Initialize
BytesReadand add a unified EOF/failed-read exit check ((!Success) || (BytesRead == 0)) that applies to both synchronous and asynchronous completion paths. - Refactor the
ERROR_IO_PENDINGhandling block so overlapped completion updatesSuccess/BytesRead, then uses the shared exit/termination logic. - Preserve existing behavior for process-exit handling (retrieving
ExitCode, resetting pseudoconsole, and breaking).
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Blue (OneBlue)
left a comment
There was a problem hiding this comment.
Change looks good, minor comments. Feel free to address in a followup
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/windows/common/interop.cpp:418
MultiHandleWaitcan deliver the process handle completion and the message-handle read completion in either order. SinceprocessExit()resetsResult->PseudoConsole, a subsequent read callback in the sameRun()iteration can still attemptResizePseudoConsole(Result->PseudoConsole.get(), ...)and throw due to a null/closed HPCON. Consider guarding the read callback after process exit so late read completions don’t touch the pseudoconsole.
DWORD exitCode = 1;
size_t pendingSize = 0;
LX_INIT_WINDOW_SIZE_CHANGED pendingMessage{};
auto processExit = [&] {
src/windows/common/interop.cpp:440
- On the EOF path (
input.empty()),WaitForSingleObjectfailure is currently surfaced asE_UNEXPECTEDviaTHROW_HR_IF, which dropsGetLastError()context. It’s better to explicitly handleWAIT_FAILEDwithTHROW_LAST_ERROR()so failures are diagnosable.
const DWORD waitStatus = WaitForSingleObject(Result->Process.get(), 0);
if (waitStatus == WAIT_OBJECT_0)
{
processExit();
}
else
{
THROW_HR_IF(E_UNEXPECTED, waitStatus != WAIT_TIMEOUT);
if (WI_IsFlagClear(Result->Flags, LX_INIT_CREATE_PROCESS_RESULT_FLAG_GUI_APPLICATION))
Summary of the Pull Request
The original logic ignores the success result of the initial
ReadFilecall. If the connection is a socket and its close is not captured byGetOverlappedResult. It could cause thisReadFilecall to return success with bytes read == 0. This will put this loop into a spin.This PR refactors the loop with
io::MultiHandleWait.PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed
This issue is triggered by a race condition. I'm not able to repro the original issue on my machine.
The normal function was tested locally.