fix(acp)!: preserve connection lifetimes and graceful drain - #385
Merged
Merged
Conversation
Preserve passive half-closes and poll owned work alongside frame forwarding. Drain accepted output before completion using private built-in transport finish coordination, and update protocol and HTTP consumers to preserve driver identity. Reject escaped output producers on active completion and drain the installed HTTP router before unregistering. Add lifecycle regressions, migration guidance, and focused changelog entries without resource-policy changes. BREAKING CHANGE: ConnectTo::into_channel_and_future now returns (Channel, ConnectionDriver) instead of a channel and boxed future. Custom overrides must mark owned versus passive work; existing raw Channel APIs remain unchanged.
Return None for endpoints without owned work and Some(ConnectionDriver) for genuine futures. Remove passive readiness and is_passive, keeping private finish coordination only on owned drivers. Propagate optional ownership through consumers and preserve all lifetime guarantees. Add compile-fail and absent-work half-close regressions and revise migration guidance. BREAKING CHANGE: ConnectTo::into_channel_and_future now returns (Channel, Option<ConnectionDriver>). Low-level callers must handle absence explicitly instead of awaiting a no-op passive driver.
Expose cooperative finish requests, preserve their contract after an earlier request or future decoration, and give wrappers a map_future path. Apply one drain rule to builder and protocol-router entry points, keep physical write-half shutdown and late-input error handling, and retain HTTP router cancellation ownership. Add public custom-adapter, direct/normalized, and gated-drain regressions and migration guidance.
benbrandt
marked this pull request as ready for review
October 2, 2026 14:56
benbrandt
enabled auto-merge (squash)
October 2, 2026 14:56
Open
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.
Summary
Make connection completion explicit so wrappers do not mistake an endpoint with no owned task for a finished component, hang on unrelated open input, or discard accepted final output.
This is the connection-lifetime slice only. It does not change ACP/MCP wire schemas, dependency pins, raw
Channelsender/receiver types, or introduce backpressure/resource limits.API and migration
ConnectTo::into_channel_and_futurenow returns(Channel, Option<ConnectionDriver>)instead of(Channel, BoxFuture<Result<()>>).Nonemeans an endpoint with no owned work—not EOF and not a ready-success future.ConnectionDriver::new(future)owns opaque work. Finite foreground completion does not indefinitely join arbitrary opaque tasks after protocol handoff.ConnectionDriver::with_finish(future, hook)lets built-in and custom adapters declare cooperative finish. The nonblocking one-shot hook requests stop/seal/drain; the still-polled future proves physical flush/write-half-close and reports errors. No implicit timeout is added.request_finish()is idempotent:truemeans supported and requested, including an earlier request;falsemeans opaque. The callback runs at most once. Already-requested work retains its graceful contract across handoff.map_futuresupports tracing, error annotation, and completion cleanup without losing finish capability or requested state. Transparent wrappers preserve the original optional driver unchanged.Most components implementing only
connect_tokeep the default conversion. Custom conversion overrides and low-level callers must handle absence explicitly. Buffered custom transports that need finite physical-drain guarantees expose the cooperative hook and coordinate it in both direct and normalized entry points.Migration guide · Public driver API
Lifecycle fixes
Regression evidence
Retained tests cover the previously reproduced failures: direct builder output truncation, escaped-producer completion hangs, split-stream write EOF, late input cancelling gated flush, and HTTP shutdown during router drain. Public custom-adapter coverage adds one-byte physical write backpressure, a separate flush gate, exact flush-error propagation, direct/normalized/decorated entry points, already-requested handoff, opaque cancellation, and one-shot/drop behavior.
Review order:
Validation
Passed locally on the committed source, with incremental compilation/debug info disabled and build warnings denied:
just test, including doctests and compile-fail coverage-D warnings)wasm32-wasip1,wasm32-wasip2, andwasm32-unknown-unknownNo manifest, lockfile, or CI workflow changes. Released changelog history is preserved.
Draft pending GitHub CI on this branch and final human review. Nothing has been merged or released.