Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions mypy/ipc.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,16 +421,36 @@ def ready_to_read(conns: Sequence[IPCBase], timeout: float | None = None) -> lis
ov.cancel()
raise IPCException(f"Failed to wait for connections: {_winapi.GetLastError()}")

# Check which pending operations completed, cancel the rest
# Cancel all pending operations. CancelIoEx is asynchronous, so an
# operation may have completed before the cancel took effect. We then
# wait for all operations to finalize and check each result: completed
# reads get their data saved and are marked ready; cancelled ones are
# simply skipped. This avoids a race between checking if an operation
# is signaled and cancelling it.
for _, ov in pending:
ov.cancel()
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.

TBH I don't know if this can raise itself. Also maybe we can combine two loops, i.e.:

for i, ov in pending:
    try:
        ov.cancel()
        _, err = ov.GetOverlappedResult(True)
    except OSError as e:
        ...
    ...

(but to be clear I know very little about Windows)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Claude Code was confident that this won't raise, but Codex warned about this potentially raising. I could look into the Python stdlib to see if they expect exceptions in similar calls.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

CPython has two loops for a similar use case, and it also appears to assume that cancel() doesn't raise in a similar use case.

for i, ov in pending:
if _winapi.WaitForSingleObject(ov.event, 0) == _winapi.WAIT_OBJECT_0:
try:
_, err = ov.GetOverlappedResult(True)
except OSError as e:
err = e.winerror
# Cancellation is expected here; broken/disconnected pipes should be
# surfaced as readable so the follow-up receive observes EOF/closure.
if err not in (
_winapi.ERROR_OPERATION_ABORTED,
_winapi.ERROR_BROKEN_PIPE,
_winapi.ERROR_NETNAME_DELETED,
):
# Anything else is a real IPC failure, not part of the probe race.
raise
if err == _winapi.ERROR_OPERATION_ABORTED:
# Operation was successfully cancelled -- no data consumed.
continue
if err in (0, _winapi.ERROR_MORE_DATA):
data = ov.getbuffer()
if data:
conns[i].buffer.extend(data)
ready.append(i)
else:
ov.cancel()
ready.append(i)

return ready

Expand Down
Loading