Skip to content

poll, pselect: handle POLLPRI / errorfds gracefully#754

Open
paralin wants to merge 1 commit intoWebAssembly:mainfrom
paralin:fix-pselect-errorfds
Open

poll, pselect: handle POLLPRI / errorfds gracefully#754
paralin wants to merge 1 commit intoWebAssembly:mainfrom
paralin:fix-pselect-errorfds

Conversation

@paralin
Copy link

@paralin paralin commented Mar 3, 2026

pselect() returned ENOSYS when errorfds had any bits set. This breaks programs that use the standard POSIX pattern of passing exceptfds to select() for EOF/error monitoring (e.g., vim, ncurses applications).

Since WASI has no out-of-band data mechanism, errorfds can never have any exceptional conditions to report. Clear it instead of rejecting the call, matching the behavior callers expect from a POSIX-compatible select().

@paralin
Copy link
Author

paralin commented Mar 3, 2026

vim and other programs hit this ENOSYS and break/exit; so returning all-zero is correct.

in practice there are only two cases I found where these "exceptional cases" would ever occur:

  1. TCP out-of-band (OOB) data: TCP has a "urgent" flag: sender can mark a single byte as out-of-band. select() reports the socket as having an exceptional condition.
  2. Pseudoterminal packet mode: (via TIOCPKT)

exceptfds doesn't report general errors like broken pipe or connection reset, these would show up as errors via getsockopt(SO_ERROR).

@alexcrichton
Copy link
Collaborator

Reading over the pselect man page, it seems like the "most correct" thing to do here would be to turn anything in exceptfds into a POLLPRI request sent to poll. If that were done, however, I believe poll would fail in the same way and return ENOSYS as well.

Personally it feels incorrect to me to ignore parameters and proceed since if those were the only fds set, for example, then there's no way for the function to wake up. Overall I feel that returning an error for "you requested to do something that isn't implementable" is the right behavior here as it's an accurate reflection of the state of the world. Would it be possible to patch the application you're working with to avoid using exceptfds on WASI platforms?

@paralin
Copy link
Author

paralin commented Mar 3, 2026

It's obviously possible to patch the programs, but I have a feeling it would end up becoming a nightmare patching every single program that calls this API. Fwiw, I tested it and it works correctly with the PR as is. If there's a more correct way to do it, I can look into it. Emulating the condition never happening seems to make sense to me, for compatibility, as opposed to ENOSYS, particularly with it being such a rare condition anyway. But I understand if you want to reject.

I guess I could send a PR to vim to handle ENOSYS for this call properly.

@alexcrichton
Copy link
Collaborator

