fix(acp): interrupt idle reads on cancellation#782
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughChangesACP read shutdown
Sequence Diagram(s)sequenceDiagram
participant Serve
participant ContextWatcher
participant ReaderCloser
participant ReadLoop
ContextWatcher->>Serve: observe ctx.Done()
Serve->>ReaderCloser: close inbound reader
ReaderCloser->>ReadLoop: unblock ReadBytes
ReadLoop->>Serve: report interrupted termination
Serve->>Serve: cancel in-flight work and await read loop
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR improves ACP server shutdown behavior by ensuring context cancellation can interrupt an otherwise-idle blocking NDJSON read, while preserving “real” terminal read outcomes (EOF / genuine read errors) when the read completes first.
Changes:
- Track an optional
io.Closerfor the input reader and close it on context cancellation to break out of idleReadBytesblocking. - Add single-arbitration logic between cancellation-triggered interruption and terminal read completion, and join the cancellation watcher before returning from
Serve. - Add tests covering idle-read cancellation and cancellation vs. terminal-read-error races for both closable and non-closable readers.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/acp/jsonrpc.go | Retains a closable read transport and adds cancellation-driven read interruption with single-arbitration and watcher join. |
| internal/acp/jsonrpc_test.go | Adds regression tests for idle read cancellation and cancellation/error race behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| readStarted := make(chan struct{}) | ||
| closeCalls := make(chan struct{}, 2) | ||
| reader := testReadCloser{ | ||
| read: func(p []byte) (int, error) { | ||
| close(readStarted) | ||
| return pipeReader.Read(p) | ||
| }, |
| writeStarted := make(chan struct{}) | ||
| releaseWrite := make(chan struct{}) | ||
| writer := testWriter(func(p []byte) (int, error) { | ||
| close(writeStarted) | ||
| <-releaseWrite | ||
| return len(p), nil | ||
| }) |
jatmn
left a comment
There was a problem hiding this comment.
I found issues that need to be addressed before this is ready.
Findings
- [P2] Preserve a terminal read error that races cancellation
internal/acp/jsonrpc.go:165
A real non-EOFReadBytesfailure can occur just asctxis cancelled. If the watcher is scheduled first, it winsendRead(true)and setsreadInterrupted; when the read loop subsequently handles the already-returned error, itsendRead(false)is a no-op and line 175 returnsnil. This silently reports a broken ACP transport as a clean shutdown, despite the stated terminal-error preservation. Record/read the terminal read outcome before allowing cancellation to claim the transport (and add a regression that races a non-EOF read failure with cancellation).
Summary
ServereturnsValidation
go test ./internal/acp -count=1-count=100go test ./internal/cli -count=1go test ./... -count=1go vet ./...go run golang.org/x/vuln/cmd/govulncheck@v1.3.0 ./...withGOTOOLCHAIN=go1.26.5— no vulnerabilities foundgofmt -l internal/acp/jsonrpc.go internal/acp/jsonrpc_test.gogit diff --checkThe pinned golangci-lint command still reports the repository's 35 known baseline findings tracked by #743; none are in the files changed here.
Fixes #777
Summary by CodeRabbit
Bug Fixes
Tests