On the Windows AFD selector, a socket registered READABLE | WRITABLE emits a spurious writable event on every read.
After a read returns WouldBlock, IoSource::do_io reregisters the socket's full stored interest; set_event resets user_evts, re-adding POLL_SEND, and since the socket is writable the next AFD poll completes immediately with POLL_SEND. So each read attempt produces a WRITABLE event nothing asked for.
Impact. This livelocks a tokio reader driven from a non-runtime thread — the spurious writable bumps the readiness tick and invalidates the reader's clear_readiness, so poll_read spins. Verified repro (Hyper): tokio-rs/tokio#8054.
Fix direction. Re-arm only the direction that blocked ("only reregister read", from the tokio-rs/tokio#8054 discussion). The wrinkle: do_io is direction-blind — Read/Write call do_io(f) with an opaque closure — so narrowing the re-arm touches the public do_io/try_io surface, not just the AFD SockState. Design discussion on tokio-rs/tokio#8054.
On the Windows AFD selector, a socket registered
READABLE | WRITABLEemits a spurious writable event on every read.After a read returns
WouldBlock,IoSource::do_ioreregisters the socket's full stored interest;set_eventresetsuser_evts, re-addingPOLL_SEND, and since the socket is writable the next AFD poll completes immediately withPOLL_SEND. So each read attempt produces aWRITABLEevent nothing asked for.Impact. This livelocks a
tokioreader driven from a non-runtime thread — the spurious writable bumps the readiness tick and invalidates the reader'sclear_readiness, sopoll_readspins. Verified repro (Hyper): tokio-rs/tokio#8054.Fix direction. Re-arm only the direction that blocked ("only reregister read", from the tokio-rs/tokio#8054 discussion). The wrinkle:
do_iois direction-blind —Read/Writecalldo_io(f)with an opaque closure — so narrowing the re-arm touches the publicdo_io/try_iosurface, not just the AFDSockState. Design discussion on tokio-rs/tokio#8054.