daemon: Fail discovery when all transports exit silently - #226
Draft
Preovaleo wants to merge 10 commits into
Draft
daemon: Fail discovery when all transports exit silently#226Preovaleo wants to merge 10 commits into
Preovaleo wants to merge 10 commits into
Conversation
added 10 commits
September 5, 2026 15:52
The push-based helper was embedded in the credential service tests, and its cancellation-focused names hid that it could script any transport state. Move it to test_support, rename it around that broader role, and share typed completion and failure helpers across USB, hybrid, and NFC tests.
USB, hybrid, and NFC duplicated state conversion and request completion in separate stream wrappers, making their terminal behavior easy to desynchronize. Route typed transport events through one lifecycle stream and cover success and failure for every backend.
A lifecycle stream only noticed cancellation after another transport event, so idle discovery could remain pending. Await transport polling through CancellationToken::run_until_cancelled so cancellation wakes the stream immediately.
A late terminal event could consume whichever request context was active and complete a newer request. Attach the request ID and a unique marker to each lifecycle stream, then ignore terminal results whose ownership no longer matches.
Discovery let each transport read the active request independently, so a replacement between reads could mix ownership and cancellation data. Snapshot the request ID, marker, and token once and pass that typed lifecycle to every selected transport.
A terminal event can become ready in the same poll that cancels its request. Without the post-poll cancellation check, that event could still consume the active context. Cancel the token from inside the scripted poll and verify that the terminal result is discarded without sending a response.
When the final selected transport ended without a terminal result, discovery returned EOF while the request stayed active and its caller could wait indefinitely. Complete that request with a transport-independent failure while ignoring exhaustion from stale or explicitly cancelled discovery.
This was referenced Sep 5, 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.
Split and stacked review
Original PR: linux-credentials/credentialsd#220.
The replacement is split into four parts. Parts 1 → 2 → 3 form native GitHub stack #4 on the fork; part 4 is independent.
main)/1→main/2→/1/3→/2Review and integrate parts 1 → 2 → 3 in order. Part 4 can be reviewed and merged independently.
GitHub does not support native stacks across forks. Use Files changed on the fork PRs for incremental review of each layer. The upstream PRs target
main; #225 and #226 include earlier layers until their dependencies land and the branches are rebased. Integration into the main project happens through the upstream PRs; merging the fork stack only updates the fork.Hello 👋,
Summary
This PR extracts silent transport exhaustion handling from #220. It builds on the request lifecycle and ownership guarantees introduced in parts 1 and 2.
Previously, every selected transport could end without producing a terminal result. The merged discovery stream would then end while the credential request remained active, leaving its caller waiting indefinitely.
This PR:
Result
A transport ending without a result does not immediately fail discovery. Other selected transports can continue and complete the request.
If all selected transports end without a terminal event, the daemon completes the request with an internal error and emits the corresponding failure update.
Explicit cancellation retains its cancellation outcome. Exhaustion from an older discovery stream cannot consume or fail a newer request. A valid terminal result continues to complete the request and cancel competing transports.
Scope and dependencies
This is part 3 of the split. It depends on part 2, which depends on part 1.
The focused review base is
perso/generic_transport-split/2. Review and integrate the parts in the order 1 → 2 → 3. Part 4 is independent.Authenticator enumeration, dynamic source-list updates, and retry policy remain separate concerns tracked by #32 and #206.
Diff composition
Commit sequence and rationale
The commit order follows the dependencies between these changes:
daemon: Fail discovery when all transports exit silently
A transport reaching EOF is not enough to conclude that the request cannot succeed: another selected transport may still produce a credential. The failure decision therefore belongs around the merged discovery stream. This is why the change adds aggregate lifecycle handling after parts 1 and 2, using their request snapshot to scope completion and distinguish exhaustion from cancellation.
No individual transport supplies the error in this case, so
DeviceStateUpdate::Failedrepresents the aggregate outcome and maps it to the UI error event. Resolving the caller's response and exposing that failure to the UI are included together. The accompanying tests establish the boundary of the decision: partial exhaustion leaves discovery running, global exhaustion ends it, and cancelled or stale discovery cannot fail a replacement request. The concurrent-terminal test also exercises how the new wrapper composes with the single accepted completion from part 2.docs: Describe silent transport exhaustion
The architecture and changelog record the new termination rule once the aggregate behavior is in place. The rule applies to exhaustion of the selected streams; decisions about selecting new authenticators or starting another discovery attempt remain with the discovery policy.
Testing
Validation performed at the branch tip during the split:
cargo test -p credentialsd --bin credentialsd— 38 tests passedcargo clippy -p credentialsd --bin credentialsd -- -Dwarnings— passedcargo fmt --all --check— passedgit diff --check— passedThe tests check response delivery, public failure events, cancellation, and protection of a newer request from stale discovery exhaustion.