Uh oh!
There was an error while loading. Please reload this page.
Report exhausted host fds as ENOMEM, not EBADF - #323
Open
xalestar wants to merge 1 commit into
Open
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 6 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
A guest thread's wait list is duplicated descriptor by descriptor while siblings are alive, so a long list can run the host table out partway through. fd_snapshot_and_dup reports that with the same -1 it uses for a slot that was never open, and host_fd_ref_open_io_state turns both into EBADF. ppoll then marks the entry POLLNVAL and pselect6 fails the whole call with EBADF, naming descriptors that are still open. The standard reaction to either is to close the descriptor, so running the host out of fds made the guest tear down its own working connections, and the pselect6 spelling does not even say which one. The two failures are already distinguishable at the point where they merge: the snapshot survives only when the slot held a file, so a failure after it is the dup. That case returns ENOMEM now, which is what Linux itself reports for a wait whose tables it cannot build. O_PATH is classified before the exhaustion is, since it is rejected either way and its answer must not move with how full the host table is. The helper is shared, so the other callers inherit the split. Their reads and writes and ioctls answered an exhausted host table with EBADF too, and an open descriptor is not a bad one; ENOMEM is undocumented for a few of them where EBADF was simply wrong. The 0/-1 spelling next door, host_fd_ref_open, collapses the same two failures across 54 call sites and is left alone: unpicking it means changing a return convention, not adding a case to one. Nothing here reduces the number of descriptors a wait consumes. The call that ran the table out still fails; it just no longer blames the caller's descriptors for it. tests/test-poll-fd-exhaustion.c keeps a sibling thread alive, fills the descriptor table, and requires that no still-open descriptor comes back POLLNVAL and that pselect6 does not fail with EBADF. It lowers its own RLIMIT_NOFILE to what its array can hold first, so the fill ends in EMFILE rather than at an array bound that would prove nothing, and it refuses to report a green run when that precondition is not met. It passes against a real kernel under qemu-system-aarch64, where the wait paths never duplicate anything.
xalestarforce-pushed
the
poll-fd-exhaustion-enomem
branch
from
August 22, 2026 16:25
42d3318 to
7b738afCompare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ppollandpselect6duplicate every descriptor in a wait list while sibling threads are alive. Running the host descriptor table out that way reached the guest as POLLNVAL or EBADF on descriptors that were still open, and the standard reaction to both is to close them. Exhaustion returns ENOMEM now, which is what Linux reports for a wait whose tables it cannot build.Follow-up to #316 and #318, which covered descriptors the host
poll()refuses. This one is about descriptors the host never got to look at.Measured
Host soft limit 1280, one temp file duped until EMFILE. Single-threaded guests borrow rather than duplicate, so the second thread is what triggers it.
Scope notes
The other callers inherit the split.
host_fd_ref_open_io{,_gen,_state}has 24 call sites, 22 outside the wait paths, and all butfuse.cpropagate the return value.read,ioctl,splice,copy_file_range,fsyncand friends answer an exhausted host table with ENOMEM now. They were returning EBADF for a descriptor that is open, so the path was already wrong; ENOMEM is undocumented for a couple of them (read,lseek) and documented for others (splice,copy_file_range,process_vm_readv). No existing test depended on the old value.host_fd_ref_openis left alone. The 0/-1 spelling next door collapses the same two failures across 54 call sites that each pick their own errno. That is a return-convention change, not an added case.Descriptor usage is unchanged. A wait consumes threads x list length, which
HOST_FD_RESERVE's two-per-thread budget does not cover, as the comment insrc/elfuse-limits.hnotes. The call still fails; it just stops blaming the caller's descriptors.Test plan
tests/test-poll-fd-exhaustion.cholds a sibling thread open, fills the descriptor table, and requires that no still-open descriptor comes back POLLNVAL and thatpselect6does not fail with EBADF. Intests/manifest.txtwithhost_nofile=elfuse-minimum, and intests/test-matrix.sh.main(628181b)qemu-system-aarch64make check,make check-format, all six.ci/*.shbuild/test-pollThe reference-kernel run is what backs the matrix entry: a real kernel never duplicates here, so the test asserts Linux behaviour rather than an elfuse-specific expectation.
Summary by cubic
Report host FD table exhaustion as ENOMEM instead of EBADF/POLLNVAL. Previously,
ppollmarked entries POLLNVAL andpselect6failed with EBADF when duplication ran out; now both return ENOMEM, preventing valid descriptors from being closed and matching Linux.Notes for review
host_fd_ref_open_io_state: return ENOMEM whendup()fails after a live snapshot; return EBADF for closed, out-of-range, orO_PATHdescriptors.ppoll/pselect6to propagate ENOMEM on mid-list exhaustion and avoid emitting POLLNVAL/EBADF for open descriptors; other callers ofhost_fd_ref_open_io{,_gen,_state}inherit ENOMEM on host table exhaustion, whilehost_fd_ref_openis unchanged.tests/test-poll-fd-exhaustion.cand wire it into the build/matrix to verify no POLLNVAL/EBADF on open descriptors and ENOMEM on exhaustion.Written for commit 7b738af. Summary will update on new commits.