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))
|
Hi Blue (@OneBlue) . I refactored the implementation with |
| while (!remaining.empty()) | ||
| { | ||
| const size_t bytesToCopy = (std::min)(remaining.size(), sizeof(pendingMessage) - pendingSize); | ||
| std::copy_n(remaining.data(), bytesToCopy, reinterpret_cast<char*>(&pendingMessage) + pendingSize); |
There was a problem hiding this comment.
I think we can skip the copy into pendingMessage here. If we have enough bytes for a full message, we could just access it via:
const auto& message = gslhelper::get_struct<LX_INIT_WINDOW_SIZE_CHANGED>(remaining);
| } | ||
|
|
||
| WI_ASSERT((BytesRead == sizeof(WindowSizeMessage)) && (WindowSizeMessage.Header.MessageType == LxInitMessageWindowSizeChanged)); | ||
| auto remaining = input; |
There was a problem hiding this comment.
remaining should be stored outside of this lambda, since it's possible for a read to be partial, and if that happens, we want the bytes to be stored until the next call.
In an ideal world, we'd reuse ReadSocketMessageHandle, but it currently only supports sockets. Maybe we can add a flag to add support for non-socket handles, but that's outside the scope of this change
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.