How about a variation of this:

  • Update the implementation of pselect to process exceptfds by threading POLLPRI to poll. This'd resolve the usage of pselect and move the "problem child" to poll itself.
  • Within poll if anything is listening for something other than POLLPRI allow the operation to happen (e.g. pretending it never happens since it's not implemented in wasi-libc). If, however, POLLPRI is the exclusive set of things being listened on an error is returned to indicate that it's not supported.

That should get pselect working I believe for these use cases where there's something else to listen with, and it'll also preserve a bit of the original intent of "this isn't actually implemented" too. Does that sound reasonable?

If so would you be up for including a test here as well to exercise this new behavior?

@paralin
Copy link
Author

paralin commented Mar 3, 2026

I'll work on it! Thanks!

@paralin
Copy link
Author

paralin commented Mar 3, 2026

@alexcrichton POLLPRI - usually 0x002 - is not defined in wasi-libc and it's already taken actually -

POLLWRNORM 0x2 <-- 0x002 conflicts here

I defined it in my PR as 0x0200 since that was unused. It's only used internally and never passed to the WASI API, so I think this is ok.

This still makes me a bit nervous as I worry that some programs will only sometimes call this with exclusively POLLPRI fds, get an unexpected ENOSYS, and crash, and now it'll be a bit harder to predict if this will happen as it's only sometimes that it will return ENOSYS.

I added the test as requested - ready for your review.

paralin added a commit to paralin/wasi-libc that referenced this pull request Mar 3, 2026
WASI has no out-of-band data, so POLLPRI can never fire. Rather than rejecting
any use of POLLPRI outright, adopt a lenient policy:

- Define POLLPRI in __header_poll.h for source compatibility.
- In poll(): if any fd requests non-POLLPRI events, strip POLLPRI and
  proceed (it simply never fires). Only return ENOSYS when every fd
  exclusively requests POLLPRI, since there is nothing useful to poll.
- In pselect(): thread errorfds through poll() as POLLPRI entries so
  the call succeeds when readfds or writefds are also provided.

This fixes programs (e.g. vim) that pass errorfds to select/pselect
alongside other fd sets.

Includes a test exercising pselect with errorfds and poll with POLLPRI.

See: WebAssembly#754 (comment)

Signed-off-by: Christian Stewart <christian@aperture.us>
@paralin paralin force-pushed the fix-pselect-errorfds branch from c0d23c8 to cdf682d Compare March 3, 2026 23:35
paralin added a commit to paralin/wasi-libc that referenced this pull request Mar 3, 2026
WASI has no out-of-band data, so POLLPRI can never fire. Rather than rejecting
any use of POLLPRI outright, adopt a lenient policy:

- Define POLLPRI in __header_poll.h for source compatibility.
- In poll(): if any fd requests non-POLLPRI events, strip POLLPRI and
  proceed (it simply never fires). Only return ENOSYS when every fd
  exclusively requests POLLPRI, since there is nothing useful to poll.
- In pselect(): thread errorfds through poll() as POLLPRI entries so
  the call succeeds when readfds or writefds are also provided.

This fixes programs (e.g. vim) that pass errorfds to select/pselect
alongside other fd sets.

Includes a test exercising pselect with errorfds and poll with POLLPRI.

See: WebAssembly#754 (comment)

Signed-off-by: Christian Stewart <christian@aperture.us>
@paralin paralin force-pushed the fix-pselect-errorfds branch from cdf682d to 8260d16 Compare March 3, 2026 23:36
paralin added a commit to paralin/wasi-libc that referenced this pull request Mar 3, 2026
WASI has no out-of-band data, so POLLPRI can never fire. Rather than rejecting
any use of POLLPRI outright, adopt a lenient policy:

- Define POLLPRI in __header_poll.h for source compatibility.
- In poll(): if any fd requests non-POLLPRI events, strip POLLPRI and
  proceed (it simply never fires). Only return ENOSYS when every fd
  exclusively requests POLLPRI, since there is nothing useful to poll.
- In pselect(): thread errorfds through poll() as POLLPRI entries so
  the call succeeds when readfds or writefds are also provided.

This fixes programs (e.g. vim) that pass errorfds to select/pselect
alongside other fd sets.

Includes a test exercising pselect with errorfds and poll with POLLPRI.

See: WebAssembly#754 (comment)

Signed-off-by: Christian Stewart <christian@aperture.us>
@paralin paralin force-pushed the fix-pselect-errorfds branch from 8260d16 to c68ff2c Compare March 3, 2026 23:58
@paralin paralin changed the title pselect: clear errorfds instead of returning ENOSYS poll, pselect: handle POLLPRI / errorfds gracefully Mar 3, 2026
WASI has no out-of-band data, so POLLPRI can never fire. Rather than rejecting
any use of POLLPRI outright, adopt a lenient policy:

- Define POLLPRI in __header_poll.h for source compatibility.
- In poll(): if any fd requests non-POLLPRI events, strip POLLPRI and
  proceed (it simply never fires). Only return ENOSYS when every fd
  exclusively requests POLLPRI, since there is nothing useful to poll.
- In pselect(): thread errorfds through poll() as POLLPRI entries so
  the call succeeds when readfds or writefds are also provided.

This fixes programs (e.g. vim) that pass errorfds to select/pselect
alongside other fd sets.

Includes a test exercising pselect with errorfds and poll with POLLPRI.

See: WebAssembly#754 (comment)

Signed-off-by: Christian Stewart <christian@aperture.us>
@paralin paralin force-pushed the fix-pselect-errorfds branch from c68ff2c to 19c51b1 Compare March 4, 2026 00:03
@paralin
Copy link
Author

paralin commented Mar 4, 2026

CI green 👍🏽

@alexcrichton
Copy link
Collaborator

Reading over this it all looks pretty good, thanks! W.r.t POLLPRI that's currently setup to be a public symbol exposed to wasi-libc users, which is also where the CI failures originally came from (thanks for bearing through the failures...)

You have a good point though, and it also raises another question for me. You say that vim and other programs were the impetus here, but do know of more details about the use case here? For example do you know if these applications just blanket insert POLLPRI to all file descriptors? Or are they specifically looking for a particular event on a specific file descriptor (e.g. some tty event or some out-of-band TCP data). Additionally, with POLLPRI missing from wasi-libc, how were these applications compiled against wasi-libc in the first place?

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.

2 participants