daemon: Harden request cancellation and transport ownership - #225
Draft
Preovaleo wants to merge 8 commits into
Draft
daemon: Harden request cancellation and transport ownership#225Preovaleo wants to merge 8 commits into
Preovaleo wants to merge 8 commits into
Conversation
added 8 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.
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 the cancellation and request ownership fixes from #220. It builds on the shared transport lifecycle introduced in part 1.
Two lifecycle problems remained:
Discovery also let individual transports read the active request separately, allowing ownership and cancellation data to come from different snapshots.
This PR:
Result
Cancellation no longer depends on additional transport activity.
A terminal event can complete a request only if its ownership still matches the active context. An event from an earlier request cannot consume the response channel or complete a replacement request.
Every transport selected by a discovery attempt receives the same request snapshot, including its identity and cancellation token.
The post-poll cancellation check also discards a terminal event when polling the underlying stream makes that event ready while cancelling its request.
Scope and dependencies
This PR depends on part 1:
perso/generic_transport-split/1Review the changes relative to that branch.
Failure after global transport exhaustion is introduced in part 3. The D-Bus discovery concurrency bound is handled independently in part 4.
Diff composition
9655d8d— Wake cancelled streams214a719— Bind results to their requestc04af6b— Share one lifecycle snapshotd785725— Test cancellation during pollingCommit sequence and rationale
The commit order follows the dependencies between these changes:
daemon: Wake credential streams when requests are cancelled
The common stream still waits on transport activity before noticing cancellation. Making that wait cancellation-aware addresses the case where the transport remains idle indefinitely. This comes first because the stream must be able to stop independently of its backend; it does not yet establish whether a result belongs to the active request.
daemon: Bind terminal events to their originating request
Cancellation and request ownership answer different questions. A late result must not consume whichever context happens to be active when it arrives. The stream therefore carries its originating request ID and a unique marker, and
complete_requestchecks ownership while taking the context under the same lock. The marker ties completion to a particular request instance, including when numeric IDs coincide. This makes acceptance of a terminal result depend on the context it owns, rather than on an earlier observation of cancellation.daemon: Carry one lifecycle snapshot across transports
Once ownership is explicit, discovery must distribute it consistently. Separate context reads for each transport could straddle a request replacement and create one discovery attempt from different requests. Capturing the request and
RequestLifecycletogether, once, gives every selected transport the same request, identity, and cancellation token. This follows the ownership commit because it carries that identity through discovery setup; it also provides the snapshot used by aggregate completion in part 3.daemon: Test simultaneous terminal cancellation
There is a remaining boundary in the polling contract: the underlying stream can cancel the token and return a terminal event in the same poll. The test makes that happen inside the poll itself, so it exercises the post-poll cancellation check without relying on task scheduling. It adds coverage for a guard already present in the common stream and explains why the cancellation-aware wait does not make that guard redundant.
docs: Describe request ownership and prompt cancellation
The documentation brings the two guarantees together: cancellation wakes pending streams, and completion is scoped to the originating request. Recording them separately matters because prompt cancellation alone would not describe the protection against stale results.
Testing
Validation performed at the branch tip:
cargo test -p credentialsd --bin credentialsd— 33 tests passedcargo clippy -p credentialsd --bin credentialsd -- -Dwarnings— passedcargo fmt --all --check— passedgit diff --check— passedThe suite includes the transport coverage from part 1 and regressions for pending-stream cancellation, stale terminal events, and terminal events becoming ready during cancellation.