Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions cmds/dutctl/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
defer cancel()

errChan := make(chan error, numWorkers)
recvDone := make(chan struct{})

stream := app.rpcClient.Run(runCtx)
req := &pb.RunRequest{
Expand Down Expand Up @@ -127,6 +128,7 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {

// Receive routine
go func() {
defer close(recvDone)
defer cancel()

for {
Expand Down Expand Up @@ -226,10 +228,9 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
}
}()

// Send routine
// Send routine — stdin EOF must not cancel the context; only the receive
// routine drives termination so that all server responses are processed.
go func() {
defer cancel()

reader := bufio.NewReader(app.stdin)

for {
Comment on lines +231 to 236
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that stdin EOF no longer cancels the context (good), the send routine still exits on io.EOF without half-closing the client->server request stream. Server-side code treats client stream closure (io.EOF on Receive) as the signal that no more requests are coming; consider explicitly closing the request side (e.g., stream.CloseRequest()/equivalent for connect.BidiStreamForClient) when stdin reaches EOF so the server can stop waiting for further client messages without relying on full context cancellation.

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -267,9 +268,10 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
}
}()

// Wait for completion or error
// Wait for the receive routine to finish (stream closed or cancelled) or for
// any worker to report an error.
select {
case <-runCtx.Done():
case <-recvDone:
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recvDone is closed unconditionally in the receive goroutine, including when that goroutine hits a non-EOF error and sends it on errChan. In that situation both recvDone and errChan can become ready, and the final select may pick recvDone and return nil, silently dropping the receive error (same for a send error racing with receive completion). Consider making the completion signal carry an error (e.g., recvDone chan error) or, at minimum, ensure the recvDone path checks/drains errChan before returning nil so real worker errors can’t be lost.

Suggested change
case <-recvDone:
case <-recvDone:
// Prefer any pending worker error over a silent success.
select {
case err := <-errChan:
if err != nil {
return err
}
default:
}

Copilot uses AI. Check for mistakes.
return nil
case err := <-errChan:
return err
Expand Down
Loading