Repository navigation
fix: nonblocking delegated fds and correct error logging - #14
Merged
Merged
Conversation
A caller may delegate a descriptor it also drives with an async runtime, so it arrives here non-blocking -- and `dup` shares the open file description, so the sender-side pump inherits that whether it wants to or not. The pumps assumed otherwise. `write_all_blocking` treated `EAGAIN` as fatal, so the first time a peer wrote faster than the far end read, the writer gave up and dropped the rest of the stream with no error reported anywhere. The reader had a milder version: a readiness report is not a guarantee, and a bare `EAGAIN` after `poll` was taken for a broken descriptor and closed the stream. Now `EAGAIN` means wait and retry, which is what a blocking descriptor would have done for us. The writer waits for `POLLOUT`, treating a hangup as `EPIPE` so it cannot wait forever on a descriptor that will never drain; the reader goes back to waiting. Blocking threads stay: caller descriptors may be regular files or ttys, which epoll cannot watch. This only makes them indifferent to the mode of the descriptor they were handed. The test delegates a non-blocking socket as stdout and drains it late, so the buffer is full while the daemon still has output to deliver. Without the fix it receives exactly one socket buffer of a four megabyte payload.
…ting The client printed a daemon's `Error` message to the process's stderr and then dropped it. For the `capsudo` binary that happened to be right; for anything embedding this library it is not. Protect's daemon serves object capabilities in-process, so a refused session printed a bare line outside its logging, unattributed to the capability that produced it, and the caller never saw the text at all. `SessionOutcome::Failed` now carries the message alongside the exit code. It is kept distinct from `Exited` because the two mean different things: one is a program that ran and failed, the other a program that never ran. `run_client`, which reports an exit status, turns it into the new `CoreError::Refused` for the same reason. Printing is the binary's job, and it still does it. Also stops printing on unexpected message types: a peer is free to send messages a given version does not know about, and saying so on stderr is noise, not diagnosis. Note for callers: `SessionOutcome` gained a variant, so exhaustive matches need an arm.
azenla
approved these changes
Aug 12, 2026
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 free
to 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.
Two fixes that surfaced while embedding this library in Protect's daemon, which serves object capabilities in-process rather than from a CLI. Both are places where the library assumed it was the capsudo binary.