Skip to content

Add Poll::prepare_external_wait - #2030

Open
guybedford wants to merge 1 commit into
tokio-rs:masterfrom
guybedford:external-wait
Open

guybedford wants to merge 1 commit into
tokio-rs:masterfrom
guybedford:external-wait

Conversation

@guybedford

@guybedford guybedford commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

This implements an alternative to #2023 providing a Poll::prepare_external_wait for integrating with host event loops cross-platform.

The hosted event loop concept in tokio-rs/tokio#8479 is that the host waits on the selector's handle instead of calling Poll::poll to block. This works fine on the Unix the epoll/kqueue fd, but on Windows, a raw completion port handle submits socket interest lazily, at the start of select, unless a thread is already blocked in it. A source that was registered or reregistered, or that returned WouldBlock, since the last poll (or that does so on another thread during the host's wait) never signals the port, and the host misses the event.

Whether such a cooperative model on Windows should be supported in such a way in Mio is of course an outstanding question, so this is posted just to push the conversation forward as opposed to trying to set an explicit direction, while I've been working on this problem.

This instead creates a contract for the hosted model using a new prepare_external_wait() method, which replaces the raw handle/fd usage:

impl Poll {
    #[cfg(unix)]
    pub fn prepare_external_wait(&self) -> io::Result<BorrowedFd<'_>>;
    #[cfg(windows)]
    pub fn prepare_external_wait(&self) -> io::Result<BorrowedHandle<'_>>;
}
  • Until the next Poll::poll, the handle becomes ready whenever events may be available, including for sources registered, reregistered or returning WouldBlock on any thread after the call. Waiting doesn't consume events, and a ready handle doesn't guarantee events.
  • Windows: submits the queued socket interest, then puts the selector into an external wait state where (re)registrations are submitted immediately, as they already are while a thread is blocked in select. The next select ends it.
  • epoll/kqueue: returns the descriptor and does nothing else.
  • poll(2) and Solaris event ports: return ErrorKind::Unsupported, since neither has a descriptor that reflects all registrations (event ports watch delivered interests with poll(2) inside select).

A host can then integrate its own loop something like:

loop {
    poll.poll(&mut events, Some(Duration::ZERO))?;
    // handle events
    host_wait(poll.prepare_external_wait()?);
}

Tests cover registration, reregistration and WouldBlock before and after the call and from other threads, blocked waits from the polling thread and from a thread that never polled, and WaitForMultipleObjects / MsgWaitForMultipleObjectsEx on Windows.

Returns the selector's waitable handle for a wait outside of Poll::poll,
such as by a host event loop. On Windows, socket interest is submitted to
the kernel lazily at the start of select, so this submits the queued
interest and, until the next poll, submits further (re)registrations
immediately, as when a thread is blocked in select.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant