fix(capsule/net): TcpStream::{read_bytes,peek} surface cancellation as Closed#760
Conversation
…s Closed Closes #759 Both methods previously collapsed cancel-token fire into Ok(Vec::new()), which is indistinguishable from a clean EOF in byte-stream reads. The framed read path (read_frame) and write side (write_bytes, shutdown) already return Err(ErrorCode::Closed) on cancellation — read_bytes / peek were the outliers. Capsules with EOF-triggered finalizers (write trailers, send last-message IPC, flush log, transition to "stream complete") would execute those finalizers under a forced unload when the cancel fires mid-read. Now matches the rest of the TcpStream surface: empty Vec keeps its "clean EOF" meaning; cancellation surfaces as its own error variant. Reported by Gemini on PR #758. Bundled CHANGELOG cleanup - Consolidate duplicate Added/Changed blocks in [Unreleased] (created when #752 and #746 landed independently with their own roll-up entries). Now: single Added block, single Changed block, single Fixed block. - Add Fixed entry for the read_bytes / peek change above.
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a subtle correctness issue in the network stream handling where cancellation events were being conflated with clean EOF signals. By surfacing cancellation as Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. The stream was closed by sudden hand, But empty bytes were all they planned. Now Closed is sent to signal why, So final tasks don't go awry. Footnotes
|
There was a problem hiding this comment.
Code Review
This PR introduces outbound TCP support for capsules via the net.connect-tcp host function and net_connect capability, alongside a refactor of the manifest and KV storage modules into submodules. It also updates the build toolchain for target-agnosticism and ensures that TCP stream cancellation returns a distinct ErrorCode::Closed instead of an empty vector. Feedback was provided to add missing audit logging to the peek implementation, ensuring consistency with other network operations and proper handling of logging failures.
Reported by Gemini on #760. peek was the only data-transfer method on TcpStream that didn't emit an astrid.audit.net event — read / write / read_bytes / write_bytes / shutdown all do, so peek was the outlier. Peek can leak observable bytes if the airlock/quota ever loosens, so an operator needs the per-call audit signal even if it's a no-op preview. Gemini's suggested snippet wrapped audit_net in if let Err(...); audit_net returns () (it just emits via tracing::debug!), so the correct form is the unadorned call site used everywhere else.
## Linked Issue Closes #757 ## Summary Bump all workspace crates from 0.6.0 to 0.7.0. Big release rolling up the per-domain WIT host ABI migration (#752 — wasi-elimination, every host call routed through audited astrid:* interfaces), outbound TCP for capsules (#746), wasmtime 43 → 45 (closes RUSTSEC-2026-0149), atomic kv_cas, O(1) HostState quota counters, the Gemini review fixups for #752, and the TcpStream cancellation hole closed in #760. ## Changes - Workspace version 0.6.0 → 0.7.0 - All 20 workspace dependency versions updated to 0.7.0 - CHANGELOG [Unreleased] rolled into [0.7.0] (consolidation already done in #760 — Breaking, Added, Changed, Fixed all single-section)
Linked Issue
Closes #759
Summary
Fix the
TcpStream::read_bytes/peekcancellation hole flagged by Gemini on #758 (CHANGELOG.md:48thread), plus a CHANGELOG[Unreleased]cleanup that consolidates duplicate Added/Changed blocks from the independent #752 + #746 roll-ups. Goes in before #758 so the 0.7.0 release inherits the corrected behaviour and cleaner CHANGELOG structure.Changes
crates/astrid-capsule/src/engine/wasm/host/net/tcp_stream.rsread_bytes:None => Ok(Vec::new())→None => Err(ErrorCode::Closed)peek:result.unwrap_or(Ok(Vec::new()))→result.unwrap_or(Err(ErrorCode::Closed))Both methods previously collapsed cancellation into an empty
Vec<u8>, which is indistinguishable from a clean EOF in byte-stream reads (std::io::Read::read/tokio::io::AsyncReadExt::readconvention). The framed read path (read_frame) and the write side (write_bytes,shutdown) already returnErr(ErrorCode::Closed)on cancellation.read_bytes/peekwere the outliers.Capsules with EOF-triggered finalizers (write trailers, send last-message IPC, flush log, transition to "stream complete" state) would execute those finalizers under a forced unload when the cancel fires mid-read. The bug surface is narrow (the capsule has milliseconds to live) but it's a real correctness hole. Now
Closeddistinguishes cancellation from EOF; emptyVeckeeps its "clean EOF" meaning (no behaviour change for the EOF case).CHANGELOG.md([Unreleased]consolidation)### Addedblocks (one from feat(kernel)!: migrate to per-domain WIT host ABI #752, one from feat(net): outbound TCP host fns (full std::net::TcpStream parity) #746) into a single block. Same for### Changed.### Fixedentry for theread_bytes/peekchange above.Test Plan
Automated
cargo test -p astrid-capsule --lib— 272 passed, 0 failedcargo clippy --workspace --all-features -- -D warningscleanManual
Checklist
[Unreleased]