You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The selector-side twin of #156333. BaseSelectorEventLoop also wakes itself through a self-socket created by socket.socketpair(). On Windows this is a loopback TCP pair, and if the connection reaches a clean EOF while the loop runs, _read_from_self returns but the reader is still registered on the dead fd — select() reports it readable immediately, forever, pinning one core at 100% with nothing logged.
At EOF, recv() returns b'', the loop breaks, and the callback returns. But the fd is still registered via _add_reader(self._ssock.fileno(), self._read_from_self) from _make_self_pipe(). A closed-for-read socket is permanently "readable", so every select() iteration re-fires the callback — a tight spin. The loop is otherwise idle and never recovers.
The proactor half of this bug is fixed by #156343 (rebuild the pair on the empty result). The same OS teardown of the loopback pair that triggers it triggers this on any WindowsSelectorEventLoopPolicy process.
Measured on Windows 11, CPython main (3.16.0a0, self-built): 582,692 callback invocations, 1.23s CPU, during a 3-second idle sleep. Expected ~0.
Note the instrumentation must patch the class before the loop is created — _make_self_pipe() captures the bound method at registration time, so patching an instance afterwards does not intercept the already-armed reader (which is also why the spin is invisible to profilers that hook late).
Unix loops use an AF_UNIX pair, which the OS does not tear down this way, so the organic trigger is Windows-specific; a deterministic test using shutdown(SHUT_WR) works on any platform, though.
On recv() == b'', rebuild the self-pipe the way #156343 does for the proactor loop: allocate the new pair first, move any signal wakeup fd registration (set_wakeup_fd() returns the previous fd, so it can be probed and moved only when it names the old socket — Unix loops with signal handlers), remove the old reader, close the old sockets, and register the reader on the new fd.
Bug description
The selector-side twin of #156333.
BaseSelectorEventLoopalso wakes itself through a self-socket created bysocket.socketpair(). On Windows this is a loopback TCP pair, and if the connection reaches a clean EOF while the loop runs,_read_from_selfreturns but the reader is still registered on the dead fd —select()reports it readable immediately, forever, pinning one core at 100% with nothing logged.Lib/asyncio/selector_events.py:At EOF,
recv()returnsb'', the loopbreaks, and the callback returns. But the fd is still registered via_add_reader(self._ssock.fileno(), self._read_from_self)from_make_self_pipe(). A closed-for-read socket is permanently "readable", so everyselect()iteration re-fires the callback — a tight spin. The loop is otherwise idle and never recovers.The proactor half of this bug is fixed by #156343 (rebuild the pair on the empty result). The same OS teardown of the loopback pair that triggers it triggers this on any
WindowsSelectorEventLoopPolicyprocess.Reproducer (deterministic, Windows)
Measured on Windows 11, CPython main (3.16.0a0, self-built): 582,692 callback invocations, 1.23s CPU, during a 3-second idle sleep. Expected ~0.
Note the instrumentation must patch the class before the loop is created —
_make_self_pipe()captures the bound method at registration time, so patching an instance afterwards does not intercept the already-armed reader (which is also why the spin is invisible to profilers that hook late).Environment
_read_from_selfcode path shown above is unchanged on mainNotes on scope
shutdown(SHUT_WR)works on any platform, though.ConnectionResetErrorfrom the pending recv instead of a clean EOF; that path is currently unhandled too (noted in gh-156333: rebuild the proactor self-pipe on EOF instead of busy-looping #156343).Suggested direction
On
recv() == b'', rebuild the self-pipe the way #156343 does for the proactor loop: allocate the new pair first, move any signal wakeup fd registration (set_wakeup_fd()returns the previous fd, so it can be probed and moved only when it names the old socket — Unix loops with signal handlers), remove the old reader, close the old sockets, and register the reader on the new fd.Linked PRs